rebase-branch
Skill400 starsChanged 48 days agoMITView on GitHub
Rebase the current feature branch onto the fresh tip of a base branch (e.g. 2.3.x) — read both sides first, plan every conflict, and keep turbo-shadowed PHP classes and their turbo-ext C++ mirrors identical in each replayed commit
- Deletes or force-pushes
- Commits and pushes
1 other project uses a version of this.
Adopted by
Most recently taken up by
- fulopkovacs/fuko-skillstheir own version, first seen Oct 13, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- fulopkovacs/fuko-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository3 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- turbo-ext/CLAUDE.mdCLAUDE.md
- regression-testSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 2 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 22
If the feature branch is older than this skill, the helpers are not in its checkout.
- Very specific: line 37
Write the three SHAs to the scratchpad.
- Very specific: line 47
Run php <skill dir>/rebase-plan.php $NEW_BASE $OLD_TIP.
- Distinctive: line 124
Never hand-merge generated files.
- Very specific: line 155
Conflict: Run git diff --name-only --diff-filter=U to list the conflicted files and git show REBASE_HEAD to see the commit being replayed.
- Distinctive: line 166
If a stop reveals something the plan didn't foresee and the right resolution is unclear, stop and ask instead of guessing.
- Very specific: line 186
If git diff --quiet "$NEW_BASE" HEAD -- turbo-ext/src reports a difference, run make bump-turbo.
- Very specific: line 194
Run make phpstan and make tests.
- Very specific: line 195
If any turbo twin, turbo-ext file or php-parser version differs from NEW_BASE, run the verify list in turbo-ext/CLAUDE.md at HEAD:
- Very specific: line 207
Make sure the build you test is the one that's loaded.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Rebasing a feature branch onto a fresh base
The base branch is $ARGUMENTS. If that is empty, use the base of the branch's pull request
(gh pr view --json baseRefName -q .baseRefName); if there is no pull request, ask.
Work in the checkout that has the feature branch checked out. Address it by absolute path
(git -C <path>), because the shell's working directory resets between commands. Never use git stash:
the stash is shared by every worktree of the repository. git rebase -i runs here only with a
scripted GIT_SEQUENCE_EDITOR (step 4), never with an interactive editor.
Helpers in this skill's directory (the directory this SKILL.md was loaded from):
rebase-plan.php <new-base> <old-tip>prints a read-only report for the planning step.rebase-todo.phpis aGIT_SEQUENCE_EDITORthat marks commitsdroporedit.
If the feature branch is older than this skill, the helpers are not in its checkout. Extract them with
git show origin/2.3.x:.claude/skills/rebase-branch/<file> > <scratchpad>/<file>.
1. Pin the three points
git fetch origin
git status --porcelain --untracked-files=no # must print nothing: commit first or ask
BRANCH=$(git branch --show-current)
OLD_TIP=$(git rev-parse HEAD)
NEW_BASE=$(git rev-parse origin/<base>) # the remote tip, never the possibly stale local branch
OLD_BASE=$(git merge-base "$NEW_BASE" "$OLD_TIP")
git branch -f "backup/$BRANCH-pre-rebase" "$OLD_TIP"Write the three SHAs to the scratchpad. Every later step uses the SHAs, not refs that can move. If
OLD_BASE equals NEW_BASE, stop, because there is nothing to rebase.
- The feature's work is
git diff $OLD_BASE $OLD_TIP, commit by commitgit log --reverse -p $OLD_BASE..$OLD_TIP. - What happened on the base meanwhile is
git diff $OLD_BASE $NEW_BASE, commit by commitgit log --reverse --no-merges -p $OLD_BASE..$NEW_BASE.
2. Read both sides, plan every conflict
Run php <skill dir>/rebase-plan.php $NEW_BASE $OLD_TIP. It reports:
- the base commits,
- merge commits in the feature range,
- feature commits the base already contains,
- turbo bump commits,
- files changed on both sides, with the feature commits that touch each one,
- the conflicts that
git merge-treepredicts for the two tips, - the turbo twin table (step 3),
- names that vanished on one side but are still spelled in the other side's C++.
The report tells you where to look; it does not replace reading the changes. Read:
- The feature's work:
git log --reverse --stat $OLD_BASE..$OLD_TIP, then the diffs, starting with the files changed on both sides. - The base's work: every base commit that touches a file changed on both sides, plus any base commit that renames, moves or changes the signature of code the feature uses. Those changes break the feature silently, without a textual conflict.
- For each file changed on both sides, put
git diff $OLD_BASE $NEW_BASE -- <file>next togit log --reverse -p $OLD_BASE..$OLD_TIP -- <file>. Decide what the combined file must contain.
Then write the plan to the scratchpad. Include every feature commit that will conflict or needs a mirror port, each with the file, what the base did there, and the intended resolution. A resolution keeps the intent of both sides. When the feature reshaped code that the base then changed, carry the base's change into the feature's new shape; don't just pick one side. When the base moved code that the feature edits, apply the feature's edit at the new location. Where the right resolution is unclear, ask before you start, not in the middle of the replay.
Decide these before replaying (ask the user if the answer isn't obvious):
- Merge commits in the feature range: a rebase flattens them away. Linearize only with the user's agreement.
- Commits the base already contains: this covers patch-identical commits and adapted twins, such as a chunk extracted into its own pull request that has since landed. Drop them instead of replaying them. Replaying one conflicts, and resolving it on the branch's side silently wipes out the base's version.
Bump expected turbo versioncommits: drop all of them and run onemake bump-turboat the end. During the replay, a conflict onEXPECTED_EXTENSION_VERSIONresolves to the base's value.
3. Turbo mirrors
Classes carrying #[ShadowedByTurboExtension(turboClass: ..., implementation: .../turbo-ext/src/X.cpp)]
have a native C++ twin. Vendored twins live in VENDORED_PAIRS in
build/PHPStan/Build/TurboAttributeCollector.php and change when their package's version in
composer.lock changes. The PHP class and the C++ class must behave identically after every replayed
commit. A mirror change goes into the same commit as the PHP change it mirrors, never into a trailing
"port" commit.
The report's twin table uses these classifications:
- Shadowed at OLD BASE and still on both sides: each side already mirrored its own PHP edits.
Resolve a
.cppconflict the same way as the matching.phpconflict, so the merged.cppmirrors the merged.php. If a side changed the.phpbut not the.cpp, check whether that change needed a port (a docblock-only change does not). - NEW ON BASE: the base ported a class that the feature edits. Every feature commit that changes
that PHP class must make the same change in the base's
.cpp. Mark those commitsedit. - NEW ON FEATURE: the feature ports a class that the base changed in the meantime. Port the base's
PHP change into the
.cppin the feature commit that introduces the.cpp, and mark that commitedit. Later feature commits that touch the.cppthen replay on top of the port. - SHADOWED ON BOTH SIDES INDEPENDENTLY: reconcile the two ports by hand, and ask which one survives.
- UNSHADOWED on one side: the other side's
.cppedits no longer apply. Resolve a modify/delete conflict by deleting the file. - Names spelled in C++: native code reaches PHP by name, which git cannot see. It calls methods and
reads properties through method and property sites and string literals. It also copies constants
(e.g.
PhpVersionFactory::MAX_PHP_VERSIONasPT_MS_MAX_PHP_VERSION) and slot numbers (aPT_*_PROP_*copy of another class's property layout). If a twin gains or loses a property on either side, grep turbo-ext for copies of its slot numbers. The report lists removed names and changed constant values it found spelled on the other side, but it can't see a changed parameter list.side-by-side.php,signature-parity.phpand the tests catch that later.
The turbo machinery itself may differ between the base and the feature. Compare turbo-ext/CLAUDE.md
and turbo-ext/README.md on both sides: stub shells vs real-name activation, handwritten reg::Class
registrations vs generated sig:: declarations, helper APIs in reg.h, zv.h and support.h. Write
each mirror in the conventions of the commit being replayed. A port that is new on the base has to
follow the feature's conventions from the feature commit that changed them onwards. The report's last
section lists the shared turbo-ext files the feature changed.
Never hand-merge generated files. Regenerate them in the commit being replayed:
| File | Resolution |
turbo-ext/src/generated/*.h | php turbo-ext/bin/generate-declarations.php, where the generator exists at that commit |
turbo-ext/src/parser/ParserRunnerActions*.cpp, ParserRunnerActionsSplit.h | php turbo-ext/bin/generate-parser-actions.php |
composer.lock | take the current (base) lock, re-apply the commit's composer.json change, then composer update <the packages it changed>, or composer update --lock when only the hash differs |
phpstan-baseline.neon | apply the replayed commit's own edit (git show REBASE_HEAD -- phpstan-baseline.neon) to the current file |
src/Turbo/TurboExtensionEnabler.php version | keep the base's value, bump at the end |
For registries that both sides append to, take the union of both sides' entries. These include
$covered / $coveredElsewhere in turbo-ext/tests/smoke.php, VENDORED_CLASS_MAP, and
pt_class_refs keys. MINIT registrations and the build files' source lists no longer exist: a
replayed commit that adds a pt_register_*() call to main.cpp, its declaration to support.h or
a .cpp name to config.w32 drops that hunk and defines the function with
PT_MINIT_REGISTRATION(pt_register_*) in its own file instead.
4. Replay
GIT_SEQUENCE_EDITOR="php <skill dir>/rebase-todo.php" \
REBASE_DROP="<shas to drop>" REBASE_EDIT="<shas to stop after>" \
git rebase -i --onto "$NEW_BASE" "$OLD_BASE" "$BRANCH"The helper aborts the rebase before anything is replayed if a listed SHA has no pick line. Git's rerere
is enabled on this machine. It may reapply a resolution recorded during an earlier attempt, so review
those hunks (git rerere diff) like any other resolution.
At each stop:
- Conflict. Run
git diff --name-only --diff-filter=Uto list the conflicted files andgit show REBASE_HEADto see the commit being replayed. Resolve according to the plan, and make this commit's planned mirror edits now as well. A conflicting commit markededitis committed by--continuewithout stopping again, so the edit stop never comes. Stage files by name, never a directory. Delete any*.origor*.rejfiles. Then runGIT_EDITOR=true git rebase --continue. - Planned
editstop. Make the mirror edit,git add <files>,git commit --amend --no-edit, thengit rebase --continue. - After touching C++: rebuild turbo-ext incrementally (
make -C turbo-ext) and fix compile errors in the same commit. After touching PHP, runphp -lon the files. - A commit that unexpectedly became empty means a resolution dropped its content. Investigate it;
don't
--skipit. Skip only commits the plan already marked as contained in the base. - If a stop reveals something the plan didn't foresee and the right resolution is unclear, stop and ask instead of guessing.
5. Check nothing was lost
git range-diff "$OLD_BASE..$OLD_TIP" "$NEW_BASE..HEAD"Review every commit that range-diff reports as changed. Each difference must be an intended conflict resolution or mirror port.
# files the rebase changed that the base never touched: exactly the adaptations and mirror ports
comm -23 <(git diff --name-only "$OLD_TIP" HEAD | sort) <(git diff --name-only "$OLD_BASE" "$NEW_BASE" | sort)
# base changes to files the feature never touched must arrive unchanged: expect no output other than
# the planned mirror ports (xargs runs nothing on empty input - a bare `git diff --` would diff everything)
comm -23 <(git diff --name-only "$OLD_BASE" "$NEW_BASE" | sort) <(git diff --name-only "$OLD_BASE" "$OLD_TIP" | sort) | xargs git diff --stat "$NEW_BASE" HEAD --If git diff --quiet "$NEW_BASE" HEAD -- turbo-ext/src reports a difference, run make bump-turbo. It
creates the single bump commit, or amends the unpushed one.
6. Verify
- Sync the dependencies, because the base may have changed the locks: run
composer install,composer install -d testsandcomposer dump-autoload. The dump regeneratesvendor/attributes.phpand the turbo manifest.composer install --dry-runmust then report nothing to install. - Run
make phpstanandmake tests. - If any turbo twin, turbo-ext file or php-parser version differs from
NEW_BASE, run the verify list inturbo-ext/CLAUDE.mdat HEAD:- the strict build,
turbo-ext/tests/smoke.php(ALL OK),turbo-ext/bin/side-by-side.php,turbo-ext/tests/signature-parity.php,turbo-ext/tests/walk-trace.phpwhere it exists,turbo-ext/tests/parser-corpus.phpwhenturbo-ext/src/parser/or php-parser changed,make lint-turbowhen C++ changed,make testswith the extension loaded,--error-format=rawanalysis output that is byte-identical with the extension loaded and not loaded.
- Make sure the build you test is the one that's loaded. The global ini may load a
.sofrom another worktree. Load this checkout's build through a scratchPHP_INI_SCAN_DIR(orphp -n -d extension=...), and confirm thatphpversion('phpstan_turbo')equalsEXPECTED_EXTENSION_VERSION. On a mismatch, turbo silently stays off.
Fix a failure in the commit that introduced it: git commit --fixup=<sha>, then
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash "$NEW_BASE", then make bump-turbo again. Don't add a
trailing fix commit.
7. Report and push
Tell the user:
- the old base and the new base,
- which commits were dropped, and why,
- each conflict, with its file and how it was resolved,
- each mirror port, with its class and commit,
- the verification results,
- the backup branch
backup/$BRANCH-pre-rebase.
Push only when asked: git push --force-with-lease="$BRANCH:$OLD_TIP" origin "$BRANCH". Delete the
backup branch only after the user confirms the result.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

