diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9ca09f71bf..52cdd1f87d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -68,6 +68,7 @@ rustfmt.toml @DataDog/libdatadog-core scripts/check_cargo_metadata.sh @DataDog/libdatadog-core scripts/commits-since-release.sh @DataDog/libdatadog-core scripts/crates-to-package.sh @DataDog/libdatadog-core +scripts/major-bumps-level.sh @DataDog/libdatadog-core scripts/publication-order.sh @DataDog/libdatadog-core scripts/reformat_copyright.sh @DataDog/libdatadog-core scripts/semver-level.sh @DataDog/libdatadog-core diff --git a/.github/workflows/release-proposal-dispatch.yml b/.github/workflows/release-proposal-dispatch.yml index 83afc6e3ee..487fe409bd 100644 --- a/.github/workflows/release-proposal-dispatch.yml +++ b/.github/workflows/release-proposal-dispatch.yml @@ -7,16 +7,22 @@ on: required: true type: choice options: + - libdd-alloc + - libdd-capabilities + - libdd-capabilities-impl - libdd-common - libdd-crashtracker - libdd-data-pipeline - libdd-ddsketch - libdd-dogstatsd-client + - libdd-http-client - libdd-library-config - libdd-libunwind-sys - libdd-log + - libdd-otel-thread-ctx - libdd-profiling - libdd-profiling-protobuf + - libdd-shared-runtime - libdd-telemetry - libdd-tinybytes - libdd-trace-normalization @@ -388,55 +394,151 @@ jobs: echo "No previous release tag for $NAME, preparing initial release..." # Use the version from the crate metadata - LEVEL=$(echo "$crate" | jq -r '.version') - + VERSION=$(echo "$crate" | jq -r '.version') + LEVEL="major" + TAG="" + RANGE="" + # fail when the version is not an initial release - if [ "$LEVEL" != "1.0.0" ] && [ "$LEVEL" != "0.1.0" ]; then - echo "Error: $NAME is not a 1.0.0 or 0.1.0 release" >&2 + if [ "$VERSION" != "0.1.0" ]; then + echo "Error: $NAME is not a 0.1.0 release" >&2 exit 1 fi INITIAL_RELEASE=true echo "Executing cargo release for $NAME with level $LEVEL..." - cargo release version -p "$NAME" -x $LEVEL --no-confirm + cargo release version -p "$NAME" --allow-branch "$BRANCH_NAME" -x $LEVEL --no-confirm + fi + + # Commit the changes + cargo release commit --no-confirm -x + + NEXT_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .version') + NEXT_TAG="$TAG_PREFIX$NEXT_VERSION" + + # Add to results array + jq --arg name "$NAME" \ + --arg level "$LEVEL" \ + --arg tag "$NEXT_TAG" \ + --arg prev_tag "$TAG" \ + --arg version "$NEXT_VERSION" \ + --arg range "$RANGE" \ + --argjson commits "$COMMITS" \ + --arg path "$CRATE_PATH" \ + --arg initial_release "$INITIAL_RELEASE" \ + '. += [{"name": $name, "level": $level, "tag": $tag, "prev_tag": $prev_tag, "version": $version, "range": $range, "commits": $commits, "path": $path, "initial_release": $initial_release}]' \ + /tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json + done + + # Check if there are commits to push + if git diff --quiet "${{ steps.ephemeral-branch.outputs.ephemeral_branch }}"; then + echo "No changes to push. Cancelling the workflow." + exit 1 + fi + + # Output the results + echo "API changes summary:" + jq . /tmp/api-changes.json + + - name: Update version for crates with libdd-* direct dependency major bumps since last release + run: | + set -euo pipefail + BRANCH_NAME="${{ steps.proposal-branch.outputs.branch_name }}" + + # Run the audit in a throwaway worktree so extra worktrees / cargo metadata do not touch the job checkout. + MAJOR_BUMPS_WT=$(mktemp -d "${RUNNER_TEMP:-/tmp}/major-bumps-wt.XXXXXX") + WF_SHA="${{ github.sha }}" + + git worktree add --detach "$MAJOR_BUMPS_WT" "$WF_SHA" + set +e + ( cd "$MAJOR_BUMPS_WT" && ./scripts/major-bumps-level.sh /tmp/api-changes.json ) \ + > /tmp/api-changes-with-major-bumps-pre-commit.json + MB_RC=$? + git worktree remove --force "$MAJOR_BUMPS_WT" || true + set -e + if [[ "$MB_RC" -ne 0 ]]; then + echo "Major bumps level script failed with code $MB_RC" + echo "Major bumps level script output:" + cat /tmp/api-changes-with-major-bumps-pre-commit.json + exit "$MB_RC" + fi + + # Full same crate list as api-changes.json; rows updated in place when a major bump is applied. + cp /tmp/api-changes-with-major-bumps-pre-commit.json /tmp/api-changes-with-major-bumps.json + + # iterate over the major bumps and check the major bumps and update the version + jq -c '.[]' /tmp/api-changes-with-major-bumps-pre-commit.json | while read -r bump; do + NAME=$(echo "$bump" | jq -r '.name') + PREV_TAG=$(echo "$bump" | jq -r '.prev_tag') + TAG=$(echo "$bump" | jq -r '.tag') + VERSION=$(echo "$bump" | jq -r '.version') + MAJOR_BUMPS=$(echo "$bump" | jq -r '.major_bumps') + + if [ "$MAJOR_BUMPS" != "[]" ]; then + echo "Updating version for $NAME with major bumps: $MAJOR_BUMPS" + cargo release version -p "$NAME" --prev-tag-name "$PREV_TAG" --allow-branch "$BRANCH_NAME" -x major --no-confirm + + git commit -am "chore(release): update version for $NAME with major bumps" + + NEXT_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .version') + NEXT_TAG="$NAME-v$NEXT_VERSION" + + echo "Updating tag $TAG to $NEXT_TAG and version $VERSION to $NEXT_VERSION for $NAME" + + jq --arg name "$NAME" \ + --arg nl "major" \ + --arg version "$NEXT_VERSION" \ + --arg tag "$NEXT_TAG" \ + 'map(if .name == $name then . + {level: $nl, version: $version, tag: $tag} else . end)' \ + /tmp/api-changes-with-major-bumps.json > /tmp/api-changes-with-major-bumps.tmp \ + && mv /tmp/api-changes-with-major-bumps.tmp /tmp/api-changes-with-major-bumps.json + fi + done + + # Output the results + echo "API changes with major bumps summary:" + jq . /tmp/api-changes-with-major-bumps.json + + - name: Generate CHANGELOGS + id: generate-changelogs + run: | + set -euo pipefail + ORIGINAL_HEAD=$(cat /tmp/release_head_sha) + + echo "Generating CHANGELOGS" + + jq -c '.[]' /tmp/api-changes-with-major-bumps.json | while read -r bump; do + COMMITS=$(echo "$bump" | jq -r '.commits') + RANGE=$(echo "$bump" | jq -r '.range') + NAME=$(echo "$bump" | jq -r '.name') + TAG=$(echo "$bump" | jq -r '.prev_tag') + NEXT_TAG=$(echo "$bump" | jq -r '.tag') + CRATE_PATH=$(echo "$bump" | jq -r '.path') + INITIAL_RELEASE=$(echo "$bump" | jq -r '.initial_release') + + if [ "$INITIAL_RELEASE" = "true" ]; then + echo "Initial release for $NAME" # Use the existing CHANGELOG.md if present, otherwise create a minimal one if [ ! -f "$CRATE_PATH/CHANGELOG.md" ]; then echo "Creating CHANGELOG.md for $NAME..." RELEASE_DATE=$(date +%Y-%m-%d) printf '# Changelog\n\n\n## %s - %s\n\nInitial release.\n' "$LEVEL" "$RELEASE_DATE" > "$CRATE_PATH/CHANGELOG.md" + + git add "$CRATE_PATH/CHANGELOG.md" + git commit -m "chore(release): update CHANGELOG.md for $NAME" else echo "Using existing CHANGELOG.md for $NAME..." fi + continue + fi - git add "$CRATE_PATH/CHANGELOG.md" - if git diff --cached --quiet; then - # Nothing changed (version already correct, CHANGELOG already present). - # An empty commit is required so the proposal branch is ahead of the - # ephemeral branch — GitHub rejects PRs between branches at the same commit. - echo "Nothing to commit for $NAME (version and CHANGELOG already up to date), creating empty release marker commit..." - git commit --allow-empty -m "chore: Release $NAME $LEVEL" - else - cargo release commit --no-confirm -x - fi - - # Add to results array - jq --arg name "$NAME" \ - --arg level "$LEVEL" \ - --arg tag "$TAG_PREFIX$LEVEL" \ - --arg version "$LEVEL" \ - --arg initial_release "$INITIAL_RELEASE" \ - '. += [{"name": $name, "level": $level, "tag": $tag, "version": $version, "initial_release": $initial_release}]' \ - /tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json - - # Skip the git-cliff changelog update below — CHANGELOG is already handled above + # FIXME: $COMMITS could be empty if there are no commits since last release + if [ "$COMMITS" = "[]" ]; then + echo "No commits since last release for $NAME, skipping CHANGELOG generation" continue fi - - # Update the CHANGELOG.md - NEXT_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .version') - NEXT_TAG="$TAG_PREFIX$NEXT_VERSION" # Build a tight range from commits already found by commits-since-release.sh. # This will save some time analising unnecessary commits and prevent unrelated commits @@ -471,44 +573,29 @@ jobs: "$CLIFF_CONTEXT_FILE" > "$CLIFF_FILTERED_FILE" git cliff --from-context "$CLIFF_FILTERED_FILE" -u -v --prepend "$CRATE_PATH/CHANGELOG.md" rm -f "$CLIFF_CONTEXT_FILE" "$CLIFF_HASHES_FILE" "$CLIFF_FILTERED_FILE" - git add "$CRATE_PATH/CHANGELOG.md" - - # Commit the changes - cargo release commit --no-confirm -x - # Add to results array - jq --arg name "$NAME" \ - --arg level "$LEVEL" \ - --arg tag "$NEXT_TAG" \ - --arg version "$NEXT_VERSION" \ - --arg initial_release "$INITIAL_RELEASE" \ - '. += [{"name": $name, "level": $level, "tag": $tag, "version": $version, "initial_release": $initial_release}]' \ - /tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json + git add "$CRATE_PATH/CHANGELOG.md" + git commit -m "chore(release): update CHANGELOG.md for $NAME" done - # Check if any crates are being released - if [ "$(jq 'length' /tmp/api-changes.json)" -eq 0 ]; then - echo "No crates to release. Cancelling the workflow." + # Check if there are commits to push + if git diff --quiet "${{ steps.ephemeral-branch.outputs.ephemeral_branch }}"; then + echo "No changes to push. Cancelling the workflow." exit 1 fi # Oldest → newest (chronological). Plain `git log` is newest-first; commit-headless should receive - # parent→child order so replays/signing match git history. Space-separated SHAs for the action. + # parent → child order so replays/signing match git history. Space-separated SHAs for the action. COMMITS=$(git log --reverse "$ORIGINAL_HEAD".. --format='%H' | tr '\n' ' ' | xargs) echo "commits=$COMMITS" >> $GITHUB_OUTPUT - # Output the results - echo "API changes summary:" - jq . /tmp/api-changes.json - - name: Push commits - if: steps.release-version-bumps.outputs.commits != '' uses: DataDog/commit-headless@action/v2.0.3 with: branch: ${{ steps.proposal-branch.outputs.branch_name }} head-sha: ${{ steps.commits-since-release.outputs.release_head_sha }} command: push - commits: "${{ steps.release-version-bumps.outputs.commits }}" + commits: "${{ steps.generate-changelogs.outputs.commits }}" - name: Upload release data uses: actions/upload-artifact@v4 @@ -517,6 +604,7 @@ jobs: path: | /tmp/commits-by-crate.json /tmp/api-changes.json + /tmp/api-changes-with-major-bumps.json retention-days: 1 - name: Cleanup on failure @@ -536,7 +624,7 @@ jobs: outputs: branch_name: ${{ steps.proposal-branch.outputs.branch_name }} ephemeral_branch: ${{ steps.ephemeral-branch.outputs.ephemeral_branch }} - + create-pr: needs: cargo-release runs-on: ubuntu-latest @@ -574,37 +662,42 @@ jobs: NON_DEFAULT="" if [ -n "$MAIN_START_REF" ]; then - NON_DEFAULT="${NON_DEFAULT}"$'\n### Cut from non-default ref\n\n'"This proposal was generated from \`$MAIN_START_REF\` instead of the default latest \`origin/$MAIN_BRANCH\`."$'\n' + NON_DEFAULT="${NON_DEFAULT}"$'\n### :exclamation: Cut from non-default ref\n\n'"This proposal was generated from \`$MAIN_START_REF\` instead of the default latest \`origin/$MAIN_BRANCH\`."$'\n' fi if [ "$BYPASS_STANDARD_CHECKS" = "true" ]; then - NON_DEFAULT="${NON_DEFAULT}"$'\n### Non-default workflow options\n\n'"**bypass_standard_checks** was enabled: the ongoing-proposal branch guard was skipped; branches use proposal prefix \`$PROPOSAL_BRANCH_PREFIX\` and release prefix \`$RELEASE_BRANCH_PREFIX\`. Crates whose resolved git tag is not the latest SemVer tag for that crate are still included (normally skipped)."$'\n' + NON_DEFAULT="${NON_DEFAULT}"$'\n### :test_tube: Non-default workflow options\n\n'"**bypass_standard_checks** was enabled: the ongoing-proposal branch guard was skipped; branches use proposal prefix \`$PROPOSAL_BRANCH_PREFIX\` and release prefix \`$RELEASE_BRANCH_PREFIX\`. Crates whose resolved git tag is not the latest SemVer tag for that crate are still included (normally skipped)."$'\n' fi if [ -n "$NON_DEFAULT" ]; then NON_DEFAULT="${NON_DEFAULT}"$'\n\n' fi - # Generate the PR body by merging commits and API changes + # PR body from api-changes-with-major-bumps.json (same crates as api-changes.json; tags/versions updated after libdd major bumps). # Note: read returns 1 when it reaches EOF, which is expected for heredocs read -r -d '' JQ_FILTER << 'EOF' || true - .[] | select((.commits | length) > 0) | - . as $crate | - ($api[0] | map(select(.name == $crate.name)) | first // { - level: "skipped because there were no changes to the public API" - }) as $api_info | - [ - "## \($crate.name)", - "", - (if $api_info.version then "**Next version:** `\($api_info.version)`" else null end), - "**Semver bump:** `\($api_info.level)`", - (if $api_info.tag then "**Tag:** `\($api_info.tag)`\n" else null end), - (if $api_info.initial_release == "true" then - "**Warning:** this is an initial release. Please verify that the version and commits included are correct.\n" - else null end), - (if ($crate.commits | length) > 0 then "### Commits\n\n" + ($crate.commits | map("- \(.subject)") | join("\n")) else null end) - ] | map(select(. != null and . != "")) | join("\n") + [ $api[0][] + | [ + "## \(.name)", + "", + (if .version then "**Next version:** `\(.version)`" else null end), + "**Semver bump:** `\(.level)`", + (if .tag then "**Tag:** `\(.tag)`\n" else null end), + (if (.major_bumps // [] | length) > 0 then + "### :warning: major bump forced due to:\n\n" + + ((.major_bumps // []) | map("- `\(.dependency)`: \(.previous_req) → \(.current_req)") | join("\n")) + + "\n" + else null end), + (if .initial_release == "true" then + "**Warning:** this is an initial release. Please verify that the version and commits included are correct.\n" + else null end), + (if (.commits | length) > 0 then "### Commits\n\n" + (.commits | map("- \(.subject)") | join("\n")) else null end) + ] + | map(select(. != null and . != "")) + | join("\n") + ] + | join("\n\n") EOF - COMMITS_AND_API_BODY=$(jq -r --slurpfile api /tmp/api-changes.json "$JQ_FILTER" /tmp/commits-by-crate.json) + COMMITS_AND_API_BODY=$(jq -nr --slurpfile api /tmp/api-changes-with-major-bumps.json "$JQ_FILTER") PR_BODY="# Release proposal for ${{ inputs.crate }} and its dependencies @@ -623,6 +716,7 @@ jobs: --label "release-proposal" \ --label "skip-metadata-check" \ --label "skip-changelog-check" \ + --label "skip-pr-title-semver-check" \ --base "${{ needs.cargo-release.outputs.ephemeral_branch }}" \ --draft diff --git a/.github/workflows/release-proposal-test.yml b/.github/workflows/release-proposal-test.yml index 5666e490fe..7f536a4d85 100644 --- a/.github/workflows/release-proposal-test.yml +++ b/.github/workflows/release-proposal-test.yml @@ -19,21 +19,40 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 with: fetch-depth: 0 - # Use PR head so git diff origin/main...HEAD matches commits on the proposal branch (not the merge commit). + # Checkout PR head (not the synthetic merge commit) so version comparison matches the proposal branch. ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.92.0 - - name: Fetch main for three-dot diff - run: git fetch origin main + # origin/main is needed for merge-base on push; PR base SHA must be fetched for git show. + - name: Fetch refs for version comparison + run: | + git fetch origin main + if [ "${{ github.event_name }}" = "pull_request" ]; then + git fetch --no-tags origin "${{ github.event.pull_request.base.sha }}" + fi - # Every workspace crate: compare effective [package] version at origin/main vs HEAD (see script). + # Compare [package] versions between PR base and head (or merge-base(main,HEAD) vs push on direct push). # Dependency-only manifest edits are ignored; excludes match the old cargo package --exclude list. - - name: Package crates changed vs main + - name: Package crates with version bumps vs comparison base id: package-crates run: | set -euo pipefail + if [ "${{ github.event_name }}" = "pull_request" ]; then + GIT_VERSION_BASE="${{ github.event.pull_request.base.sha }}" + export GIT_VERSION_BASE + GIT_VERSION_HEAD="${{ github.event.pull_request.head.sha }}" + export GIT_VERSION_HEAD + else + # Direct push: compare from fork point with main, not origin/main's current tip (main may have advanced). + GIT_VERSION_BASE="$(git merge-base origin/main HEAD)" + export GIT_VERSION_BASE + GIT_VERSION_HEAD="${GITHUB_SHA}" + export GIT_VERSION_HEAD + fi + + echo "Comparing Cargo.toml package versions: GIT_VERSION_BASE=${GIT_VERSION_BASE} GIT_VERSION_HEAD=${GIT_VERSION_HEAD}" mapfile -t PACKAGES < <(./scripts/crates-to-package.sh) echo "Packages to run cargo package on (version bump only):" printf '%s\n' "${PACKAGES[@]}" diff --git a/scripts/crates-to-package.sh b/scripts/crates-to-package.sh index 92e5a51f74..e7f4706738 100755 --- a/scripts/crates-to-package.sh +++ b/scripts/crates-to-package.sh @@ -3,9 +3,13 @@ # SPDX-License-Identifier: Apache-2.0 # # Compares every workspace package's effective [package] version between GIT_VERSION_BASE -# and GIT_VERSION_HEAD (default origin/main vs HEAD). Prints package names suitable for -# `cargo package -p` when the version changed — not when only dependency versions changed. +# and GIT_VERSION_HEAD. Prints package names suitable for `cargo package -p` when the version +# changed — not when only dependency versions changed. # Uses git for both sides so results match the refs, not uncommitted working tree edits. +# +# CI should set GIT_VERSION_BASE / GIT_VERSION_HEAD to the commits being compared (e.g. PR base +# and head). Defaults below are for local use; comparing to origin/main vs HEAD is not the same +# as "what this PR changed" once main has moved. set -euo pipefail @@ -59,7 +63,6 @@ excluded_crate() { local n="$1" case "$n" in libdd-*-ffi) return 0 ;; - libdd-crashtracker) return 0 ;; datadog-*) return 0 ;; bin_tests | tools | sidecar_mockgen | cc_utils | spawn_worker | symbolizer-ffi | test_spawn_from_lib | build_common | build-common | builder) return 0 @@ -101,7 +104,7 @@ while IFS=$'\t' read -r name mpath; do done < <(echo "$METADATA" | jq -r '.packages[] | "\(.name)\t\(.manifest_path)"') if [ "${#emit[@]}" -eq 0 ]; then - echo "release-proposal-crates-to-package.sh: no packages with a changed [package] version (nothing to emit)." >&2 + echo "crates-to-package.sh: no packages with a changed [package] version between refs (nothing to emit)." >&2 exit 0 fi diff --git a/scripts/major-bumps-level.sh b/scripts/major-bumps-level.sh new file mode 100755 index 0000000000..6bc333a0f4 --- /dev/null +++ b/scripts/major-bumps-level.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +# Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +# SPDX-License-Identifier: Apache-2.0 + +# For each crate in an api-changes.json array, compare direct libdd-* dependency +# version requirements between the current tree and prev_tag. If any dependency's +# major version (first digit in the req string) increased, record details in `major_bumps`. +# +# Usage: major-bumps-level.sh API_CHANGES_JSON +# Writes enriched JSON (same shape + major_bumps) to stdout. + +set -euo pipefail + +usage() { + echo "Usage: $0 API_CHANGES_JSON" >&2 + exit 2 +} + +[[ ${1:-} ]] || usage +API_JSON=$1 +[[ -f "$API_JSON" ]] || { echo "Not a file: $API_JSON" >&2; exit 2; } + +level_rank() { + case "${1:-}" in + patch) echo 0 ;; + minor) echo 1 ;; + major) echo 2 ;; + *) echo -1 ;; + esac +} + +libdd_deps_for_crate() { + local manifest crate + manifest=$1 + crate=$2 + # Cargo metadata `.req` is the version *requirement* from the manifest, not the resolved version. + # Path / workspace deps often show req "*". Exclude dev-dependencies and build-dependencies + # (`kind` "dev" / "build"); only normal [dependencies] use kind null. When duplicates remain + # (e.g. target-specific), prefer non-"*" req so "*" from path-only edges does not win. + jq --arg crate "$crate" ' + .packages[] + | select(.name == $crate) + | [.dependencies[] + | select(.name | startswith("libdd-")) + | select(.kind != "dev" and .kind != "build") + | {key: .name, value: .req}] + | group_by(.key) + | map(sort_by(if .value == "*" then 1 else 0 end) | .[0]) + | from_entries + ' <(cargo metadata --manifest-path "$manifest" --format-version=1 --no-deps) +} + +compare_libdd_bumps() { + local path_prev path_curr + path_prev=$1 + path_curr=$2 + jq -n --slurpfile prev "$path_prev" --slurpfile curr "$path_curr" ' + ($prev[0]) as $p + | ($curr[0]) as $c + | def first_num(s): + try ( + (s | tostring | match("[0-9]+") | .string | tonumber) + ) catch null + ; + [ + ($c | keys_unsorted[]) as $k + | select($p[$k] != null) + | first_num($p[$k]) as $pm + | first_num($c[$k]) as $cm + | select($pm != null and $cm != null and $cm > $pm) + | { + dependency: $k, + previous_req: $p[$k], + current_req: $c[$k] + } + ] + ' +} + +WORKTREES=() +cleanup() { + local d + for d in "${WORKTREES[@]:-}"; do + git worktree remove --force "$d" >/dev/null 2>&1 || true + done +} +trap cleanup EXIT + +n=$(jq length "$API_JSON") +OUT=$(mktemp) +echo '[]' >"$OUT" +FAIL=0 + +for ((i = 0; i < n; i++)); do + row=$(jq -c ".[$i]" "$API_JSON") + name=$(echo "$row" | jq -r .name) + prev_tag=$(echo "$row" | jq -r .prev_tag) + initial=$(echo "$row" | jq -r .initial_release) + lev=$(echo "$row" | jq -r .level) + + bumps_json='[]' + new_lev=$lev + + if [[ "$initial" == "true" ]] || [[ -z "$prev_tag" ]] || [[ "$prev_tag" == "null" ]]; then + : + else + cur_mf="${name}/Cargo.toml" + if [[ ! -f "$cur_mf" ]]; then + echo "ERROR: missing manifest $cur_mf" >&2 + exit 2 + fi + wt=$(mktemp -d) + WORKTREES+=("$wt") + git worktree add --detach "$wt" "$prev_tag" >/dev/null + prev_mf="${wt}/${name}/Cargo.toml" + if [[ ! -f "$prev_mf" ]]; then + echo "ERROR: missing manifest $prev_mf at $prev_tag" >&2 + exit 2 + fi + prev_json=$(mktemp) + curr_json=$(mktemp) + libdd_deps_for_crate "$prev_mf" "$name" >"$prev_json" + libdd_deps_for_crate "$cur_mf" "$name" >"$curr_json" + bumps_json=$(compare_libdd_bumps "$prev_json" "$curr_json") + rm -f "$prev_json" "$curr_json" + if [[ $(echo "$bumps_json" | jq 'length') -gt 0 ]]; then + echo "libdd-* direct dependency major bump for crate ${name} (prev_tag=${prev_tag}):" >&2 + echo "$bumps_json" | jq -r '.[] | " - \(.dependency): \(.previous_req) -> \(.current_req)"' >&2 + fi + fi + + enriched=$(echo "$row" | jq -c --argjson bumps "$bumps_json" --arg nl "$new_lev" \ + '. + {level: $nl, major_bumps: $bumps}') + jq --argjson enriched "$enriched" '. + [$enriched]' "$OUT" >"${OUT}.new" + mv "${OUT}.new" "$OUT" +done + +jq . "$OUT" +rm -f "$OUT"