3

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:

  1. master stays managable, because the Bootstrap commits never become part of that branch.
  2. 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
Community
  • 1
  • 1
Evan
  • 2,400
  • 1
  • 18
  • 34

1 Answers1

1

Take the approach that is described in the Pro Git book:

On the Bootstrap branch, merge the upstream changes:

$ git checkout bootstrap-upstream
$ git pull

Then, on your master branch, subtree merge the Bootstrap subtree with the --squash option:

$ git checkout master
$ git merge --squash -s subtree --no-commit bootstrap-upstream

The key here is the use of the subtree merge strategy, which takes all the changes from Bootstrap wholesale and puts it in their place (sub-directory).

Commit and write your commit message:

$ git commit

This will avoid having all the history of bootstrap-upstream in your master branch, and you won't have to use git rebase.

Sigi
  • 1,784
  • 13
  • 19
  • Thanks for answering. But when I merged `bootstrap-upstream` to `master` a second time, I got a ton of merge conflicts. All the changes to Bootstrap between versions were flagged as a conflict. I've edited the question with more details. – Evan Apr 29 '14 at 18:52
  • You did not use the **subtree** merge strategy, as advised in my answer, but the **recursive** strategy. Can you please try again with the `-s subtree` option, because that is key to my proposed solution. – Sigi Apr 29 '14 at 19:01
  • By the way, very recent versions of `git` have a `subtree` subcommand, which might make things still easier for you. Please have a look at that, too. The manual page for `git-subtree` is actually quite readable (for Git standards, anyway :) – Sigi Apr 29 '14 at 19:03
  • Thanks for the pointer to `git-subtree`; that looks promising. As for the merge strategy: I did try the subtree merge strategy (line 46 of [the gist](https://gist.github.com/evan-dickinson/11407758)) and it deleted nearly all of the Bootstrap files. The second try used the subtree argument to the recursive strategy, and it caused mass merge conflicts. But I don't think I'll pursue either of those approaches, and look into `git-subtree` instead. Cheers. – Evan Apr 30 '14 at 23:45
  • I don't have any experience with `git-subtree`, since it is so new. My understanding is, though, that it would be very similar to the manual approach described above. Please check back here and report your experience, I'd be interested in that. – Sigi Apr 30 '14 at 23:47