Skip to content

Replace peer_repo + source_is_private with public_repo + private_repo #3

Replace peer_repo + source_is_private with public_repo + private_repo

Replace peer_repo + source_is_private with public_repo + private_repo #3

Workflow file for this run

# Reusable workflow: sync creation.
# Triggered by a consuming repo on push to its default branch.
# All decision-making logic lives in Python (repo_sync.workflows.cli).
name: sync
on:
workflow_call:
inputs:
public_repo:
description: "Public repo (e.g. warpdotdev/warp-public)."
required: true
type: string
private_repo:
description: "Private repo (e.g. warpdotdev/warp-internal)."
required: true
type: string
escalate_to:
description: "GitHub team or user to escalate to on timeout."
required: false
type: string
default: "@oncall-client-primary"
slack_webhook_url:
description: "Slack webhook URL for stripping error notifications."
required: false
type: string
default: ""
secrets:
auth_token:
required: true
concurrency:
group: repo-sync-${{ github.repository == inputs.private_repo && 'private-to-public' || 'public-to-private' }}-${{ github.repository }}-${{ github.repository == inputs.private_repo && inputs.public_repo || inputs.private_repo }}
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.auth_token }}
PUBLIC_REPO: ${{ inputs.public_repo }}
PRIVATE_REPO: ${{ inputs.private_repo }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0, token: "${{ secrets.auth_token }}" }
- uses: actions/checkout@v4
with: { repository: warpdotdev/repo-sync, ref: v1, path: .repo-sync, token: "${{ secrets.auth_token }}" }
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -e .repo-sync
# Derive peer_repo and source_is_private from the two repo names.
- name: Derive sync context
id: context
run: |
if [ "${{ github.repository }}" = "${PRIVATE_REPO}" ]; then
echo "source_is_private=true" >> "$GITHUB_OUTPUT"
echo "peer_repo=${PUBLIC_REPO}" >> "$GITHUB_OUTPUT"
else
echo "source_is_private=false" >> "$GITHUB_OUTPUT"
echo "peer_repo=${PRIVATE_REPO}" >> "$GITHUB_OUTPUT"
fi
- name: Detect direction
id: direction
run: |
PRIVATE_FLAG=""
[ "${{ steps.context.outputs.source_is_private }}" = "true" ] && PRIVATE_FLAG="--source-is-private"
python -m repo_sync.workflows.cli detect-direction $PRIVATE_FLAG
# No early-exit loop check here. enumerate_unsynced_commits already
# filters out sync-originated commits per-commit. An early exit on
# the trigger SHA would incorrectly skip unsynced commits when a
# sync-originated commit replaces a normal commit in the concurrency
# group's pending slot.
- name: Read watermark
id: watermark
run: |
python -m repo_sync.workflows.cli read-watermark \
--peer-repo "${{ steps.context.outputs.peer_repo }}" \
--direction "${{ steps.direction.outputs.direction }}"
- name: List unsynced commits
id: unsynced
run: |
python -m repo_sync.workflows.cli list-unsynced \
--repo-dir . --gh-repo "${{ github.repository }}" \
--direction "${{ steps.direction.outputs.direction }}" \
--default-branch "${{ github.event.repository.default_branch }}" \
--watermark-sha "${{ steps.watermark.outputs.last_synced_sha }}"
- name: Checkout peer repo
if: steps.unsynced.outputs.count != '0'
uses: actions/checkout@v4
with:
repository: ${{ steps.context.outputs.peer_repo }}
ref: ${{ github.event.repository.default_branch }}
path: peer
fetch-depth: 0
token: ${{ secrets.auth_token }}
- name: Find existing stack top
if: steps.unsynced.outputs.count != '0'
id: stack_top
run: |
python -m repo_sync.workflows.cli find-stack-top \
--peer-repo "${{ steps.context.outputs.peer_repo }}" \
--direction "${{ steps.direction.outputs.direction }}"
# Per-commit sync PR creation. The loop and git/gh side effects remain
# in shell; all decision logic (descriptions, reviewer, idempotency) is
# delegated to Python CLI subcommands.
- name: Create sync PRs
if: steps.unsynced.outputs.count != '0'
env:
ESCALATE_TO: ${{ inputs.escalate_to }}
SLACK_WEBHOOK_URL: ${{ inputs.slack_webhook_url }}
SOURCE_REPO: ${{ github.repository }}
STACK_TOP: ${{ steps.stack_top.outputs.stack_top }}
run: |
set -euo pipefail
DIRECTION="${{ steps.direction.outputs.direction }}"
BRANCH_PREFIX="${{ steps.direction.outputs.branch_prefix }}"
PEER_REPO="${{ steps.context.outputs.peer_repo }}"
SOURCE_IS_PRIVATE="${{ steps.context.outputs.source_is_private }}"
DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
STACK_BASE_BRANCH="${STACK_TOP:-${DEFAULT_BRANCH}}"
IS_STACK_BOTTOM="true"
[ -n "${STACK_TOP:-}" ] && IS_STACK_BOTTOM="false"
while IFS= read -r SOURCE_SHA; do
[ -z "$SOURCE_SHA" ] && continue
SHORT_SHA="${SOURCE_SHA:0:7}"
SYNC_BRANCH="${BRANCH_PREFIX}/${SHORT_SHA}"
# Idempotency guard.
if gh api "/repos/${PEER_REPO}/git/ref/heads/${SYNC_BRANCH}" --jq '.ref' 2>/dev/null | grep -q .; then
STACK_BASE_BRANCH="${SYNC_BRANCH}"; IS_STACK_BOTTOM="false"; continue; fi
if gh pr list --repo "${PEER_REPO}" --head "${SYNC_BRANCH}" --state all \
--json number --jq '.[0].number // empty' 2>/dev/null | grep -q .; then
STACK_BASE_BRANCH="${SYNC_BRANCH}"; IS_STACK_BOTTOM="false"; continue; fi
if [ "$SOURCE_IS_PRIVATE" = "true" ]; then
# --- Private-to-public: strip, tree replacement, Docker agent. ---
SNAPSHOT_DIR="/tmp/snapshot-${SHORT_SHA}"
mkdir -p "${SNAPSHOT_DIR}"
# Extract the tree at SOURCE_SHA into SNAPSHOT_DIR, then strip in-place.
git archive "${SOURCE_SHA}" | tar -x -C "${SNAPSHOT_DIR}"
if ! python -m repo_sync.strip.cli "${SNAPSHOT_DIR}"; then
[ -n "$SLACK_WEBHOOK_URL" ] && curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"repo-sync: stripping failed for ${SHORT_SHA} in ${SOURCE_REPO}.\"}" \
"$SLACK_WEBHOOK_URL" || true
exit 1
fi
pushd peer > /dev/null
git checkout -B _sync_work "origin/${STACK_BASE_BRANCH}" 2>/dev/null \
|| git checkout -B _sync_work "${STACK_BASE_BRANCH}"
git rm -rf --quiet . 2>/dev/null || true
cp -a "${SNAPSHOT_DIR}/." . && git add -A
if git diff --cached --quiet; then popd > /dev/null; rm -rf "${SNAPSHOT_DIR}"; continue; fi
git checkout -b "${SYNC_BRANCH}"
git commit -m "repo-sync: sync from private
Repo-Sync-Origin: ${SOURCE_REPO}@${SOURCE_SHA}"
git diff "origin/${STACK_BASE_BRANCH}" HEAD -- > /tmp/diff-${SHORT_SHA}.patch
AGENT_OUT="/tmp/agent-out-${SHORT_SHA}"; mkdir -p "$AGENT_OUT"
PR_TITLE="repo-sync: sync from private (${SHORT_SHA})"

