Add linearize fallback for reverse sync and workflow guide (v1.3)

When josh-proxy rejects a reverse sync push due to unmappable merge
commits, fall back to linearizing: cherry-pick regular commits
individually, squash only the merge commits via cherry-pick -m 1.

Also adds a recommended Git workflow section to the guide explaining
where cross-branch merges should happen (monorepo) vs feature work
(subrepo), and expands troubleshooting for the "josh rejected push"
error with root cause analysis and prevention advice.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-17 12:48:17 +03:00
parent 8ab07b83ab
commit d6f334b861
6 changed files with 253 additions and 19 deletions

View File

@@ -284,37 +284,109 @@ reverse_sync() {
local ts
ts=$(date +%Y%m%d-%H%M%S)
local staging_branch="auto-sync/subrepo-${subrepo_branch}-${ts}"
local push_ref="HEAD"
local linearized=false
if git push -o "base=${mono_branch}" "$(josh_auth_url)" "HEAD:refs/heads/${staging_branch}"; then
log "INFO" "Pushed to staging branch via josh: ${staging_branch}"
if ! git push -o "base=${mono_branch}" "$(josh_auth_url)" "${push_ref}:refs/heads/${staging_branch}" 2>/dev/null; then
# Josh rejects pushes when the history contains merge commits whose
# parents it cannot map (e.g., merges of staging/feature branches that
# include auto-sync history). Fall back to linearizing: cherry-pick
# regular commits as-is, squash only the problematic merge commits.
# See ADR-011.
log "WARN" "Direct push failed — linearizing history (cherry-pick + squash merges)"
# 6. Create PR on monorepo (NEVER direct push)
local pr_body
pr_body="## Subrepo changes
local original_head
original_head=$(git rev-parse HEAD)
git checkout -b "tmp-linearize-$$" "mono-filtered/${mono_branch}" 2>/dev/null \
|| die "Failed to create linearize branch"
local commit_shas
commit_shas=$(git log --ancestry-path "mono-filtered/${mono_branch}..${original_head}" \
--reverse --format="%H" --invert-grep --grep="^${BOT_TRAILER}:" 2>/dev/null || echo "")
local sha
for sha in $commit_shas; do
local parent_count
parent_count=$(git cat-file -p "$sha" | grep -c "^parent ")
if [ "$parent_count" -le 1 ]; then
# Regular commit — cherry-pick to preserve message and authorship
if ! git cherry-pick "$sha" 2>/dev/null; then
log "WARN" "Cherry-pick conflict on ${sha:0:7} — applying tree directly"
git cherry-pick --abort 2>/dev/null
# Fall back to applying the exact tree diff for this commit
local msg author_name author_email author_date
msg=$(git log -1 --format="%B" "$sha")
author_name=$(git log -1 --format="%an" "$sha")
author_email=$(git log -1 --format="%ae" "$sha")
author_date=$(git log -1 --format="%aI" "$sha")
git diff HEAD "$sha" | git apply --index 2>/dev/null \
|| die "Failed to apply diff for ${sha:0:7}"
GIT_AUTHOR_NAME="$author_name" GIT_AUTHOR_EMAIL="$author_email" \
GIT_AUTHOR_DATE="$author_date" \
git commit -m "$msg" || die "Failed to commit ${sha:0:7}"
fi
else
# Merge commit — cherry-pick relative to first parent (squashes the merge)
local msg author_name author_email author_date
msg=$(git log -1 --format="%B" "$sha")
author_name=$(git log -1 --format="%an" "$sha")
author_email=$(git log -1 --format="%ae" "$sha")
author_date=$(git log -1 --format="%aI" "$sha")
if ! git cherry-pick -m 1 "$sha" 2>/dev/null; then
git cherry-pick --abort 2>/dev/null
git diff HEAD "$sha" | git apply --index 2>/dev/null \
|| die "Failed to apply merge diff for ${sha:0:7}"
GIT_AUTHOR_NAME="$author_name" GIT_AUTHOR_EMAIL="$author_email" \
GIT_AUTHOR_DATE="$author_date" \
git commit -m "$msg" || die "Failed to commit merge ${sha:0:7}"
fi
log "INFO" "Squashed merge commit ${sha:0:7}: $(echo "$msg" | head -1)"
fi
done
push_ref="HEAD"
linearized=true
git push -o "base=${mono_branch}" "$(josh_auth_url)" \
"${push_ref}:refs/heads/${staging_branch}" \
|| { log "ERROR" "Josh rejected linearized push — check josh-proxy logs"; echo "josh-rejected"; return; }
fi
log "INFO" "Pushed to staging branch via josh: ${staging_branch}${linearized:+ (linearized)}"
# 6. Create PR on monorepo (NEVER direct push)
local pr_body
local linearize_note=""
if [ "$linearized" = true ]; then
linearize_note="
> **Note:** Merge commits were squashed during sync because the subrepo
> history contained merges that josh-proxy cannot map (see ADR-011).
> Regular commits are preserved individually."
fi
pr_body="## Subrepo changes
New commits from subrepo \`${subrepo_branch}\`:
\`\`\`
${human_commits}
\`\`\`
${linearize_note}
**Review checklist:**
- [ ] Changes scoped to synced subfolder
- [ ] No leaked credentials or environment-specific config
- [ ] CI passes"
create_pr "${MONOREPO_API}" "${GITEA_TOKEN}" \
"$mono_branch" "$staging_branch" \
"[Subrepo Sync] ${subrepo_branch}${mono_branch}" \
"$pr_body" \
|| die "Failed to create PR on monorepo (check GITEA_TOKEN)"
create_pr "${MONOREPO_API}" "${GITEA_TOKEN}" \
"$mono_branch" "$staging_branch" \
"[Subrepo Sync] ${subrepo_branch}${mono_branch}" \
"$pr_body" \
|| die "Failed to create PR on monorepo (check GITEA_TOKEN)"
log "INFO" "Reverse sync PR created on monorepo"
echo "pr-created"
else
log "ERROR" "Josh rejected push — check josh-proxy logs"
echo "josh-rejected"
fi
log "INFO" "Reverse sync PR created on monorepo"
echo "pr-created"
}
# ─── Initial Import: Subrepo → Monorepo (first time) ───────────────