I'm working on a project where we'd like to edit the LESS files that came with Twitter Bootstrap. The standard advice is to leave those files untouched, to make it easier to upgrade Bootstrap. But that advice isn't working for us; our code is becoming fragile and hard to maintain.
It seems like it should be possible to solve this with git subtree merging: We could edit Bootstrap's LESS files to have clear, maintainable code, then use git's merge tools to bring in new versions of Bootstrap.
We've come up with a plan for using subtree merging. But before we put the plan into action, I'd like to get some feedback: Are there significant drawbacks that we've overlooked? Is there a better/simpler approach?
Approaches with significant drawbacks
In the simplest approach to subtree merging, all the commits from the subtree (i.e., Bootstrap) are merged into your project's master
branch. The drawback is that Bootstrap has so many commits, our commits get lost in the noise. We'd like to keep their commits off our master
branch.
In theory, we could use git rebase -i
to squash all the Bootstrap commits into one commit, that we then merge into master
. But rebase -i
does not work well with merges.
@Sigi helpfully suggested that we use squash merges to go from bootstrap-upstream
to master
. Grabbing the initial version of Bootstrap (v3.1.0) worked OK. But when we merged in the next version (v3.1.1), we over 100 merge conflicts. Every Bootstrap change between 3.1.0 and 3.1.1 was flagged as a conflict. (Our exact steps are in this gist.)
The plan
Our plan is to use a branch, merge-from-bootstrap
, for pulling Bootstrap commits into our repository. When merging changes in to merge-from-bootstrap
(either our changes on master
, or Bootstrap's changes), always record the merge. When merging out from merge-from-bootstrap
to master
, use git merge --squash
so that the merge is not recorded.
Our hope is that:
master
stays managable, because the Bootstrap commits never become part of that branch.- Git has as much information as possible when merging in a new version of Bootstrap, since the
merge-from-bootstrap
branch has the full history of our changes and the Bootstrap team's changes.
Proof of concept
In the following steps, we grab Bootstrap v3.1.0, make some changes, and then upgrade to v3.1.1. The POC seems to work well (as did my tests with our real repo), but I'd like to know if we're setting ourselves up for trouble down the line.
Start with a new repo, where the only file is README.md
(i.e., the new repo that GitHub gives you).
# Add bootstrap as a remote
git remote add bootstrap https://github.com/twbs/bootstrap.git
# Only fetch the master branch; don't fetch tags
git config remote.bootstrap.fetch +refs/heads/master:refs/remotes/bootstrap/master
git config remote.bootstrap.tagopt --no-tags
git fetch bootstrap
# Start with Bootstrap v3.1.0
git checkout -b merge-from-bootstrap
# SHA is the commit tagged v3.1.0 from the bootstrap repo
git merge -s ours --no-commit 1409cde7e800ca83fd761f87e5ad8f0d259e38d1
git read-tree -u --prefix=bootstrap/ 1409cde7e800ca83fd761f87e5ad8f0d259e38d1
git commit -am "Bootstrap v3.1.0"
# Merge Bootstrap 3.1.0 to master
git checkout master
git merge --squash merge-from-bootstrap
git commit -am "Merge bootstrap v3.1.0 to master"
# Make some changes on master, so that we have something to
# be merged
sed -e 's/= space/= force-merge-conflict/g' -i '' bootstrap/.editorconfig
git commit -am "Force a merge conflict"
sed -e 's/"Helvetica Neue"/"Comic Sans"/g' -i '' bootstrap/less/variables.less
git commit -am "Comic Sans"
# Get ready to upgrade to the new version of Bootstrap
git checkout merge-from-bootstrap
git merge -s recursive -Xtheirs master
# Merge in Bootstrap v3.1.1 from bootstrap/master to
# merge-to-bootstrap. (SHA is for v3.1.1 from the bootstrap repo)
git merge -s recursive -X subtree=bootstrap --no-commit a365d8689c3f3cee7f1acf86b61270ecca8e106d
# Fix the merge conflict, then do:
git commit -am "Merged in Bootstrap v3.1.1"
# Merge back to master
git checkout master
git merge --squash merge-from-bootstrap