|
| 1 | +# Upstream Sync Skill |
| 2 | + |
| 3 | +Sync fork with one or more upstream remotes via cherry-pick + merge-base advance. |
| 4 | + |
| 5 | +## When to use |
| 6 | + |
| 7 | +User says: "sync with upstream", "cherry-pick from X", "merge upstream", or similar. |
| 8 | + |
| 9 | +## Workflow |
| 10 | + |
| 11 | +### Phase 1: Explore divergence |
| 12 | + |
| 13 | +```bash |
| 14 | +git fetch <remote> |
| 15 | +git log --oneline --right-only <branch>...<remote>/master --no-merges |
| 16 | +``` |
| 17 | + |
| 18 | +For each upstream-only commit, get files changed: |
| 19 | + |
| 20 | +```bash |
| 21 | +git log --right-only <branch>...<remote>/master --no-merges --format="%h %s" | while read hash msg; do |
| 22 | + echo "=== $hash $msg ==="; git diff-tree --no-commit-id --name-only -r $hash; echo |
| 23 | +done |
| 24 | +``` |
| 25 | + |
| 26 | +### Phase 2: Triage commits |
| 27 | + |
| 28 | +| Category | Action | |
| 29 | +|----------|--------| |
| 30 | +| Already in fork | SKIP | |
| 31 | +| Release/version bumps | SKIP | |
| 32 | +| Lock file only | SKIP | |
| 33 | +| Native WebRTC lib version changes | SKIP (fork uses StreamWebRTC) | |
| 34 | +| Merge commits | SKIP | |
| 35 | +| Cosmetic formatting | SKIP (run formatters separately) | |
| 36 | +| Bug fixes | CHERRY-PICK | |
| 37 | +| New features | CHERRY-PICK (ask user) | |
| 38 | +| Refactoring | CHERRY-PICK (evaluate risk) | |
| 39 | +| Docs/CI/tools | Ask user | |
| 40 | + |
| 41 | +Check for equivalents: `git log --oneline --left-only <branch>...<remote>/master | grep -i "<keyword>"` |
| 42 | + |
| 43 | +### Phase 3: Ask user |
| 44 | + |
| 45 | +Present triage. Ask about large/risky features, optional items, anything ambiguous. |
| 46 | + |
| 47 | +### Phase 4: Cherry-pick in order |
| 48 | + |
| 49 | +```bash |
| 50 | +git checkout -b sync/upstream-cherry-picks <base-branch> |
| 51 | +``` |
| 52 | + |
| 53 | +Order: TS fixes → Android fixes → iOS fixes → small features → large features → docs. |
| 54 | + |
| 55 | +If conflict: resolve, `git add`, `git cherry-pick --continue --no-edit`. |
| 56 | +If empty after resolution: `git cherry-pick --skip`. |
| 57 | + |
| 58 | +### Phase 5: Merge to advance merge-base |
| 59 | + |
| 60 | +Without this, future merges replay ALL upstream commits including skipped ones. |
| 61 | + |
| 62 | +```bash |
| 63 | +git merge <remote>/master --no-commit |
| 64 | + |
| 65 | +# Conflicted files — keep ours |
| 66 | +git diff --name-only --diff-filter=U | xargs git checkout --ours |
| 67 | +# Files deleted in our branch — remove |
| 68 | +git rm <deleted-files> |
| 69 | +# Auto-merged files — reset to our version |
| 70 | +git diff --cached --name-only --diff-filter=M | xargs git checkout HEAD -- |
| 71 | +# Unwanted new files from upstream — remove |
| 72 | +git diff --cached --name-only --diff-filter=A # review, then: |
| 73 | +git rm -f <unwanted-files> |
| 74 | + |
| 75 | +git add -A |
| 76 | +git diff --cached --stat HEAD # should be empty or near-empty |
| 77 | +git commit -m "merge: sync merge-base with <remote>/master" |
| 78 | +``` |
| 79 | + |
| 80 | +Verify: `git log --oneline --right-only <branch>...<remote>/master | wc -l` should be `0`. |
| 81 | + |
| 82 | +### Phase 6: Verify |
| 83 | + |
| 84 | +Run ALL of these. Do not skip any. |
| 85 | + |
| 86 | +```bash |
| 87 | +npm run lint |
| 88 | +cd examples/GumTestApp/android && ./gradlew assembleDebug |
| 89 | +cd examples/GumTestApp/ios && pod install && \ |
| 90 | + xcodebuild -workspace GumTestApp.xcworkspace -scheme GumTestApp \ |
| 91 | + -sdk iphonesimulator -configuration Debug build |
| 92 | +``` |
| 93 | + |
| 94 | +### Phase 7: Format native files |
| 95 | + |
| 96 | +```bash |
| 97 | +git ls-files | grep -e "\(\.java\|\.h\|\.m\)$" | grep -v examples | xargs npx clang-format -i |
| 98 | +``` |
| 99 | + |
| 100 | +Rebuild Android + iOS to confirm, then commit. |
| 101 | + |
| 102 | +### Phase 8: Update package-lock.json |
| 103 | + |
| 104 | +If `package.json` dependencies changed, lock file will be stale. |
| 105 | + |
| 106 | +```bash |
| 107 | +npm install |
| 108 | +git add package-lock.json && git commit -m "chore: update package-lock.json" |
| 109 | +``` |
| 110 | + |
| 111 | +## Preservation rules |
| 112 | + |
| 113 | +These MUST NOT change during sync: |
| 114 | + |
| 115 | +| File | Guard | |
| 116 | +|------|-------| |
| 117 | +| `android/build.gradle` | Must keep `io.getstream:stream-video-webrtc-android:*` | |
| 118 | +| `stream-react-native-webrtc.podspec` | Must keep `StreamWebRTC` dependency | |
| 119 | +| `ios/RCTWebRTC/Utils/AudioDeviceModule/` | Fork's custom audio engine — untouched | |
| 120 | +| `SpeechActivityDetector.java` | Fork's custom VAD — untouched | |
| 121 | +| `AudioDeviceModule.ts`, `AudioDeviceModuleEvents.ts` | Fork's custom TS APIs — untouched | |
| 122 | + |
| 123 | +Post-sync: `grep -r "org.webrtc:google-webrtc\|webrtc-ios" --include="*.gradle" --include="*.podspec" .` must return nothing. |
| 124 | + |
| 125 | +## Pitfalls |
| 126 | + |
| 127 | +1. **Always run native builds, not just tsc.** Cherry-picks can pass tsc but fail gradlew/xcodebuild. |
| 128 | + |
| 129 | +2. **Native API names differ across WebRTC versions.** Enum values, type names, and method signatures may not exist in our WebRTC SDK. After cherry-picking from a fork on a different WebRTC version, verify types exist before building. |
| 130 | + |
| 131 | +3. **`git add -A` re-adds files you removed.** Use `git rm -f` (not `--cached`) to remove from both index and disk. |
| 132 | + |
| 133 | +4. **Auto-merged files need `git checkout HEAD --`, not `--ours`.** `--ours` only works on conflicted files. For auto-merged files with unwanted changes, use `git checkout HEAD -- <file>`. |
| 134 | + |
| 135 | +5. **Watch for duplicates after conflict resolution.** Duplicate variable declarations, closing braces, or imports when keeping both sides of a conflict. |
| 136 | + |
| 137 | +6. **Advance merge-base for EVERY upstream remote.** If syncing with multiple upstreams, merge each one separately. Otherwise the un-advanced remote replays all its history on the next merge. |
| 138 | + |
| 139 | +7. **Upstream podspec/build files leak into cherry-picks.** Other forks have their own podspec (e.g., `livekit-react-native-webrtc.podspec`). Always `git rm` them when they appear. |
| 140 | + |
| 141 | +8. **Cross-check cherry-picks against all upstreams for reverts.** Before cherry-picking a commit from one upstream, search the other upstreams for the same change — it may have been tried and reverted. Run: `git log --all --oneline -S "<key code snippet>"` to find if the same change exists elsewhere in history with a subsequent revert. |
| 142 | + |
| 143 | +9. **Verify cherry-picked changes still exist on upstream HEAD.** A commit could have been added and later reverted/modified by a subsequent commit on the same upstream. After cherry-picking, verify the actual code still matches upstream's current state: `git show <remote>/master:<file> | grep "<key code>"`. Don't just trust that a commit was made — it may have been undone. |
| 144 | + |
| 145 | +10. **Squash-merging the sync PR destroys the merge-base advancement.** The merge commits from Phase 5 (`git merge <remote>/master`) are the only thing that tells git "we've seen up to this point." Squash-merge flattens them into a single commit, losing that information. When merging the sync PR, use **"Create a merge commit"** (regular merge), not squash. If already squashed, create a follow-up PR with ghost merges: `git merge -s ours <remote>/master` for each upstream, then merge that PR with a regular merge commit. |
0 commit comments