#!/usr/bin/env bash # lib/adopt.sh — Non-destructive adoption merge primitive # # Provides: # adopt_branch() — Create one adoption merge commit and push it fast-forward # # The orchestration (state machine, import → wait → finalize) lives in # lib/onboard.sh under strategy="adopt". `josh-sync adopt` is now a CLI alias # for `josh-sync onboard --mode=adopt`; see ADR-013 for design rationale. # # Requires: lib/core.sh, lib/config.sh, lib/auth.sh sourced # ─── Adopt Branch ──────────────────────────────────────────────── # Establishes shared ancestry without rewriting the existing subrepo branch. # # Preconditions: # - The subrepo branch and josh-filtered monorepo branch must have identical trees. # # Resulting commit: # - parent 1: josh-filtered HEAD, so josh can follow first-parent history # - parent 2: existing subrepo HEAD, so old subrepo history stays reachable # - tree: josh-filtered tree # # Returns: adopted | already-adopted | tree-mismatch | push-rejected | missing-branch adopt_branch() ( # Subshell function: contains the function-level EXIT trap, the internal cd, # and any set -e propagation so it cannot pollute the caller's shell. Without # this, the trap below would clobber callers' EXIT handlers (bats's own test # reporting in particular silently disappears). local mono_branch="$SYNC_BRANCH_MONO" local subrepo_branch="$SYNC_BRANCH_SUBREPO" local work_dir work_dir=$(mktemp -d) # shellcheck disable=SC2064 # Intentional early expansion — work_dir is local trap "rm -rf '$work_dir'" EXIT log "INFO" "=== Adopt: subrepo/${subrepo_branch} ↔ mono/${mono_branch} ===" local remote_subrepo_sha remote_subrepo_sha=$(git ls-remote "$(subrepo_auth_url)" "refs/heads/${subrepo_branch}" | awk '{print $1}') \ || die "Failed to reach subrepo (check SSH key / auth)" if [ -z "$remote_subrepo_sha" ]; then log "ERROR" "Subrepo branch ${subrepo_branch} does not exist" echo "missing-branch" return fi git clone "$(subrepo_auth_url)" \ --branch "$subrepo_branch" --single-branch \ "${work_dir}/subrepo" || die "Failed to clone subrepo" cd "${work_dir}/subrepo" || exit git config user.name "$BOT_NAME" git config user.email "$BOT_EMAIL" local subrepo_head subrepo_tree subrepo_head=$(git rev-parse HEAD) # shellcheck disable=SC1083 # {tree} is git syntax, not shell brace expansion subrepo_tree=$(git rev-parse "HEAD^{tree}") log "INFO" "Subrepo HEAD: ${subrepo_head:0:12}" git remote add josh-filtered "$(josh_auth_url)" git fetch josh-filtered "$mono_branch" || die "Failed to fetch from josh-proxy" local josh_head josh_tree josh_head=$(git rev-parse "josh-filtered/${mono_branch}") # shellcheck disable=SC1083 josh_tree=$(git rev-parse "josh-filtered/${mono_branch}^{tree}") log "INFO" "Josh-filtered HEAD: ${josh_head:0:12}" if [ "$subrepo_tree" != "$josh_tree" ]; then log "ERROR" "Tree mismatch: merge the import PR and ensure subrepo/${subrepo_branch} matches mono/${mono_branch} before adopting" echo "tree-mismatch" return fi if git merge-base --is-ancestor "$josh_head" "$subrepo_head" \ && git merge-base --is-ancestor "$subrepo_head" "$josh_head"; then log "INFO" "Subrepo and josh-filtered histories already point to the same commit" echo "already-adopted" return fi if git merge-base --is-ancestor "$josh_head" "$subrepo_head"; then log "INFO" "Josh-filtered HEAD is already an ancestor of subrepo HEAD" echo "already-adopted" return fi local merge_commit merge_commit=$(git commit-tree "$josh_tree" \ -p "$josh_head" \ -p "$subrepo_head" \ -m "Adopt ${JOSH_SYNC_TARGET_NAME} for josh-sync ${BOT_TRAILER}: adopt/${mono_branch}/$(date -u +%Y-%m-%dT%H:%M:%SZ)") git reset --hard "$merge_commit" >&2 log "INFO" "Created adoption merge: ${merge_commit:0:12}" if git push "$(subrepo_auth_url)" "HEAD:refs/heads/${subrepo_branch}"; then log "INFO" "Adoption complete: pushed fast-forward merge to subrepo/${subrepo_branch}" echo "adopted" else log "WARN" "Fast-forward push rejected — subrepo changed during adoption" echo "push-rejected" fi )