Check failure on line 168 in .github/workflows/sync.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/sync.yml

Invalid workflow file

You have an error in your yaml syntax on line 168
PR_BODY="repo-sync: sync from private (source: \`${SHORT_SHA}\`)"
if docker run --rm \
-v "${SNAPSHOT_DIR}:/workspace:ro" -v "/tmp/diff-${SHORT_SHA}.patch:/diff.patch:ro" \
-v "$(pwd)/../.repo-sync/.agents/skills/pr-description/SKILL.md:/skill.md:ro" \
-v "${AGENT_OUT}:/output" -w /workspace warpdotdev/repo-sync-agent:v1 \
oz agent run --skill-file /skill.md --context-file /diff.patch --output-dir /output \
2>/dev/null; then
[ -f "${AGENT_OUT}/title.txt" ] && PR_TITLE=$(cat "${AGENT_OUT}/title.txt")
[ -f "${AGENT_OUT}/body.txt" ] && PR_BODY=$(cat "${AGENT_OUT}/body.txt")
fi
PR_BODY="${PR_BODY}
Repo-Sync-Origin: ${SOURCE_REPO}@${SOURCE_SHA}"
git push origin "${SYNC_BRANCH}"
PR_URL=$(gh pr create --repo "${PEER_REPO}" --head "${SYNC_BRANCH}" \
--base "${STACK_BASE_BRANCH}" --title "${PR_TITLE}" --body "${PR_BODY}")
if [ "$IS_STACK_BOTTOM" = "true" ]; then
gh pr merge "$(echo "$PR_URL" | grep -oE '[0-9]+$')" \
--repo "${PEER_REPO}" --auto --squash || true
fi
popd > /dev/null
rm -rf "${SNAPSHOT_DIR}" "/tmp/diff-${SHORT_SHA}.patch" "${AGENT_OUT}"
else
# --- Public-to-private: cherry-pick, Python-built description. ---
pushd peer > /dev/null
if ! git remote get-url source >/dev/null 2>&1; then
git remote add source "$(cd .. && pwd)"
fi
git fetch source
git checkout -B _sync_work "origin/${STACK_BASE_BRANCH}" 2>/dev/null \
|| git checkout -B _sync_work "${STACK_BASE_BRANCH}"
git checkout -b "${SYNC_BRANCH}"
CHERRY_PICK_FAILED="false"
if ! git cherry-pick "${SOURCE_SHA}" --allow-empty -x 2>/dev/null; then
CHERRY_PICK_FAILED="true"
if ! oz agent run --skill warpdotdev/repo-sync:conflict-resolution \
--context "Conflicting files: $(git diff --name-only --diff-filter=U)" 2>/dev/null; then
git cherry-pick --abort 2>/dev/null || true
git commit --allow-empty -m "repo-sync: cherry-pick conflict — needs manual resolution
Repo-Sync-Origin: ${SOURCE_REPO}@${SOURCE_SHA}"
fi
fi
# Append Repo-Sync-Origin trailer to the commit regardless of
# whether the cherry-pick succeeded cleanly or the agent resolved
# conflicts. This ensures the trailer is always present in the
# commit message for loop detection.
if [ "$CHERRY_PICK_FAILED" = "false" ]; then
git commit --amend -m "$(git log -1 --format='%B')
Repo-Sync-Origin: ${SOURCE_REPO}@${SOURCE_SHA}"
else
# Agent-resolved cherry-pick: amend the agent's commit to add the trailer.
EXISTING_MSG=$(git log -1 --format='%B')
if ! echo "$EXISTING_MSG" | grep -q '^Repo-Sync-Origin:'; then
git commit --amend --no-edit -m "${EXISTING_MSG}
Repo-Sync-Origin: ${SOURCE_REPO}@${SOURCE_SHA}"
fi
fi
# Build description via Python.
COMMIT_SUBJECT=$(git log -1 --format='%s' "${SOURCE_SHA}" 2>/dev/null || echo "sync")
COMMIT_BODY_TEXT=$(git log -1 --format='%b' "${SOURCE_SHA}" 2>/dev/null || true)
DESC_JSON=$(python -m repo_sync.workflows.cli build-description \
--source-repo "${SOURCE_REPO}" --source-sha "${SOURCE_SHA}" \
--commit-subject "${COMMIT_SUBJECT}" --commit-body "${COMMIT_BODY_TEXT}")
PR_TITLE=$(echo "$DESC_JSON" | jq -r '.title')
PR_BODY=$(echo "$DESC_JSON" | jq -r '.body')
PR_BODY="${PR_BODY}
Repo-Sync-Origin: ${SOURCE_REPO}@${SOURCE_SHA}"
git push origin "${SYNC_BRANCH}"
PR_URL=$(gh pr create --repo "${PEER_REPO}" --head "${SYNC_BRANCH}" \
--base "${STACK_BASE_BRANCH}" --title "${PR_TITLE}" --body "${PR_BODY}")
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
if [ "$CHERRY_PICK_FAILED" = "true" ]; then
REVIEWER_JSON=$(python -m repo_sync.workflows.cli determine-reviewer \
--source-repo "${SOURCE_REPO}" --source-sha "${SOURCE_SHA}" \
--fallback-team "${ESCALATE_TO}")
REVIEWER=$(echo "$REVIEWER_JSON" | jq -r '.reviewer')
gh pr edit "${PR_NUMBER}" --repo "${PEER_REPO}" --add-reviewer "${REVIEWER}" 2>/dev/null || true
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
CURRENT_BODY=$(gh pr view "${PR_NUMBER}" --repo "${PEER_REPO}" --json body --jq '.body')
gh pr edit "${PR_NUMBER}" --repo "${PEER_REPO}" \
--body "${CURRENT_BODY}
Repo-Sync-Assigned: ${REVIEWER}@${TIMESTAMP}"
elif [ "$IS_STACK_BOTTOM" = "true" ]; then
gh pr merge "${PR_NUMBER}" --repo "${PEER_REPO}" --auto --squash || true
fi
popd > /dev/null
fi
STACK_BASE_BRANCH="${SYNC_BRANCH}"; IS_STACK_BOTTOM="false"
done < /tmp/unsynced_commits.txt