From 1bead05d19f0e475908240a80a6050d80105def6 Mon Sep 17 00:00:00 2001 From: Javanaut Date: Thu, 16 Apr 2026 19:36:40 +0200 Subject: [PATCH] ff --- tools/merge_dev_into_main.sh | 50 ++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/tools/merge_dev_into_main.sh b/tools/merge_dev_into_main.sh index 3cf4ff6..e540635 100755 --- a/tools/merge_dev_into_main.sh +++ b/tools/merge_dev_into_main.sh @@ -189,7 +189,27 @@ branch_divergence_counts() { printf '%s %s\n' "${remote_only}" "${local_only}" } -require_branch_contains_remote() { +fast_forward_branch_to_remote() { + local branch="$1" + local remote_ref="refs/remotes/${ORIGIN_REMOTE}/${branch}" + local current_head="" + + current_head="$(git rev-parse --abbrev-ref HEAD)" + + printf "Fast-forwarding local branch '%s' to '%s/%s'...\n" \ + "${branch}" \ + "${ORIGIN_REMOTE}" \ + "${branch}" + + if [ "${current_head}" = "${branch}" ]; then + git merge --ff-only "${remote_ref}" >/dev/null + return 0 + fi + + git branch -f "${branch}" "${remote_ref}" >/dev/null +} + +sync_release_source_branch() { local branch="$1" local remote_only="" local local_only="" @@ -201,7 +221,7 @@ require_branch_contains_remote() { 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." + fast_forward_branch_to_remote "${branch}" fi if [ "${local_only}" -ne 0 ]; then @@ -213,26 +233,24 @@ require_branch_contains_remote() { fi } -require_branch_matches_remote_exactly() { +sync_release_target_branch() { 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." + if [ "${local_only}" -ne 0 ]; then + fail "Local branch '${branch}' is ahead of '${ORIGIN_REMOTE}/${branch}' by ${local_only} commit(s). Push or reconcile first so the release starts from the published ${branch} tip." 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." + if [ "${remote_only}" -ne 0 ]; then + fast_forward_branch_to_remote "${branch}" + fi } resolve_release_version() { @@ -295,9 +313,7 @@ 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 contains %s/%s while local %s exactly matches %s/%s.\n' \ - "${ORIGIN_REMOTE}" \ - "${DEV_BRANCH}" \ + printf '2. Fetch %s, fast-forward local %s and %s from %s when safe, and fail on divergence or unpublished local %s commits.\n' \ "${ORIGIN_REMOTE}" \ "${DEV_BRANCH}" \ "${MAIN_BRANCH}" \ @@ -350,8 +366,8 @@ require_repo_state require_dev_checkout require_clean_worktree fetch_remote_state -require_branch_contains_remote "${DEV_BRANCH}" -require_branch_matches_remote_exactly "${MAIN_BRANCH}" +sync_release_source_branch "${DEV_BRANCH}" +sync_release_target_branch "${MAIN_BRANCH}" RELEASE_VERSION="$(resolve_release_version)" RELEASE_TAG="v${RELEASE_VERSION}" @@ -387,8 +403,8 @@ fi run_pre_release_tests require_clean_worktree fetch_remote_state -require_branch_contains_remote "${DEV_BRANCH}" -require_branch_matches_remote_exactly "${MAIN_BRANCH}" +sync_release_source_branch "${DEV_BRANCH}" +sync_release_target_branch "${MAIN_BRANCH}" require_release_tag_available "${RELEASE_VERSION}" git switch "${MAIN_BRANCH}" >/dev/null