Skip to content

Commit 623b382

Browse files
committed
Add script to sych upstream
Change-Id: Ib7fb5d4584442ed4ccfbcfbb3ad9268eecde0aa0 Co-developed-by: Cursor <noreply@cursor.com>
1 parent 5f80b89 commit 623b382

11 files changed

Lines changed: 1024 additions & 131 deletions

File tree

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Sync upstream changes into a local branch via "git merge" (not rebase).
4+
#
5+
# Why merge instead of rebase?
6+
# - After merge, the sync branch is a direct descendant of the local base
7+
# branch, so merging the sync branch back into main is always clean.
8+
# - Upstream commits are preserved in the merge history; next time we run
9+
# "git merge upstream/main", Git automatically skips already-merged commits.
10+
# - Conflicts only need to be resolved once (during the merge), not twice
11+
# (once during rebase, once when merging back).
12+
13+
set -euo pipefail
14+
15+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
16+
REPO_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
17+
18+
UPSTREAM_REMOTE="upstream"
19+
UPSTREAM_URL=""
20+
UPSTREAM_BRANCH="main"
21+
UPSTREAM_COMMIT="" # if set, merge this commit instead of upstream/branch tip
22+
BASE_BRANCH="main"
23+
SYNC_BRANCH=""
24+
RESUME=false
25+
SKIP_PR=false
26+
SKIP_MAPPING_UPDATE=false
27+
28+
MAPPING_FILE="$REPO_ROOT/util/opentelemetry-util-genai/upstream_version_map.json"
29+
VERSION_FILE="$REPO_ROOT/util/opentelemetry-util-genai/src/opentelemetry/util/genai/version.py"
30+
UPDATE_MAPPING_SCRIPT="$REPO_ROOT/util/opentelemetry-util-genai/scripts/update_upstream_version_map.py"
31+
GENERATE_TABLE_SCRIPT="$REPO_ROOT/util/opentelemetry-util-genai/scripts/generate_version_mapping_table.py"
32+
README_FILE="$REPO_ROOT/util/opentelemetry-util-genai/README-loongsuite.rst"
33+
34+
usage() {
35+
cat <<'EOF'
36+
Usage:
37+
.github/scripts/sync-upstream-with-rebase.sh [options]
38+
39+
Options:
40+
--upstream-remote <name> Upstream git remote name (default: upstream)
41+
--upstream-url <url> Upstream remote URL (required when remote does not exist)
42+
--upstream-branch <name> Upstream branch name (default: main)
43+
--upstream-commit <sha> Sync to this specific commit instead of branch tip
44+
--base-branch <name> Local base branch name (default: main)
45+
--sync-branch <name> Sync working branch (default: auto-generated)
46+
--resume Continue after manual conflict resolution
47+
--skip-pr Do not create pull request automatically
48+
--skip-mapping-update Do not update util-genai upstream mapping files
49+
-h, --help Show this help message
50+
51+
Typical flow:
52+
1) Start sync:
53+
.github/scripts/sync-upstream-with-rebase.sh --upstream-url <url>
54+
55+
2) Sync to a specific upstream commit:
56+
.github/scripts/sync-upstream-with-rebase.sh --upstream-url <url> \\
57+
--upstream-commit <sha-or-ref>
58+
59+
3) If conflict occurs, resolve conflicts, then:
60+
git add <resolved-files>
61+
git commit # finish the merge commit
62+
.github/scripts/sync-upstream-with-rebase.sh --resume --sync-branch <branch>
63+
(or /tmp/sync-upstream-with-rebase.sh if script not on current branch)
64+
EOF
65+
}
66+
67+
while [[ $# -gt 0 ]]; do
68+
case "$1" in
69+
--upstream-remote)
70+
UPSTREAM_REMOTE="$2"
71+
shift 2
72+
;;
73+
--upstream-url)
74+
UPSTREAM_URL="$2"
75+
shift 2
76+
;;
77+
--upstream-branch)
78+
UPSTREAM_BRANCH="$2"
79+
shift 2
80+
;;
81+
--upstream-commit)
82+
UPSTREAM_COMMIT="$2"
83+
shift 2
84+
;;
85+
--base-branch)
86+
BASE_BRANCH="$2"
87+
shift 2
88+
;;
89+
--sync-branch)
90+
SYNC_BRANCH="$2"
91+
shift 2
92+
;;
93+
--resume)
94+
RESUME=true
95+
shift
96+
;;
97+
--skip-pr)
98+
SKIP_PR=true
99+
shift
100+
;;
101+
--skip-mapping-update)
102+
SKIP_MAPPING_UPDATE=true
103+
shift
104+
;;
105+
-h|--help)
106+
usage
107+
exit 0
108+
;;
109+
*)
110+
echo "Unknown option: $1"
111+
usage
112+
exit 1
113+
;;
114+
esac
115+
done
116+
117+
cd "$REPO_ROOT"
118+
119+
require_command() {
120+
if ! command -v "$1" >/dev/null 2>&1; then
121+
echo "Missing required command: $1"
122+
exit 1
123+
fi
124+
}
125+
126+
is_merge_in_progress() {
127+
[[ -f .git/MERGE_HEAD ]]
128+
}
129+
130+
ensure_clean_worktree() {
131+
if [[ -n $(git status --porcelain) ]]; then
132+
echo "Worktree is not clean. Please commit or stash changes first."
133+
exit 1
134+
fi
135+
}
136+
137+
ensure_remote() {
138+
if git remote get-url "$UPSTREAM_REMOTE" >/dev/null 2>&1; then
139+
return
140+
fi
141+
if [[ -z "$UPSTREAM_URL" ]]; then
142+
echo "Remote '$UPSTREAM_REMOTE' not found. Please provide --upstream-url."
143+
exit 1
144+
fi
145+
git remote add "$UPSTREAM_REMOTE" "$UPSTREAM_URL"
146+
}
147+
148+
# Resolved ref to merge (either a specific commit or branch tip)
149+
get_upstream_target() {
150+
if [[ -n "$UPSTREAM_COMMIT" ]]; then
151+
git rev-parse --verify "$UPSTREAM_COMMIT"
152+
else
153+
echo "${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}"
154+
fi
155+
}
156+
157+
extract_upstream_version() {
158+
local target
159+
target=$(get_upstream_target)
160+
git show "${target}:util/opentelemetry-util-genai/src/opentelemetry/util/genai/version.py" \
161+
| python3 -c 'import re,sys; m=re.search(r"__version__\s*=\s*\"([^\"]+)\"", sys.stdin.read()); print(m.group(1) if m else "unknown")'
162+
}
163+
164+
update_mapping_and_readme() {
165+
local target upstream_commit upstream_version ref_type ref_val
166+
target=$(get_upstream_target)
167+
upstream_commit=$(git rev-parse "$target")
168+
upstream_version=$(extract_upstream_version)
169+
170+
if [[ -n "$UPSTREAM_COMMIT" ]]; then
171+
ref_type="commit"
172+
ref_val="$upstream_commit"
173+
else
174+
ref_type="branch"
175+
ref_val="$UPSTREAM_BRANCH"
176+
fi
177+
178+
python3 "$UPDATE_MAPPING_SCRIPT" \
179+
--mapping-file "$MAPPING_FILE" \
180+
--version-file "$VERSION_FILE" \
181+
--upstream-version "$upstream_version" \
182+
--upstream-commit "$upstream_commit" \
183+
--upstream-ref-type "$ref_type" \
184+
--upstream-ref "$ref_val"
185+
186+
python3 "$GENERATE_TABLE_SCRIPT" --mapping "$MAPPING_FILE" --readme "$README_FILE"
187+
188+
if ! git diff --quiet -- "$MAPPING_FILE" "$README_FILE"; then
189+
git add "$MAPPING_FILE" "$README_FILE"
190+
git commit -m "chore(util-genai): update upstream mapping after sync"
191+
fi
192+
}
193+
194+
push_branch() {
195+
git push -u origin "$SYNC_BRANCH"
196+
}
197+
198+
create_pr() {
199+
if [[ "$SKIP_PR" == true ]]; then
200+
return
201+
fi
202+
require_command gh
203+
local title body upstream_desc
204+
upstream_desc="${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}"
205+
if [[ -n "$UPSTREAM_COMMIT" ]]; then
206+
upstream_desc="commit $(git rev-parse --short "$UPSTREAM_COMMIT") (from ${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH})"
207+
fi
208+
title="chore: sync ${upstream_desc} into ${BASE_BRANCH}"
209+
body=$(
210+
cat <<EOF
211+
## Summary
212+
- Merge upstream \`${upstream_desc}\` into \`${BASE_BRANCH}\`
213+
- Preserve upstream commit history for incremental future syncs
214+
- Auto-update util-genai upstream version mapping
215+
216+
## Notes
217+
- This PR can be merged into \`${BASE_BRANCH}\` without conflicts (conflicts were resolved during the upstream merge)
218+
- Use **merge commit** (not squash) to preserve upstream commit granularity
219+
EOF
220+
)
221+
222+
if gh pr view "$SYNC_BRANCH" >/dev/null 2>&1; then
223+
echo "PR for branch $SYNC_BRANCH already exists, skipping creation."
224+
return
225+
fi
226+
227+
gh pr create --base "$BASE_BRANCH" --head "$SYNC_BRANCH" --title "$title" --body "$body"
228+
}
229+
230+
# ── Main ──────────────────────────────────────────────────────────────
231+
232+
require_command git
233+
require_command python3
234+
235+
ensure_remote
236+
git fetch origin "$BASE_BRANCH"
237+
git fetch "$UPSTREAM_REMOTE" "$UPSTREAM_BRANCH"
238+
# When --upstream-commit is set, ensure we have that commit (might need full fetch)
239+
if [[ -n "$UPSTREAM_COMMIT" ]]; then
240+
if ! git rev-parse --verify "$UPSTREAM_COMMIT" >/dev/null 2>&1; then
241+
echo "Commit $UPSTREAM_COMMIT not found. Fetching all upstream refs..."
242+
git fetch "$UPSTREAM_REMOTE"
243+
fi
244+
if ! git rev-parse --verify "$UPSTREAM_COMMIT" >/dev/null 2>&1; then
245+
echo "Error: commit $UPSTREAM_COMMIT does not exist in $UPSTREAM_REMOTE."
246+
exit 1
247+
fi
248+
fi
249+
250+
if [[ "$RESUME" == true ]]; then
251+
# ── Resume after manual conflict resolution ──
252+
if [[ -z "$SYNC_BRANCH" ]]; then
253+
SYNC_BRANCH=$(git branch --show-current)
254+
fi
255+
git checkout "$SYNC_BRANCH"
256+
257+
if is_merge_in_progress; then
258+
echo "Merge is still in progress (MERGE_HEAD exists)."
259+
echo "Please finish the merge commit first:"
260+
echo " git add <resolved-files>"
261+
echo " git commit"
262+
echo "Then re-run:"
263+
echo " .github/scripts/sync-upstream-with-rebase.sh --resume --sync-branch $SYNC_BRANCH"
264+
exit 2
265+
fi
266+
267+
echo "Merge completed. Proceeding to finalize."
268+
269+
else
270+
# ── Start a new sync ──
271+
ensure_clean_worktree
272+
273+
if [[ -z "$SYNC_BRANCH" ]]; then
274+
SYNC_BRANCH="sync/upstream-$(date -u +%Y%m%d-%H%M%S)"
275+
fi
276+
277+
echo "Creating sync branch: $SYNC_BRANCH (from origin/$BASE_BRANCH)"
278+
git checkout -B "$SYNC_BRANCH" "origin/$BASE_BRANCH"
279+
280+
UPSTREAM_TARGET=$(get_upstream_target)
281+
if [[ -n "$UPSTREAM_COMMIT" ]]; then
282+
echo "Merging upstream commit $(git rev-parse --short "$UPSTREAM_TARGET") into $SYNC_BRANCH ..."
283+
else
284+
echo "Merging ${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH} into $SYNC_BRANCH ..."
285+
fi
286+
if ! git merge "$UPSTREAM_TARGET" \
287+
--no-edit \
288+
-m "Merge upstream $(git rev-parse --short "$UPSTREAM_TARGET") into ${SYNC_BRANCH}"; then
289+
290+
echo ""
291+
echo "════════════════════════════════════════════════════════════"
292+
echo " Merge conflict detected."
293+
echo ""
294+
echo " Please resolve conflicts manually:"
295+
echo " 1) Fix conflicting files"
296+
echo " 2) git add <resolved-files>"
297+
echo " 3) git commit # finishes the merge commit"
298+
echo " 4) Re-run this script:"
299+
echo " .github/scripts/sync-upstream-with-rebase.sh \\"
300+
echo " --resume --sync-branch $SYNC_BRANCH"
301+
echo "════════════════════════════════════════════════════════════"
302+
exit 2
303+
fi
304+
305+
echo "Merge completed without conflicts."
306+
fi
307+
308+
# ── Finalize: update mapping, push, create PR ──
309+
310+
if [[ "$SKIP_MAPPING_UPDATE" == false ]]; then
311+
update_mapping_and_readme
312+
fi
313+
314+
push_branch
315+
create_pr
316+
317+
echo ""
318+
echo "Done. Sync branch: $SYNC_BRANCH"
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Sync upstream with rebase
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
upstream_url:
7+
description: "Upstream repository URL"
8+
required: true
9+
default: "https://github.com/open-telemetry/opentelemetry-python-contrib.git"
10+
upstream_branch:
11+
description: "Upstream branch to sync from"
12+
required: true
13+
default: "main"
14+
base_branch:
15+
description: "Target base branch in this repo"
16+
required: true
17+
default: "main"
18+
skip_pr:
19+
description: "Skip creating pull request"
20+
required: false
21+
default: false
22+
type: boolean
23+
skip_mapping_update:
24+
description: "Skip util-genai mapping update"
25+
required: false
26+
default: false
27+
type: boolean
28+
29+
permissions:
30+
contents: write
31+
pull-requests: write
32+
33+
jobs:
34+
sync:
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v4
38+
with:
39+
fetch-depth: 0
40+
41+
- name: Configure git identity
42+
run: |
43+
git config user.name "github-actions[bot]"
44+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
45+
46+
- name: Run upstream sync
47+
env:
48+
SKIP_PR: ${{ inputs.skip_pr }}
49+
SKIP_MAPPING_UPDATE: ${{ inputs.skip_mapping_update }}
50+
GH_TOKEN: ${{ github.token }}
51+
run: |
52+
chmod +x .github/scripts/sync-upstream-with-rebase.sh
53+
cmd=".github/scripts/sync-upstream-with-rebase.sh \
54+
--upstream-url ${{ inputs.upstream_url }} \
55+
--upstream-branch ${{ inputs.upstream_branch }} \
56+
--base-branch ${{ inputs.base_branch }} \
57+
--sync-branch sync/upstream-${{ github.run_id }}"
58+
59+
if [[ "$SKIP_PR" == "true" ]]; then
60+
cmd="$cmd --skip-pr"
61+
fi
62+
if [[ "$SKIP_MAPPING_UPDATE" == "true" ]]; then
63+
cmd="$cmd --skip-mapping-update"
64+
fi
65+
66+
eval "$cmd"

0 commit comments

Comments
 (0)