Using Rebase to Tidy up Dolt Commit History
Dolt is a relational database that provides Git-style version control for your data and schema. In addition to all the power of SQL, you can create branches to work on data changes in isolation, merge changes, quickly diff any two points in history, work with a forked copy of a data set, and much more. Dolt even supports the more advanced features of Git, such as rebasing.
In today's blog post, we're looking at an example of how a video game developer could use Dolt's rebasing support to clean up their commit history before getting their game configuration changes reviewed and merged back into the main branch. If you're looking for more background on how rebasing works, check out our previous blog post on rebasing where we explain the mental model for rebasing, how commits are selected, and how changes from commits are reapplied during rebasing.
Rebasing with Dolt
Just over a year ago, we announced support for interactive rebasing with Dolt. Rebasing is a powerful feature, because it enables you to clean up your commit history, squash commits together, reorder commits, and more. This is especially useful when you're working in a team, and you want to make sure your commit history is clean and easy to understand before your merge your development changes back to the main branch.
Since launching initial support for rebasing last year, we've made several enhancements, including: CLI support for interactive rebasing, support for the --empty
flag for handling empty commits during rebasing, and support for resolving data conflicts during rebasing. Let us know if there are other rebase features that would be useful for you.
Game configuration data
One of the areas where we're seeing customers adopt Dolt is using Dolt to version video game configuration data. Modern games rely on a huge amount of configuration data in order to control the behavior of the game. Configuration controls things like the physics of the game, character attributes, level design, when and where enemies spawn, how and when the overall story progresses, and much more. Finely tuning these values is often the key to producing a game that is well-balanced, has the right amount of challenging, and is fun to play.
If you want to read about how Dolt is being used for versioning game configuration by a real Dolt customer, check out this case study.
Retro RPG example
Let's take a closer look at a concrete example and see how we can use Dolt's rebasing support to help us keep a clean commit history while we iterate on our game configuration changes. In our example game, we're building a turn-based role-playing game, similar to the original Final Fantasy NES game from way back in 1987.
Our game team consists of two game developers who build the core game engine, two game artists who create all the art resources, and four game designers who build the game configuration that the engine uses to run the game. This game configuration defines almost everything in the game, including how spells work, how strong weapons and armor are, how the map is laid out, where and when enemies spawn, and much more. Building a fun addition to a game is an iterative, team effort, and takes many rounds of play testing and tweaking to get it right.
The Cove of Trials expansion
One of these game designers is working on creating a new expansion area for the game, called the "Cove of Trials". This involves adding a new area to the game's map, creating new weapons, creating new treasure chests, and creating many new enemies. The game designer has been working on this for a few weeks, and has made many commits to the game configuration data in Dolt using a branch named cove-of-trials-dev
. We can use the dolt log
command from the Dolt CLI to take a look at the commit history of this branch so far...
dolt log HEAD...main --oneline
6d10eqso3pcn8vr9illeigdcanv47j37 (HEAD -> cove-of-trials-dev) Changing the Balrog weapon (feedback from art team): the Fire Axe
i084ope70b1grcmttta98snsggetu9ni Adjusting the Balrog to have more defense
eon9epviub1p054h9n5jkgtkbnj7440i Giving the Balrog a new weapon: the Flame Whip
icddjokgrlrcrjt47i3071i3t91i9o04 Adjusting the Balrog to have a faster attack
eossq7c9uii1o93q5p0okusjtehlital Turning on debugging logging
ki0sfu2mucfu3kj174jimu2rgj688sur Adding the Balrog boss enemy
rvlu683o38b1j221qrkugu7rhvnae8h7 Oopsie, really fixing the map bug now
tf9k37vhjf674b93osp9d18ocsu7booe Fixing bug with map definition
qbi1ear2bvc5m6vjqsl4ufhdmck00phr Adding treasure chests to the Cove of Trails
msi1oqk62hsavs71088cl5svt9k8pmnh Creating new weapon: Ice Sword
bbn5q538oniv5t55csi067iiu53aqcc9 Creating new overworld map area
We can already see that adding a new section to the game requires many small changes and configuration updates. Creating regular commits allows game designers to work on the game configuration in small, manageable chunks, and helps to keep track of the changes they've made. After lots of iterating, these changes can start to become cluttered in our history. That's where rebasing can help us tidy things up.
Let's clean up a few things in our history. In particular, we want to clean up a few mistakes we made. We misspelled "Trials" in one commit, and it took us a couple tries to fix a bug in the game's map configuration.
From the CLI, we can start an interactive rebase by running dolt rebase -i
. When we run that (just like with git rebase -i
) a text editor is brought up with the default rebase plan populated and ready for us to customize:
pick bbn5q538oniv5t55csi067iiu53aqcc9 Creating new overworld map area
pick msi1oqk62hsavs71088cl5svt9k8pmnh Creating new weapon: Ice Sword
pick qbi1ear2bvc5m6vjqsl4ufhdmck00phr Adding treasure chests to the Cove of Trials
pick tf9k37vhjf674b93osp9d18ocsu7booe Fixing bug with map definition
pick rvlu683o38b1j221qrkugu7rhvnae8h7 Oopsie, really fixing the map bug now
pick ki0sfu2mucfu3kj174jimu2rgj688sur Adding the Balrog boss enemy
pick eossq7c9uii1o93q5p0okusjtehlital Turning on debugging logging
pick icddjokgrlrcrjt47i3071i3t91i9o04 Adjusting the Balrog to have a faster attack
pick eon9epviub1p054h9n5jkgtkbnj7440i Giving the Balrog a new weapon: the Flame Whip
pick i084ope70b1grcmttta98snsggetu9ni Adjusting the Balrog to have more defense
pick 6d10eqso3pcn8vr9illeigdcanv47j37 Changing the Balrog weapon (feedback from art team): the Fire Axe```
# Rebase d0lt8jam2mj5kh48vvais1g6g74ipla7..86h6etccebvard5bgsoj5apds8h3rgoc onto d0lt8jam2mj5kh48vvais1g6g74ipla7 (11 commands)
#
# Commands:
# p, pick <commit> = use commit
# d, drop <commit> = remove commit
# r, reword <commit> = use commit, but edit the commit message
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's message
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
Let's make a few changes to the commit list to tidy up those embarrassing mistakes we made. We'll move the bug fix commits to right below the change where we updated the game map, and mark those as "fixup" commits, which means they will be rolled into the previous commit, without updating its commit message. We'll also use the "reword" action to fix the commit message in qbi1ear2bvc5m6vjqsl4ufhdmck00phr
where we misspelled "Trials". After those edits, our customized rebase plan looks like the plan below. (Note that we added a couple empty lines to help highlight the sections we're changing.)
pick bbn5q538oniv5t55csi067iiu53aqcc9 Creating new overworld map area
fixup tf9k37vhjf674b93osp9d18ocsu7booe Fixing bug with map definition
fixup rvlu683o38b1j221qrkugu7rhvnae8h7 Oopsie, really fixing the map bug now
pick msi1oqk62hsavs71088cl5svt9k8pmnh Creating new weapon: Ice Sword
reword qbi1ear2bvc5m6vjqsl4ufhdmck00phr Adding treasure chests to the Cove of Trials
pick ki0sfu2mucfu3kj174jimu2rgj688sur Adding the Balrog boss enemy
pick eossq7c9uii1o93q5p0okusjtehlital Turning on debugging logging
pick icddjokgrlrcrjt47i3071i3t91i9o04 Adjusting the Balrog to have a faster attack
pick eon9epviub1p054h9n5jkgtkbnj7440i Giving the Balrog a new weapon: the Flame Whip
pick i084ope70b1grcmttta98snsggetu9ni Adjusting the Balrog to have more defense
pick 6d10eqso3pcn8vr9illeigdcanv47j37 Changing the Balrog weapon (feedback from art team): the Fire Axe
After we exit the editor, the rebase plan is executed, and our commit log looks a little bit cleaner and easier to read:
dolt log --oneline HEAD...main
vmb5lijd38vq9sj0f323t7muubgs6dgf (HEAD -> cove-of-trials-dev) Changing the Balrog weapon (feedback from art team): the Fire Axe
qokqsjmb8s88250fptajtu4o5dfmj8km Adjusting the Balrog to have more defense
5nqji6ukdcc4a4f6qitg2ii8f6kbcteg Giving the Balrog a new weapon: the Flame Whip
e3d5mjk1n0uh1ghv42r98sjrvsvigtrh Adjusting the Balrog to have a faster attack
c84nab4o1cls1l0abfnm361ff827radt Turning on debugging logging
58au6rji8nukrhvfjesr9u0644d1nb9q Adding the Balrog boss enemy
eogp8des7fc6fb6d1qrt5mnnv8emlbfa Adding treasure chests to the Cove of Trials
dkp2k33dljtr66sfha4nccshjpahqmod Creating new weapon: Ice Sword
egt5j91fh9qs0s34eo68rjd5nrs6jihl Creating new overworld map area
The Balrog attacks!
It turns out that the Balrog character is copyrighted by the Tolkien Estate, so we can't use it in our game. The game artists will handle reskinning the visual design of the character, but we also need to change the name in the game configuration. A common Git convention is to use the "fixup:" prefix in a commit message where you're making a small change to fix a problem in a previous commit.
We make the configuration changes and add a new commit to the tip of our cove-of-trials-dev
branch and here's what our commit log looks like now:
dolt log --oneline HEAD...main
qdsfnt868min0pdgfddt3jeb5coene4u (HEAD -> cove-of-trials-dev) fixup: Renaming the Balrog to the Fire Demon
vmb5lijd38vq9sj0f323t7muubgs6dgf Changing the Balrog weapon (feedback from art team): the Fire Axe
qokqsjmb8s88250fptajtu4o5dfmj8km Adjusting the Balrog to have more defense
5nqji6ukdcc4a4f6qitg2ii8f6kbcteg Giving the Balrog a new weapon: the Flame Whip
e3d5mjk1n0uh1ghv42r98sjrvsvigtrh Adjusting the Balrog to have a faster attack
c84nab4o1cls1l0abfnm361ff827radt Turning on debugging logging
58au6rji8nukrhvfjesr9u0644d1nb9q Adding the Balrog boss enemy
eogp8des7fc6fb6d1qrt5mnnv8emlbfa Adding treasure chests to the Cove of Trials
dkp2k33dljtr66sfha4nccshjpahqmod Creating new weapon: Ice Sword
egt5j91fh9qs0s34eo68rjd5nrs6jihl Creating new overworld map area
There are still several commits that reference "Balrog", so this seems like a good time to combine those commits and reword the commit message to reflect the new character name. We again run dolt rebase -i main
to start another interactive rebase, and in the text editor that pops up, we modify our rebase plan to look like this:
pick egt5j91fh9qs0s34eo68rjd5nrs6jihl Creating new overworld map area
pick c84nab4o1cls1l0abfnm361ff827radt Turning on debugging logging
pick dkp2k33dljtr66sfha4nccshjpahqmod Creating new weapon: Ice Sword
pick eogp8des7fc6fb6d1qrt5mnnv8emlbfa Adding treasure chests to the Cove of Trials
reword 58au6rji8nukrhvfjesr9u0644d1nb9q Adding the Fire Demon boss enemy
fixup e3d5mjk1n0uh1ghv42r98sjrvsvigtrh Adjusting the Balrog to have a faster attack
fixup 5nqji6ukdcc4a4f6qitg2ii8f6kbcteg Giving the Balrog a new weapon: the Flame Whip
fixup qokqsjmb8s88250fptajtu4o5dfmj8km Adjusting the Balrog to have more defense
fixup vmb5lijd38vq9sj0f323t7muubgs6dgf Changing the Balrog weapon (feedback from art team): the Fire Axe
fixup qdsfnt868min0pdgfddt3jeb5coene4u fixup: Renaming the Balrog to the Fire Demon
Note that we moved all the Balrog commits together so that we could reword the first to change the name, and then used "fixup" on the rest to combine them all into the previous commit, without combining their commit messages. Our commit history is now starting to look very tidy! It almost makes the chaotic development process look like a well-planned and executed project.
dolt log --oneline HEAD...main
mkm85obo1vg7d0os94t9mvainl13nesr (HEAD -> cove-of-trials-dev) Adding the Fire Demon boss enemy
718b3k6c9fq6cldi5jk9kqlsfj6gbqqs Adding treasure chests to the Cove of Trials
ice0d7v0odgk4ltpebp3v9nuaqrasg9i Creating new weapon: Ice Sword
slisjjn6qhburlic7gfa6gn3u37p6nnr Turning on debugging logging
l42pi2pmie2m93bf81jidk4hlmkssk3u Creating new overworld map area
The party was wiped out...
We fixed that copyright issue, but... play testing our changes was brutal! The enemies turned out to be way too hard and because of that, the testers just didn't enjoy the Cove of Trials, and didn't want to play through it. We need to adjust the enemy configuration to make it less of a meat grinder and more of a fun challenge.
We add a few more commits and go through more play testing until we have a good balance of challenge to reward. Our commit history looks like this now:
dolt log --oneline
m4nvufgmaspiacs669v6eqheon17le3f (HEAD -> cove-of-trials-dev) fixup: dialing down Fire Axe burning after effect
6gf3fef70hhc180u7440b64u24sbh3fc fixup: reducing Fire Demon defense
ra4i5jha7bft9u3ggg9i85gim5ubiifj fixup: reducing Fire Demon attack strength and HP
mkm85obo1vg7d0os94t9mvainl13nesr Adding the Fire Demon boss enemy
718b3k6c9fq6cldi5jk9kqlsfj6gbqqs Adding treasure chests to the Cove of Trials
ice0d7v0odgk4ltpebp3v9nuaqrasg9i Creating new weapon: Ice Sword
slisjjn6qhburlic7gfa6gn3u37p6nnr Turning on debugging logging
l42pi2pmie2m93bf81jidk4hlmkssk3u Creating new overworld map area
We can clean this up with another interactive rebase (dolt rebase -i main
) and adjust the rebase plan to use the "fixup" action to combine these new commits with the main Fire Demon commit. Here's our updated rebase plan:
pick l42pi2pmie2m93bf81jidk4hlmkssk3u Creating new overworld map area
pick slisjjn6qhburlic7gfa6gn3u37p6nnr Turning on debugging logging
pick ice0d7v0odgk4ltpebp3v9nuaqrasg9i Creating new weapon: Ice Sword
pick 718b3k6c9fq6cldi5jk9kqlsfj6gbqqs Adding treasure chests to the Cove of Trials
pick mkm85obo1vg7d0os94t9mvainl13nesr Adding the Fire Demon boss enemy
fixup ra4i5jha7bft9u3ggg9i85gim5ubiifj fixup: reducing Fire Demon attack strength and HP
fixup 6gf3fef70hhc180u7440b64u24sbh3fc fixup: reducing Fire Demon defense
fixup m4nvufgmaspiacs669v6eqheon17le3f fixup: dialing down Fire Axe burning after effect
Now, we're left with a tidy, clean set of commits on our dev branch:
dolt log --oneline HEAD...main
fl850rtj1c3khcts47d72pohffqchoa6 (HEAD -> cove-of-trials-dev) Adding the Fire Demon boss enemy
60vshi5ip3b5k6l649o1bsdio7e0opnh Adding treasure chests to the Cove of Trials
qj25b96ga6d2hcqg8i1olsa05u3fdmsj Creating new weapon: Ice Sword
aguhokudoeuipllq31q4pvmi30ui3vvr Turning on debugging logging
3pqvlqijm3roa4t2gludtbeg0581gf6q Creating new overworld map area
Dropping Changes
During development, we turned on some extra debugging configuration. This causes a lot of debugging data to be written out while we're play testing, and although that data is useful during development, we don't want to check this into the main branch. Now that we're ready to get our changes reviewed and merge them in, we need to turn this debugging setting off. We could create a new commit that turns the setting off, but if you know how to rebase, and you've made small, granular commits, it's easier and cleaner to simply drop that commit from your commit history. That turns off the debugging changes we made and removes any sign of them ever being enabled from our commit history.
We can use the "drop" rebase action to completely remove commit aguhokudoeuipllq31q4pvmi30ui3vvr
from our commit log. Here's the edited rebase plan we're going to execute:
pick 3pqvlqijm3roa4t2gludtbeg0581gf6q Creating new overworld map area
drop aguhokudoeuipllq31q4pvmi30ui3vvr Turning on debugging logging
pick qj25b96ga6d2hcqg8i1olsa05u3fdmsj Creating new weapon: Ice Sword
pick 60vshi5ip3b5k6l649o1bsdio7e0opnh Adding treasure chests to the Cove of Trials
pick fl850rtj1c3khcts47d72pohffqchoa6 Adding the Fire Demon boss enemy
Now, when we look at our commit history, we don't see any sign of that debugging change:
dolt log --oneline HEAD...main
cg3vq6ugkk2titc92cm5sg2s5uln7kpl (HEAD -> cove-of-trials-dev) Adding the Fire Demon boss enemy
150h2nuin6ui2m1jh4vrl9subbq70mnq Adding treasure chests to the Cove of Trials
riqphla8726at68342elp98r6e8gops8 Creating new weapon: Ice Sword
d8ee2rpjhbd7fj74h1vv9gp1pm1c7c2r Creating new overworld map area
One rebase to squash them all
Another useful way to use rebase is to squash all commits on a feature branch into a single commit before merging. This is largely a personal preference. One advantage of squashing before merging is that the commit history on main stays clean and high level, without including any of the nitty-gritty small commits that were relevant during development, but will be less relevant when looking back at the history in a year or so. On the other hand, many people believe that keeping the more granular version history around provides valuable information about how the data was changed, and should be kept in. In the end, there isn't a right or wrong choice, but it's good to know about the options and tradeoffs.
If you and your team do decide to squash dev commits into a single commit before merging, Dolt lets you do it in a few different ways. We can use dolt rebase -i
below to combine all the changes on the feature branch into a single commit by editing our rebase plan to pick or reword the first commit, and then squash all the other commits into it:
reword d8ee2rpjhbd7fj74h1vv9gp1pm1c7c2r Creating Cove of Trails expansion
squash riqphla8726at68342elp98r6e8gops8 Creating new weapon: Ice Sword
squash 150h2nuin6ui2m1jh4vrl9subbq70mnq Adding treasure chests to the Cove of Trials
squash cg3vq6ugkk2titc92cm5sg2s5uln7kpl Adding the Fire Demon boss enemy
Now, if we look at our commit history, we just see one commit, with the other commit messages combined in it:
dolt log HEAD...main
commit hvgl0vqf9prf75rrn140ji8f31p408gd (HEAD -> cove-of-trials-dev)
Author: root <root@localhost>
Date: Mon Jan 06 14:07:55 -0800 2025
Creating Cove of Trails expansion
Creating new weapon: Ice Sword
Adding treasure chests to the Cove of Trials
Adding the Fire Demon boss enemy
Summary
Configuration data, whether it's video game configuration, or network configuration, is a great use case for Dolt. Dolt lets you track exactly how your configuration has changed over time, who changed it, and when it was changed. Branches let you work on changes in isolation, fast diffs let you quickly compare any two points in history, and tools like rebase allow you to clean up your commit history before merging it back for the rest of your team to pick up.
If you want to talk about game development, data versioning, or database development, come join us on the DoltHub Discord server! We're always happy to help people get started with Dolt and discuss ideas for leveraging Dolt in your applications.