If we understand what files git rebase
creates, then we can manually create our own rebase plan.
# Lets check out a branch to work with
git checkout origin/main demo-branch
# and kick off a rebase against root
git rebase -i --root
While our EDITOR
is open, we have a directory that looks like this.
tree .git/rebase-merge
.git/rebase-merge
├── .git-rebase-todo.swp
├── git-rebase-todo
├── git-rebase-todo.backup
├── head-name
├── interactive
├── no-reschedule-failed-exec
├── onto
├── orig-head
└── squash-onto
We have a few files that have data in them
git-rebase-todo
is the list of commits that we are used to seeing.head-name
is a reference to our starting branch. In this caserefs/heads/demo-branch
orig-head
is a reference to our starting branch as a commitish.onto
andsquash-onto
are our new parents (since we used--root
)
There are also a few files that mark flags we set
interactive
no-reschedule-failed-exec
( A lot of tools like starship.rs
will also look at the existence of files under our .git
tree for displaying
status
)
Knowing what is in this directory, we should be able to craft a rebase programmatically, and then execute it with git rebase --continue
Creating a Rebase
First, we want to setup our basic rebase
# Directory where our rebase request will be built (running `git rebase` also makes this directory)
mkdir .git/rebase-merge
# Ensure we treat this as an interactive rebase
touch .git/rebase-merge/interactive
# And optionally set any flags we want
touch .git/rebase-merge/no-reschedule-failed-exec
Next, we need to setup the branches that will be affected by our rebase.
# Save our branch name
git symbolic-ref HEAD > .git/rebase-merge/head-name
# Save the original head
git rev-parse HEAD > .git/rebase-merge/orig-head
Normally running git rebase
will populate git-rebase-todo
with all the commits, and you can use keywords like pick
and squash
and so on to direct how you want to rebase.
# Example git-rebase-todo from
# https://git-scm.com/docs/git-rebase
pick deadbee Implement feature XXX
fixup f1a5c00 Fix to feature XXX
exec make
pick c0ffeee The oneline of the next commit
edit deadbab The oneline of the commit after
exec cd subdir; make test
...
Since we’re rebasing, we need to configure the commit we will rebase onto. If we want to rebase against a new root, we need to create a new, empty commit
# All commits need a tree, and a 'null' commit is often a tree pointing to /dev/null
NULL_TREE=`git hash-object -t tree /dev/null`
# Given our tree, we create a new commit
NULL_COMMIT=`echo 'Some commit message' | git commit-tree $NULL_TREE`
# We want to save our commit into our rebase-merge config
echo $NULL_COMMIT > .git/rebase-merge/onto
echo $NULL_COMMIT > .git/rebase-merge/squash-onto
# Then we checkout our detached head
# Since we'll start applying our commits here.
git checkout --quiet $NULL_COMMIT
With this in place, we have created the state that a normal git rebase -i --root
would have.
Now we can run git rebase --continue
and git
will run through our .git/rebase-merge/git-rebase-todo
and apply all of our changes.
Because we made a .git/rebase-merge/interactive
file, it will work the same as any other interactive merge, and will prompt us any time there are commits to be resolved.