From 34978fcbc4d63cfec58e865304d32cdd19dc1603 Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Thu, 3 Sep 2026 10:41:18 -0400 Subject: [PATCH 1/2] tool: update-rust-sdk.sh script, update rust hash tool: update-cpp-example-collection.sh Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- docs/tools.md | 45 ++++-- scripts/update-cpp-example-collection.sh | 148 +++++++++++++++++++ scripts/update-rust-sdk.sh | 174 +++++++++++++++++++++++ 3 files changed, 357 insertions(+), 10 deletions(-) create mode 100755 scripts/update-cpp-example-collection.sh create mode 100755 scripts/update-rust-sdk.sh diff --git a/docs/tools.md b/docs/tools.md index 362c6c25..8e393bd9 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -163,23 +163,48 @@ For details on the Doxygen configuration and CI pipeline, see the ### Bump the pinned Rust submodule +Update to the commit from the most recent published Rust SDK release: + ```bash -cd client-sdk-cpp -git fetch origin -git switch -c try-rust-main origin/main +./scripts/update-rust-sdk.sh +``` + +The script requires the GitHub CLI (`gh`) when `--hash` is omitted. To select a +specific commit without `gh`, pass its full or abbreviated hash: -# Sync submodule URLs and check out what origin/main pins (recursively): -git submodule sync --recursive -git submodule update --init --recursive --checkout +```bash +./scripts/update-rust-sdk.sh --hash 1a477bc +``` -# If the nested submodule under yuv-sys didn't materialize, force it: -git -C client-sdk-rust/yuv-sys submodule sync --recursive -git -C client-sdk-rust/yuv-sys submodule update --init --recursive --checkout +The script updates the `client-sdk-rust` gitlink and synchronizes its nested +submodules. It stops without changing the checkout if the Rust submodule has +tracked changes. -# Sanity check: +Review the selected commits before committing the gitlink update: + +```bash git submodule status --recursive ``` +### Bump the pinned C++ example collection + +Update to the most recent commit on the `main` branch of the examples +repository: + +```bash +./scripts/update-cpp-example-collection.sh +``` + +To select a specific commit, pass its full or abbreviated hash: + +```bash +./scripts/update-cpp-example-collection.sh --hash 7e76bea +``` + +The script updates the `cpp-example-collection` gitlink and synchronizes its +nested submodules. It stops without changing the checkout if the examples +submodule has tracked changes. + ### If `yuv-sys` fails to build ```bash diff --git a/scripts/update-cpp-example-collection.sh b/scripts/update-cpp-example-collection.sh new file mode 100755 index 00000000..a3c231df --- /dev/null +++ b/scripts/update-cpp-example-collection.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# +# Copyright 2026 LiveKit +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +repo_root="$(cd "${script_dir}/.." && pwd -P)" +examples_dir="${repo_root}/cpp-example-collection" +examples_repo="livekit-examples/cpp-example-collection" +examples_remote="https://github.com/${examples_repo}.git" +default_branch="main" + +usage() { + cat <<'EOF' +Usage: ./scripts/update-cpp-example-collection.sh [--hash HASH] + +Update the cpp-example-collection submodule used by the C++ SDK. + +Options: + --hash HASH + Use the specified examples commit. You can use a short or full SHA-1 + hash. + -h, --help + Show this help and exit. + +When --hash is omitted, the script uses the most recent commit on the main +branch of livekit-examples/cpp-example-collection. +EOF +} + +requested_hash="" +hash_provided=false + +while (($#)); do + case "$1" in + --hash) + shift + if (($# == 0)); then + echo "ERROR: --hash requires a value" >&2 + exit 2 + fi + requested_hash="$1" + hash_provided=true + shift + ;; + --hash=*) + requested_hash="${1#--hash=}" + hash_provided=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "ERROR: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ "$hash_provided" == true && ! "$requested_hash" =~ ^[0-9a-fA-F]{7,40}$ ]]; then + echo "ERROR: --hash must be a 7- to 40-character hexadecimal Git SHA-1 hash" >&2 + exit 2 +fi + +if ! command -v git >/dev/null 2>&1; then + echo "ERROR: git not found in PATH" >&2 + exit 1 +fi + +if ! git -C "$repo_root" rev-parse --show-toplevel >/dev/null 2>&1; then + echo "ERROR: ${repo_root} is not a Git worktree" >&2 + exit 1 +fi + +if [[ ! -e "${examples_dir}/.git" ]]; then + echo "==> Initializing cpp-example-collection" + git -C "$repo_root" submodule update --init cpp-example-collection +fi + +if ! git -C "$examples_dir" diff --quiet || + ! git -C "$examples_dir" diff --cached --quiet; then + echo "ERROR: cpp-example-collection has tracked changes; commit or stash them first" >&2 + exit 1 +fi + +fetch_ref="$requested_hash" + +if [[ "$hash_provided" == false ]]; then + echo "==> Finding the most recent cpp-example-collection commit" + branch_ref="refs/heads/${default_branch}" + remote_ref="$(git ls-remote "$examples_remote" "$branch_ref")" + read -r requested_hash resolved_ref <<<"$remote_ref" + if [[ -z "$requested_hash" || "$resolved_ref" != "$branch_ref" ]]; then + echo "ERROR: branch '${default_branch}' was not found in ${examples_repo}" >&2 + exit 1 + fi + fetch_ref="$branch_ref" + echo "==> Latest ${default_branch} commit: ${requested_hash}" +else + requested_hash="$(printf '%s' "$requested_hash" | tr '[:upper:]' '[:lower:]')" + echo "==> Requested examples commit: ${requested_hash}" +fi + +echo "==> Fetching cpp-example-collection" +if [[ "$hash_provided" == false ]]; then + git -C "$examples_dir" fetch --quiet origin "$fetch_ref" +elif [[ ${#requested_hash} -eq 40 ]]; then + if ! git -C "$examples_dir" fetch --quiet origin "$fetch_ref"; then + git -C "$examples_dir" fetch --quiet origin + fi +else + git -C "$examples_dir" fetch --quiet origin +fi + +if ! resolved_hash="$(git -C "$examples_dir" rev-parse --verify "${requested_hash}^{commit}" 2>/dev/null)"; then + echo "ERROR: '${requested_hash}' does not resolve to a cpp-example-collection commit" >&2 + exit 1 +fi + +current_hash="$(git -C "$examples_dir" rev-parse HEAD)" +if [[ "$current_hash" == "$resolved_hash" ]]; then + echo "==> cpp-example-collection is already at ${resolved_hash}" +else + git -C "$examples_dir" checkout --quiet --detach "$resolved_hash" + echo "==> Updated cpp-example-collection: ${current_hash} -> ${resolved_hash}" +fi + +echo "==> Synchronizing nested example submodules" +git -C "$examples_dir" submodule sync --recursive +git -C "$examples_dir" submodule update --init --recursive + +echo "==> Done. Review and commit the cpp-example-collection gitlink change." diff --git a/scripts/update-rust-sdk.sh b/scripts/update-rust-sdk.sh new file mode 100755 index 00000000..3ae669fd --- /dev/null +++ b/scripts/update-rust-sdk.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# +# Copyright 2026 LiveKit +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +repo_root="$(cd "${script_dir}/.." && pwd -P)" +rust_dir="${repo_root}/client-sdk-rust" +rust_repo="livekit/client-sdk-rust" +rust_remote="https://github.com/${rust_repo}.git" + +usage() { + cat <<'EOF' +Usage: ./scripts/update-rust-sdk.sh [--hash HASH] + +Update the client-sdk-rust submodule used by the C++ SDK. + +Options: + --hash HASH + Use the specified Rust SDK commit. You can use a short or full SHA-1 + hash. + -h, --help + Show this help and exit. + +When --hash is omitted, the script uses the commit tagged by the most recent +published, non-draft GitHub release in livekit/client-sdk-rust (requires gh). +EOF +} + +requested_hash="" +hash_provided=false + +while (($#)); do + case "$1" in + --hash) + shift + if (($# == 0)); then + echo "ERROR: --hash requires a value" >&2 + exit 2 + fi + requested_hash="$1" + hash_provided=true + shift + ;; + --hash=*) + requested_hash="${1#--hash=}" + hash_provided=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "ERROR: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ "$hash_provided" == true && ! "$requested_hash" =~ ^[0-9a-fA-F]{7,40}$ ]]; then + echo "ERROR: --hash must be a 7- to 40-character hexadecimal Git SHA-1 hash" >&2 + exit 2 +fi + +if ! command -v git >/dev/null 2>&1; then + echo "ERROR: git not found in PATH" >&2 + exit 1 +fi + +if ! git -C "$repo_root" rev-parse --show-toplevel >/dev/null 2>&1; then + echo "ERROR: ${repo_root} is not a Git worktree" >&2 + exit 1 +fi + +if [[ ! -e "${rust_dir}/.git" ]]; then + echo "==> Initializing client-sdk-rust" + git -C "$repo_root" submodule update --init client-sdk-rust +fi + +if ! git -C "$rust_dir" diff --quiet || + ! git -C "$rust_dir" diff --cached --quiet; then + echo "ERROR: client-sdk-rust has tracked changes; commit or stash them first" >&2 + exit 1 +fi + +fetch_ref="$requested_hash" +release_tag="" + +if [[ "$hash_provided" == false ]]; then + if ! command -v gh >/dev/null 2>&1; then + echo "ERROR: gh not found in PATH; install GitHub CLI or provide --hash" >&2 + exit 1 + fi + + echo "==> Finding the most recent published Rust SDK release" + release_tags="$( + gh api --paginate "repos/${rust_repo}/releases?per_page=100" \ + --jq '.[] | select(.draft == false) | .tag_name' + )" + release_tag="${release_tags%%$'\n'*}" + if [[ -z "$release_tag" ]]; then + echo "ERROR: no published release found for ${rust_repo}" >&2 + exit 1 + fi + + tag_refs="$( + git ls-remote "$rust_remote" \ + "refs/tags/${release_tag}" "refs/tags/${release_tag}^{}" + )" + while read -r tag_hash tag_ref; do + if [[ "$tag_ref" == "refs/tags/${release_tag}^{}" ]]; then + requested_hash="$tag_hash" + break + fi + if [[ "$tag_ref" == "refs/tags/${release_tag}" ]]; then + requested_hash="$tag_hash" + fi + done <<<"$tag_refs" + + if [[ -z "$requested_hash" ]]; then + echo "ERROR: release tag '${release_tag}' was not found in ${rust_repo}" >&2 + exit 1 + fi + fetch_ref="refs/tags/${release_tag}" + echo "==> Latest release: ${release_tag} (${requested_hash})" +else + requested_hash="$(printf '%s' "$requested_hash" | tr '[:upper:]' '[:lower:]')" + echo "==> Requested Rust SDK commit: ${requested_hash}" +fi + +echo "==> Fetching client-sdk-rust" +if [[ "$hash_provided" == false ]]; then + git -C "$rust_dir" fetch --quiet origin "$fetch_ref" +elif [[ ${#requested_hash} -eq 40 ]]; then + if ! git -C "$rust_dir" fetch --quiet origin "$fetch_ref"; then + git -C "$rust_dir" fetch --quiet origin + fi +else + git -C "$rust_dir" fetch --quiet origin +fi + +if ! resolved_hash="$(git -C "$rust_dir" rev-parse --verify "${requested_hash}^{commit}" 2>/dev/null)"; then + echo "ERROR: '${requested_hash}' does not resolve to a client-sdk-rust commit" >&2 + exit 1 +fi + +current_hash="$(git -C "$rust_dir" rev-parse HEAD)" +if [[ "$current_hash" == "$resolved_hash" ]]; then + echo "==> client-sdk-rust is already at ${resolved_hash}" +else + git -C "$rust_dir" checkout --quiet --detach "$resolved_hash" + echo "==> Updated client-sdk-rust: ${current_hash} -> ${resolved_hash}" +fi + +echo "==> Synchronizing nested Rust SDK submodules" +git -C "$rust_dir" submodule sync --recursive +git -C "$rust_dir" submodule update --init --recursive --checkout + +echo "==> Done. Review and commit the client-sdk-rust gitlink change." From b6be9bd8b2b2b50da69c2ffa0ddf8813bd3fbaed Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Thu, 5 Nov 2026 10:14:16 -0700 Subject: [PATCH 2/2] updates --- docs/tools.md | 31 ++++--- scripts/update-cpp-example-collection.sh | 72 ++-------------- scripts/update-rust-sdk.sh | 101 ++++------------------- scripts/update-submodule-common.sh | 99 ++++++++++++++++++++++ 4 files changed, 143 insertions(+), 160 deletions(-) create mode 100644 scripts/update-submodule-common.sh diff --git a/docs/tools.md b/docs/tools.md index 8e393bd9..18988946 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -163,27 +163,31 @@ For details on the Doxygen configuration and CI pipeline, see the ### Bump the pinned Rust submodule -Update to the commit from the most recent published Rust SDK release: +Update to the commit from the Rust SDK release with the newest publication +time (including prereleases, excluding drafts): ```bash ./scripts/update-rust-sdk.sh ``` -The script requires the GitHub CLI (`gh`) when `--hash` is omitted. To select a -specific commit without `gh`, pass its full or abbreviated hash: +The script requires an authenticated GitHub CLI (`gh auth login`, or `GH_TOKEN`) +when `--hash` is omitted. To select a specific commit without `gh`, pass its full +or abbreviated hash: ```bash ./scripts/update-rust-sdk.sh --hash 1a477bc ``` -The script updates the `client-sdk-rust` gitlink and synchronizes its nested -submodules. It stops without changing the checkout if the Rust submodule has -tracked changes. +The script checks out the selected Rust commit and synchronizes its nested +submodules. It leaves the parent index untouched; review and stage the gitlink +when ready. `--hash` also permits unreleased commits for local development; CI +requires a published release for the pinned Rust commit. Review the selected commits before committing the gitlink update: ```bash -git submodule status --recursive +git diff --submodule=log -- client-sdk-rust +git add -- client-sdk-rust ``` ### Bump the pinned C++ example collection @@ -201,9 +205,16 @@ To select a specific commit, pass its full or abbreviated hash: ./scripts/update-cpp-example-collection.sh --hash 7e76bea ``` -The script updates the `cpp-example-collection` gitlink and synchronizes its -nested submodules. It stops without changing the checkout if the examples -submodule has tracked changes. +Review and stage the examples gitlink when ready: + +```bash +git diff --submodule=log -- cpp-example-collection +git add -- cpp-example-collection +``` + +Both updaters run from any working directory, initialize the selected submodule +if necessary, detach its checkout at the selected commit, and synchronize nested +submodules using checkout mode. ### If `yuv-sys` fails to build diff --git a/scripts/update-cpp-example-collection.sh b/scripts/update-cpp-example-collection.sh index a3c231df..57d502f5 100755 --- a/scripts/update-cpp-example-collection.sh +++ b/scripts/update-cpp-example-collection.sh @@ -18,7 +18,6 @@ set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" repo_root="$(cd "${script_dir}/.." && pwd -P)" -examples_dir="${repo_root}/cpp-example-collection" examples_repo="livekit-examples/cpp-example-collection" examples_remote="https://github.com/${examples_repo}.git" default_branch="main" @@ -78,71 +77,12 @@ if [[ "$hash_provided" == true && ! "$requested_hash" =~ ^[0-9a-fA-F]{7,40}$ ]]; exit 2 fi -if ! command -v git >/dev/null 2>&1; then - echo "ERROR: git not found in PATH" >&2 - exit 1 -fi - -if ! git -C "$repo_root" rev-parse --show-toplevel >/dev/null 2>&1; then - echo "ERROR: ${repo_root} is not a Git worktree" >&2 - exit 1 -fi - -if [[ ! -e "${examples_dir}/.git" ]]; then - echo "==> Initializing cpp-example-collection" - git -C "$repo_root" submodule update --init cpp-example-collection -fi - -if ! git -C "$examples_dir" diff --quiet || - ! git -C "$examples_dir" diff --cached --quiet; then - echo "ERROR: cpp-example-collection has tracked changes; commit or stash them first" >&2 - exit 1 -fi - -fetch_ref="$requested_hash" +# Shared checkout logic keeps the two dependency updaters consistent. +source "${script_dir}/update-submodule-common.sh" +check_update_prerequisites +fetch_ref="" if [[ "$hash_provided" == false ]]; then - echo "==> Finding the most recent cpp-example-collection commit" - branch_ref="refs/heads/${default_branch}" - remote_ref="$(git ls-remote "$examples_remote" "$branch_ref")" - read -r requested_hash resolved_ref <<<"$remote_ref" - if [[ -z "$requested_hash" || "$resolved_ref" != "$branch_ref" ]]; then - echo "ERROR: branch '${default_branch}' was not found in ${examples_repo}" >&2 - exit 1 - fi - fetch_ref="$branch_ref" - echo "==> Latest ${default_branch} commit: ${requested_hash}" -else - requested_hash="$(printf '%s' "$requested_hash" | tr '[:upper:]' '[:lower:]')" - echo "==> Requested examples commit: ${requested_hash}" + fetch_ref="refs/heads/${default_branch}" fi - -echo "==> Fetching cpp-example-collection" -if [[ "$hash_provided" == false ]]; then - git -C "$examples_dir" fetch --quiet origin "$fetch_ref" -elif [[ ${#requested_hash} -eq 40 ]]; then - if ! git -C "$examples_dir" fetch --quiet origin "$fetch_ref"; then - git -C "$examples_dir" fetch --quiet origin - fi -else - git -C "$examples_dir" fetch --quiet origin -fi - -if ! resolved_hash="$(git -C "$examples_dir" rev-parse --verify "${requested_hash}^{commit}" 2>/dev/null)"; then - echo "ERROR: '${requested_hash}' does not resolve to a cpp-example-collection commit" >&2 - exit 1 -fi - -current_hash="$(git -C "$examples_dir" rev-parse HEAD)" -if [[ "$current_hash" == "$resolved_hash" ]]; then - echo "==> cpp-example-collection is already at ${resolved_hash}" -else - git -C "$examples_dir" checkout --quiet --detach "$resolved_hash" - echo "==> Updated cpp-example-collection: ${current_hash} -> ${resolved_hash}" -fi - -echo "==> Synchronizing nested example submodules" -git -C "$examples_dir" submodule sync --recursive -git -C "$examples_dir" submodule update --init --recursive - -echo "==> Done. Review and commit the cpp-example-collection gitlink change." +update_submodule cpp-example-collection "$examples_remote" "$fetch_ref" diff --git a/scripts/update-rust-sdk.sh b/scripts/update-rust-sdk.sh index 3ae669fd..2acac10f 100755 --- a/scripts/update-rust-sdk.sh +++ b/scripts/update-rust-sdk.sh @@ -18,7 +18,6 @@ set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" repo_root="$(cd "${script_dir}/.." && pwd -P)" -rust_dir="${repo_root}/client-sdk-rust" rust_repo="livekit/client-sdk-rust" rust_remote="https://github.com/${rust_repo}.git" @@ -35,8 +34,9 @@ Options: -h, --help Show this help and exit. -When --hash is omitted, the script uses the commit tagged by the most recent -published, non-draft GitHub release in livekit/client-sdk-rust (requires gh). +When --hash is omitted, the script uses the commit tagged by the most recently +published, non-draft GitHub release (including prereleases) in +livekit/client-sdk-rust (requires gh). EOF } @@ -77,98 +77,31 @@ if [[ "$hash_provided" == true && ! "$requested_hash" =~ ^[0-9a-fA-F]{7,40}$ ]]; exit 2 fi -if ! command -v git >/dev/null 2>&1; then - echo "ERROR: git not found in PATH" >&2 - exit 1 -fi - -if ! git -C "$repo_root" rev-parse --show-toplevel >/dev/null 2>&1; then - echo "ERROR: ${repo_root} is not a Git worktree" >&2 - exit 1 -fi - -if [[ ! -e "${rust_dir}/.git" ]]; then - echo "==> Initializing client-sdk-rust" - git -C "$repo_root" submodule update --init client-sdk-rust -fi - -if ! git -C "$rust_dir" diff --quiet || - ! git -C "$rust_dir" diff --cached --quiet; then - echo "ERROR: client-sdk-rust has tracked changes; commit or stash them first" >&2 - exit 1 -fi - -fetch_ref="$requested_hash" -release_tag="" +# Shared checkout logic keeps the two dependency updaters consistent. +source "${script_dir}/update-submodule-common.sh" +check_update_prerequisites if [[ "$hash_provided" == false ]]; then if ! command -v gh >/dev/null 2>&1; then echo "ERROR: gh not found in PATH; install GitHub CLI or provide --hash" >&2 exit 1 fi - - echo "==> Finding the most recent published Rust SDK release" - release_tags="$( - gh api --paginate "repos/${rust_repo}/releases?per_page=100" \ - --jq '.[] | select(.draft == false) | .tag_name' - )" - release_tag="${release_tags%%$'\n'*}" + echo "==> Finding the most recently published Rust SDK release" + # Sort across every page by publication time, including prereleases. API + # list order and GitHub's designated "latest" release are different concepts. + release_tag="$(gh api --hostname github.com --paginate --slurp \ + "repos/${rust_repo}/releases?per_page=100" \ + --jq '[.[][] | select(.draft == false and .published_at != null)] | + sort_by(.published_at, .id) | last | .tag_name // empty')" if [[ -z "$release_tag" ]]; then echo "ERROR: no published release found for ${rust_repo}" >&2 exit 1 fi - - tag_refs="$( - git ls-remote "$rust_remote" \ - "refs/tags/${release_tag}" "refs/tags/${release_tag}^{}" - )" - while read -r tag_hash tag_ref; do - if [[ "$tag_ref" == "refs/tags/${release_tag}^{}" ]]; then - requested_hash="$tag_hash" - break - fi - if [[ "$tag_ref" == "refs/tags/${release_tag}" ]]; then - requested_hash="$tag_hash" - fi - done <<<"$tag_refs" - - if [[ -z "$requested_hash" ]]; then - echo "ERROR: release tag '${release_tag}' was not found in ${rust_repo}" >&2 - exit 1 - fi + git check-ref-format "refs/tags/${release_tag}" >/dev/null fetch_ref="refs/tags/${release_tag}" - echo "==> Latest release: ${release_tag} (${requested_hash})" -else - requested_hash="$(printf '%s' "$requested_hash" | tr '[:upper:]' '[:lower:]')" - echo "==> Requested Rust SDK commit: ${requested_hash}" -fi - -echo "==> Fetching client-sdk-rust" -if [[ "$hash_provided" == false ]]; then - git -C "$rust_dir" fetch --quiet origin "$fetch_ref" -elif [[ ${#requested_hash} -eq 40 ]]; then - if ! git -C "$rust_dir" fetch --quiet origin "$fetch_ref"; then - git -C "$rust_dir" fetch --quiet origin - fi + echo "==> Selected release: ${release_tag}" else - git -C "$rust_dir" fetch --quiet origin + fetch_ref="" fi -if ! resolved_hash="$(git -C "$rust_dir" rev-parse --verify "${requested_hash}^{commit}" 2>/dev/null)"; then - echo "ERROR: '${requested_hash}' does not resolve to a client-sdk-rust commit" >&2 - exit 1 -fi - -current_hash="$(git -C "$rust_dir" rev-parse HEAD)" -if [[ "$current_hash" == "$resolved_hash" ]]; then - echo "==> client-sdk-rust is already at ${resolved_hash}" -else - git -C "$rust_dir" checkout --quiet --detach "$resolved_hash" - echo "==> Updated client-sdk-rust: ${current_hash} -> ${resolved_hash}" -fi - -echo "==> Synchronizing nested Rust SDK submodules" -git -C "$rust_dir" submodule sync --recursive -git -C "$rust_dir" submodule update --init --recursive --checkout - -echo "==> Done. Review and commit the client-sdk-rust gitlink change." +update_submodule client-sdk-rust "$rust_remote" "$fetch_ref" diff --git a/scripts/update-submodule-common.sh b/scripts/update-submodule-common.sh new file mode 100644 index 00000000..bb672524 --- /dev/null +++ b/scripts/update-submodule-common.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# +# Copyright 2026 LiveKit +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Internal helpers sourced by update-rust-sdk.sh and +# update-cpp-example-collection.sh. Requires Bash 3.2 or newer. + +check_update_prerequisites() { + if ! command -v git >/dev/null 2>&1; then + echo "ERROR: git not found in PATH" >&2 + exit 1 + fi + local top_level + top_level="$(git -C "$repo_root" rev-parse --show-toplevel)" + if [[ "$(cd "$top_level" && pwd -P)" != "$repo_root" ]]; then + echo "ERROR: ${repo_root} is not the SDK Git worktree root" >&2 + exit 1 + fi +} + +require_clean_submodule() { + local directory="$1" + local changes + # Explicitly override submodule.*.ignore and diff.ignoreSubmodules. This + # includes nested HEAD changes, staged edits, and untracked files. + changes="$(git -C "$directory" status --porcelain --untracked-files=all \ + --ignore-submodules=none)" + if [[ -n "$changes" ]]; then + echo "ERROR: ${directory} has local changes (possibly in nested submodules)." >&2 + echo "Commit, stash, or move them before updating:" >&2 + printf '%s\n' "$changes" >&2 + exit 1 + fi +} + +update_submodule() { + local name="$1" remote="$2" fetch_ref="$3" + local directory="${repo_root}/${name}" + local resolved_hash current_hash + local fetch_options=(--no-tags) + + if [[ -e "${directory}/.git" ]]; then + require_clean_submodule "$directory" + else + echo "==> Initializing ${name}" + git -C "$repo_root" submodule update --init --checkout -- "$name" + require_clean_submodule "$directory" + fi + + echo "==> Fetching ${name} from ${remote}" + # Use the same authoritative repository for resolution and fetching, even + # when a developer's origin points to a fork. Leave their remotes unchanged. + if [[ "$hash_provided" == false ]]; then + git -C "$directory" fetch --quiet --no-tags "$remote" "$fetch_ref" + resolved_hash="$(git -C "$directory" rev-parse --verify 'FETCH_HEAD^{commit}')" + else + requested_hash="$(printf '%s' "$requested_hash" | tr '[:upper:]' '[:lower:]')" + if [[ ${#requested_hash} -lt 40 ]] || + ! git -C "$directory" fetch --quiet --no-tags "$remote" "$requested_hash"; then + # Abbreviated hashes cannot be fetched directly. Fetch all branch/tag + # objects without updating local branches or overwriting local tags. + if [[ "$(git -C "$directory" rev-parse --is-shallow-repository)" == true ]]; then + fetch_options+=(--unshallow) + fi + git -C "$directory" fetch --quiet "${fetch_options[@]}" "$remote" \ + '+refs/heads/*:refs/sdk-update/heads/*' '+refs/tags/*:refs/sdk-update/tags/*' + fi + if ! resolved_hash="$(git -C "$directory" rev-parse --verify "${requested_hash}^{commit}" 2>/dev/null)" || + [[ "$resolved_hash" != "$requested_hash"* ]]; then + echo "ERROR: '${requested_hash}' does not unambiguously identify a ${name} commit" >&2 + exit 1 + fi + fi + + current_hash="$(git -C "$directory" rev-parse HEAD)" + echo "==> Selected ${name}: ${current_hash} -> ${resolved_hash}" + # Failures here can leave a partial update. Do not attempt a destructive + # rollback; show the developer exactly what needs inspection and retrying. + trap 'echo "ERROR: Update incomplete. Inspect git status and git submodule status --recursive; fix the error and rerun the updater. No changes were staged or committed." >&2' ERR + git -C "$directory" checkout --quiet --no-overwrite-ignore --detach "$resolved_hash" + git -C "$directory" submodule sync --recursive + git -C "$directory" submodule update --init --recursive --checkout + trap - ERR + + echo "==> Done. Review with: git diff --submodule=log -- ${name}" + echo "==> Stage when ready: git add -- ${name}" +}