From 9fe2a842e9962a96aecb3218cd4a8c1ad70fd978 Mon Sep 17 00:00:00 2001 From: Javanaut Date: Thu, 16 Apr 2026 19:32:41 +0200 Subject: [PATCH] ff --- tools/merge_dev_into_main.sh | 74 +++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/tools/merge_dev_into_main.sh b/tools/merge_dev_into_main.sh index 0340a11..3cf4ff6 100755 --- a/tools/merge_dev_into_main.sh +++ b/tools/merge_dev_into_main.sh @@ -172,21 +172,67 @@ fetch_remote_state() { git fetch "${ORIGIN_REMOTE}" "${DEV_BRANCH}" "${MAIN_BRANCH}" --tags >/dev/null } -require_branch_matches_remote() { +branch_divergence_counts() { local branch="$1" - local local_sha="" - local remote_sha="" + local remote_only="" + local local_only="" if ! git show-ref --verify --quiet "refs/remotes/${ORIGIN_REMOTE}/${branch}"; then fail "Remote branch '${ORIGIN_REMOTE}/${branch}' does not exist." fi - local_sha="$(git rev-parse "refs/heads/${branch}")" - remote_sha="$(git rev-parse "refs/remotes/${ORIGIN_REMOTE}/${branch}")" + read -r remote_only local_only < <( + git rev-list --left-right --count \ + "refs/remotes/${ORIGIN_REMOTE}/${branch}...refs/heads/${branch}" + ) - if [ "${local_sha}" != "${remote_sha}" ]; then - fail "Local branch '${branch}' is not up to date with '${ORIGIN_REMOTE}/${branch}'. Pull, rebase, or push first." + printf '%s %s\n' "${remote_only}" "${local_only}" +} + +require_branch_contains_remote() { + local branch="$1" + local remote_only="" + local local_only="" + + read -r remote_only local_only < <(branch_divergence_counts "${branch}") + + if [ "${remote_only}" -ne 0 ] && [ "${local_only}" -ne 0 ]; then + fail "Local branch '${branch}' has diverged from '${ORIGIN_REMOTE}/${branch}' (${local_only} local-only commit(s), ${remote_only} remote-only commit(s)). Reconcile the branches first." fi + + if [ "${remote_only}" -ne 0 ]; then + fail "Local branch '${branch}' is behind '${ORIGIN_REMOTE}/${branch}' by ${remote_only} commit(s). Pull or rebase first." + fi + + if [ "${local_only}" -ne 0 ]; then + printf "Notice: local branch '%s' is ahead of '%s/%s' by %s commit(s); release will use the local tip.\n" \ + "${branch}" \ + "${ORIGIN_REMOTE}" \ + "${branch}" \ + "${local_only}" + fi +} + +require_branch_matches_remote_exactly() { + local branch="$1" + local remote_only="" + local local_only="" + + read -r remote_only local_only < <(branch_divergence_counts "${branch}") + + if [ "${remote_only}" -eq 0 ] && [ "${local_only}" -eq 0 ]; then + return 0 + fi + + if [ "${remote_only}" -ne 0 ] && [ "${local_only}" -ne 0 ]; then + fail "Local branch '${branch}' has diverged from '${ORIGIN_REMOTE}/${branch}' (${local_only} local-only commit(s), ${remote_only} remote-only commit(s)). Reconcile the branches first." + fi + + if [ "${remote_only}" -ne 0 ]; then + fail "Local branch '${branch}' is behind '${ORIGIN_REMOTE}/${branch}' by ${remote_only} commit(s). Pull or rebase first." + fi + + fail "Local branch '${branch}' is ahead of '${ORIGIN_REMOTE}/${branch}' by ${local_only} commit(s). Push first so the release starts from the published ${branch} tip." } resolve_release_version() { @@ -249,13 +295,13 @@ print_release_plan() { printf 'Dry run only. Planned steps:\n' printf '1. Ensure current branch is %s and the worktree is clean.\n' "${DEV_BRANCH}" - printf '2. Fetch %s and verify local %s and %s exactly match %s/%s and %s/%s.\n' \ + printf '2. Fetch %s and verify local %s contains %s/%s while local %s exactly matches %s/%s.\n' \ + "${ORIGIN_REMOTE}" \ + "${DEV_BRANCH}" \ "${ORIGIN_REMOTE}" \ "${DEV_BRANCH}" \ "${MAIN_BRANCH}" \ "${ORIGIN_REMOTE}" \ - "${DEV_BRANCH}" \ - "${ORIGIN_REMOTE}" \ "${MAIN_BRANCH}" if [ "${SKIP_TESTS}" -eq 1 ]; then printf '3. Skip the pre-release test gate.\n' @@ -304,8 +350,8 @@ require_repo_state require_dev_checkout require_clean_worktree fetch_remote_state -require_branch_matches_remote "${DEV_BRANCH}" -require_branch_matches_remote "${MAIN_BRANCH}" +require_branch_contains_remote "${DEV_BRANCH}" +require_branch_matches_remote_exactly "${MAIN_BRANCH}" RELEASE_VERSION="$(resolve_release_version)" RELEASE_TAG="v${RELEASE_VERSION}" @@ -341,8 +387,8 @@ fi run_pre_release_tests require_clean_worktree fetch_remote_state -require_branch_matches_remote "${DEV_BRANCH}" -require_branch_matches_remote "${MAIN_BRANCH}" +require_branch_contains_remote "${DEV_BRANCH}" +require_branch_matches_remote_exactly "${MAIN_BRANCH}" require_release_tag_available "${RELEASE_VERSION}" git switch "${MAIN_BRANCH}" >/dev/null