secret
Last active

a simple git branching model

  • Download Gist
simple-git-branching-model.md
Markdown

a simple git branching model

This is a very simple git workflow. It (and variants) is in use by many people. I settled on it after using it very effectively at Athena. GitHub does something similar; Zach Holman mentioned it in this talk.

Update: Woah, thanks for all the attention. Didn't expect this simple rant to get popular.

Special Thanks to friends for feedback:

The gist

  1. master must always be deployable.
  2. all changes made through feature branches (pull-request + merge)
  3. rebase to avoid/resolve conflicts; merge in to master

Or, as Zach Holman succinctly put it:

flow

The workflow

# everything is happy and up-to-date in master
git checkout master
git pull origin master

# let's branch to make changes
git checkout -b my-new-feature

# go ahead, make changes now.
$EDITOR file

# commit your (incremental, atomic) changes
git add -p
git commit -m "my changes"

# keep abreast of other changes, to your feature branch or master.
# rebasing keeps our code working, merging easy, and history clean.
git fetch origin
git rebase origin/my-new-feature
git rebase origin/master

# optional: push your branch for discussion (pull-request)
#           you might do this many times as you develop.
git push origin my-new-feature

# optional: feel free to rebase within your feature branch at will.
#           ok to rebase after pushing if your team can handle it!
git rebase -i origin/master

# merge when done developing.
# --no-ff preserves feature history and easy full-feature reverts
# merge commits should not include changes; rebasing reconciles issues
# github takes care of this in a Pull-Request merge
git checkout master
git pull origin master
git merge --no-ff my-new-feature


# optional: tag important things, such as releases
git tag 1.0.0-RC1

useful config

# autosetup rebase so that pulls rebase by default
git config --global branch.autosetuprebase always

# if you already have branches (made before `autosetuprebase always`)
git config branch.<branchname>.rebase true

DOs and DON'Ts

No DO or DON'T is sacred. You'll obviously run into exceptions, and develop your own way of doing things. However, these are guidelines I've found useful.

DOs

  • DO keep master in working order.
  • DO rebase your feature branches.
    • DO pull in (rebase on top of) changes
  • DO tag releases
  • DO push feature branches for discussion
  • DO learn to rebase

DON'Ts

  • DON'T merge in broken code.
  • DON'T commit onto master directly.
    • DON'T hotfix onto master! use a feature branch.
  • DON'T rebase master.
  • DON'T merge with conflicts. handle conflicts upon rebasing.

Links

FAQ

Won't git merge --no-ff generate merge bubbles?

Yes. Merge bubbles aren't inherently bad. They allow you to revert entire features at a time. They get confusing and annoying to deal with if they cross (commits interleave), so don't do that.

merge bubbles

What do you mean by "incremental, atomic" changes?

http://en.wikipedia.org/wiki/Atomic_commit#Atomic_Commit_Convention

Thanks wikipedia, I couldn't have put it better myself.

Why not gitflow or another complex workflow?

Be my guest. I've used gitflow and other similar models. After working in various teams, this is just what I've come to use. But next time you have to ask someone whether it is okay to push or pull from this or that branch, remember my face.

But, is it web-scale?

Friends claim more complex models are necessary for scaling large teams, maintaining old releases, controlling information flow, etc. It very well may be that using multiple mainlines (e.g. develop, stable, release, v2, tested, etc) is exactly what fits your organization's constraints. That's for you to decide, not me (unless we work together -- oh hi there!).

But you always have to wonder, "shouldn't I use tags for that"? For example, tracking releases on a branch is a bit silly. A release commit can be tagged. You can checkout a tag, just like any branch, or any commit, and do whatever it is you need to do.

My guess is this relationship holds:

headless

So, perhaps taking five minutes to teach your team how to use checkout and tag might save you more than 15% on car insurance.

GitHub notes

Don't fork. Push feature branches to main repo.

Sometimes I see people forking repositories in order to issue pull-requests. Yes, you may have to do this when contributing to open-source projects you don't regularly contribute to. But, if you are a contributor, or working in the same org, get push rights on the repo and push all your feature branches to it. Issue pull requests from one branch to another within the same repo.

Should I merge Pull Requests on the site or commandline?

Up to you. Github does git merge --no-ff so that the commit message indicates the pull request number. This is useful information to have, don't just throw away history for the sake of it. You never know what will be useful to look at in the future.

z-git-branching.jpeg
z-git-headless.jpeg

Exactly.

One small addition: if you share a feature branch with someone else (because you're working together), don't rebase the pushed branch. You can rebase locally but once it's pushed don't rebase until the very end when it's ready to merge. To help that final rebase, think of annotating your commits with "squashme onto ..." indications.

You may wish to use git rebase -p to merge changes into your feature branches http://notes.envato.com/developers/rebasing-merge-commits-in-git/

It's about time this got popular. Our team has been using this method for years.

We've built a tool to help make sure no one on our team makes mistakes: http://github.com/reenhanced/gitreflow

Reflow automatically creates the pull request against master from your feature branch and ensures that it gets a code review. There's no need for it to be more complicated than this.

just for stuff's sake I also have these added git configs:

settings

git config --global branch.autosetupmerge true
git config --global push.default tracking

display

git config --global log.decorate short
git config --global color.ui auto
git config --global color.interactive auto
git config --global color.diff auto
git config --global color.branch auto
git config --global color.status auto
git config --global pager.status true
git config --global pager.show-branch true
git config --global format.numbered auto

shorts

git config --global alias.st status
git config --global alias.ci commit
git config --global alias.co checkout
git config --global alias.ru "remote update"
git config --global alias.br branch
git config --global alias.cam "commit -a -m"

utils

git config --global alias.praise blame
git config --global alias.staged "diff --cached"
git config --global alias.unstaged diff
git config --global alias.both "diff HEAD"
git config --global alias.oneline "log --oneline"
git config --global alias.amend "commit --amend"
git config --global alias.undo "reset --hard HEAD^"
git config --global alias.tree "log --graph --decorate --pretty=oneline --abbrev-commit --all"
git config --global alias.myhist '!git log --author="$(git config user.name)" --format=%H |xargs git show --name-only --format=-------------%n%Cred%s%Creset%n%Cblue%h%Creset'

I don't fully agree with tagging instead of branching for keep track of releases. Is true you can always go back to that tag easily, but you can't do much things if things go wrong. For example:

Say you have this workflow, and have 2 servers: live and staging. Of course, staging tends to be ahead with new features that you (or your client) don't want to release yet, but a critical bug appears on live. What do you do? If you have a branch for each one of the servers, you just need to commit in there (or fork and merge) and deploy. Done.

You can still fork from a old tag, true, but it makes the history a real mess.

See also: sane branch handling - always set up your pushed branches as 'tracking' branches and have them use rebase.

btw, @fhawkinsozer "undo" is a terrible name for reset --hard. ;)

@pyriku: If you need to put a bugfix on the live server, you can create a branch off of the tag you've made and then commit the bugfix. If that's what you meant by "you can still fork from a old tag", then I don't know what you mean by it making history a real mess -- you end up with the same situation you had before, you just made the branch at a different point in time.

@jbenet: Could you elaborate on the reasoning behind the "Don't Fork" point? What are the pros and cons of the two approaches?

We ended up with almost exactly the same workflow in our team, though we're using Gitlab for repo management. Feature branches, and merge requests in Gitlab facilitating code review (no-self-merge rule).

The only change as that our master branch is always being deployed into "training" (staging) environment, and we're doing releases once every three weeks - branching them off master on the release day, for later hotfixes etc.

@tjmcewan I have something similar but I labeled it bail

@jbenet how come "Don't fork. Push feature branches to main repo."
Inherently I believe you but I can't convince the rest of my team. The fork model allows us to give pull only access to junior devs which they seem to think is useful...

Well done. Thanks for posting this.

@jbenet nice post! I will also add in DON'T rules:

  • Don't rebase people's branch until it's ready to be merged
  • Don't rebase collaborative branch

we work like you at frostwire, never needed to use the "rebase" command (still have no clue what it is for), we just make sure that the feature branches have merged all the changes of the master if the master has kept moving forward, then when you merge the feature into the master you only see the changes, we hardly ever deal with conflicts.

@nicotaing i use this workflow and on feature branches i frequently rebase, reset and force push all the time. those "donts" you just listed aren't really "donts". if nobody else is using my feature branch its no business of yours how often i force push. when there's collaboration on a feature branch it gets more interesting, and then you have to negotiate resets and rebases and force pushes with your collaborators... but, someone just needs to grab the global write lock on the branch (no this isn't a git feature -- use hipchat or whatever) before force pushing then. you failed to read the comment in this article which mentioned explicitly: "ok to rebase after pushing if your team can handle it!"

if there's too many commits on a branch and rebases turn into a mess, i'll also sometimes use git reset and use a single commit or reduce the number of commits by squashing all the "oops, typo" commits and then rebase the reset branch and force push it all. i've got a branch i need to merge later today that has 50 commits in it, and i'll probably squash those down to about 5 before rebasing and submitting a PR for the branch (most of the commits are bumping a Gemfile.lock repeatedly to test various upstream changes).

This simple model appeals to me. I presented it to my team, and a question came up. What's the best way to handle the situation when there are two feature branches that we want to deploy to our test server that aren't in master yet? Often feature branches will touch the same files so there will certainly be merge conflicts. Any ideas, besides trying to merge one feature branch into the other (which requires some communication, etc)?

I'm working in a service-oriented environment, running a slight variant on this right now, with the addition that 'qa' and 'deploy' are (under normal circumstances) fast-forward-only followers of master. Our build infrastructure uses these branch pointers to identify which builds should go to which environment. (We find it more convenient than managing tag-based builds.)

Woah, thanks for the attention! Didn't expect this rant to get popular.

Credit where credit is due. Special thanks to friends who gave me feedback on this:

Responses below:


@zimbatm

One small addition: if you share a feature branch with someone else (because you're working together), don't rebase the pushed branch. You can rebase locally but once it's pushed don't rebase until the very end when it's ready to merge. To help that final rebase, think of annotating your commits with "squashme onto ..." indications.

That's probably safest. But I find when working with few others who are OK with rebasing, it can help keep things clean.


@hugoduncan

git rebase -p

Awesome!


@nhance

We've built a tool to help make sure no one on our team makes mistakes: http://github.com/reenhanced/gitreflow

Thanks! Cool tool! And good diagram :)

Want to note an important difference. Your gitreflow encourages squash-merging. I don't, as I seek to preserve the individual commits. I do this because well-scoped atomic commits provide a phenomenal way to understand how a codebase works/evolved. (I imagine you [can] solve that problem by linking to the Pull-Request from your squash commit message?)


@fhawkinsozer thanks! some very useful aliases there. Be careful though, overusing aliases can hide away what git is really doing underneath, and potentially confuse your team members. I like magic and shortcuts as much as the next guy, but i've learned magic tends to force more communication.


@pyriku

Say you have this workflow, and have 2 servers: live and staging. Of course, staging tends to be ahead with new features that you (or your client) don't want to release yet, but a critical bug appears on live. What do you do? If you have a branch for each one of the servers, you just need to commit in there (or fork and merge) and deploy. Done.

You can still fork from a old tag, true, but it makes the history a real mess.

Not particularly, just:

git checkout -b well-named-hotfix-branch <tag>
# edit, commit, and merge into master as usual.

# And either:
# - deploy new master (after all, it's **supposed** to be deployable always)
git push live master

# - deploy your hotfix branch to production servers, if you really dont want 
#   the commits in new master. not recommended :). This means you'll have to
#   apply any additional hotfixes on this. You might want to create a new branch
#   *at that point*, penalizing hotfixes (instead of making them part of the 
#   system). Again, your mileage will vary. Always do what you find simplest
#   for your team. The real cost is communication/organization overhead.
git push live well-named-hotfix-branch:master

@tjmcewan

sane branch handling

Yeah! precisely. :) If you'd like me to add some of this to this gist, send me a diff.


@jsanders

Could you elaborate on the reasoning behind the "Don't Fork" point? What are the pros and cons of the two approaches?

Sure: forks add significant management complexity. For example, the fork owner has to make sure all changes from the mainline are pulled to his master. This can easily get out of sync as the developer's interest in the project waxes and wanes. It's best if there is just one mainline to worry about. Also, other users may seek fixes/experimental branches that haven't been merged yet. It's much easier to find those as branches in the same repo (particularly if they haven't been PRed yet) than explore the fork network, particularly when -- as a newcommer to a project -- it's very unclear what forks/devs are important to watch.

In my view, teams can reduce communication complexity by just sticking to one repo, and putting all branches there. (Also, if the repo is private, you're going to be doing this anyway :])

@rileytg see above. Happy to expand if you want more reasons?

I will say, if you don't trust your junior devs not to push onto another's branch (destroying code), or not to merge things into master (doing all sorts of bad things), you might want to re-teach your junior devs (or exchange them for new ones). Trust your team. Make it clear what is right and isn't right to do, but trust them.


@morhekil

and we're doing releases once every three weeks - branching them off master on the release day, for later hotfixes etc.

Check out the comments to @pyriku above. You might find it easier to do this with tags. If it's easier with a branch for you, keep it up!


@nicotaing thanks! I think it's ok to rebase collaborative branch, iff the other devs are comfortable. It can work really well to fix stupid mistakes.


@gubatron learn rebase. it will change your life.


@lamont-granquist exactly! I applaud your workflow, good sir :)


@breck7 thanks!


@treseder

What's the best way to handle the situation when there are two feature branches that we want to deploy to our test server that aren't in master yet?

In the end, both branches will be merged into master sequentially, so you'll have to handle conflics there. If they touch the same files, or build upon each other, you might want to rebase the later feature branch on top of the earlier. When you merge the earlier into master, rebase the later over new master to keep things up to date. (Should I make a drawing of what i mean by this?)

It's worth noting that I'm wary of why the problem is coming up in the first place. While it's not uncommon to have two feature branches that depend on each other, wanting to test them together, not sequentially, can mean trouble. What if problems are emerging (or hiding) due to the interaction of the two feature sets? It's harder to reason about two sets of changin-and-potentially-broken code interacting together than it is to reason about only one. Of course, I don't know your constraints, no rule works always, and exceptions will emerge. But always worth stepping back and wondering whether there is a simpler way.


@twhaples

Our build infrastructure uses these branch pointers to identify which builds should go to which environment. (We find it more convenient than managing tag-based builds.)

I imagine by tag-based builds, you mean builds on a specific release tag. Worth mentioning you can use a rolling tag (i.e. test). Picking between branches or tags depends on what your team is more comfortable with. Updating tags requires -f, as it is not the main use case for tags. FWIW, in your case, I'd probably use branches too.

# branch
git checkout test
git reset --hard <commit>

# tag
git tag -f test <commit>

Don't fork. Push feature branches to main repo.

Sometimes I see people forking repositories in order to issue pull-requests. Yes, you may have to do this when contributing to open-source projects you don't regularly contribute to. But, if you are a contributor, or working in the same org, get push rights on the repo and push all your feature branches to it. Issue pull requests from one branch to another within the same repo.

Is there anyway to give people push access without giving them the ability to accept pull-request? What if a junior developer not familiar with git accidentally pushes a toxic master branch? Forking gives each developer a sandbox.

Is there anyway to give people push access without giving them the ability to accept pull-request? What if a junior developer not familiar with git accidentally pushes a toxic master branch? Forking gives each developer a sandbox.

@amccloud I don't think there is. This would be nice to be able to do; but finding the right UX to avoid an access control headache is non trivial.

People generally won't be pushing to master, so it may not be a real problem. Is what you're worried about people copy-pasting git push origin master from articles on the web and blowing up master in the process? Perhaps an easy workaround to that is renaming master -> production and just having no master ;)

I prefer keep master branch clean with current release, think about users going to github repo and viewing a README that is not of the current release. Instead I use a develop branch, a part that I use the same workflow.

@amccloud gitolite allows you to mark any branch as "protected", so that only people with the appropriate permissions can push to it. This allows anyone to play with the feature branches (and maybe deploy them into staging sandbox), but then go through the review process with a more senior person to get the code sanity checked and merged. Gitlab (based on gitolite) puts a nice Github-like web interface on top of that - allowing people to create merge requests from their feature branches, and stopping them from merging their changes into master if they don't have write access to it.

@fibo cool! One point to note is that master should be release-ready at any time. One should be able to take master and expect it to work. For example, when using continuous-deployment, you don't have voluntary releases. As soon as the codebase passes the test suites, it is automatically deployed. Many webapp dev teams operate this way. So if the last release is many commits ago -- and the readme is now radically different -- perhaps try releasing more often :).

In my experience, this flow without the rebasing works well. The problem with rebasing is that you're rewriting history. If you have a merge conflict between what master has become and your branch that conflict resolution is lost into one (or worse, many) commits in your rebased branch. This means that if you did this merge wrong, well, you're going to have to hunt it down. A regular merge keeps the conflict resolution in the merge commit itself. It's easy to see if something went wrong there.

Rebasing also makes conflict resolution more cumbersome. You must resolve conflicts every commit instead of at the end. This means that if you may have to resolve conflicts in the same file multiple times without the context of what you did in your branch later on in the history. It makes those more error prone and more tedious. This obviously exacerbates the issue I mentioned in my previous point.

I've also had absolutely no problem bisecting with gnarly "branch bubbles". Yes, it's a little hard to read, but I don't often need to read it, and when I do, I can figure it out.

In short, it's less work and less dangerous to not rebase before merging, and the only benefit I've actually seen from rebasing before merging is a nicer looking history.

That said, I do rebase often, but typically it's -i and on my previous base simply to clean up commits before pushing.

No back merges. See my model on Dymitruk.com. You need more to make release candidate and see how things fit along the way with an integration branch.

Small question though, if you use Early Pull Requests to discuss subjects, how would you easily rebase a branch since it has already been pushed to Github ?

We're not using github, but a bare git workflow and trying to implement this, but we have an issue with wanting bug fixes from master in our feature branches and then after rebase and merge not have it appear in the history of the feature branch or generate any nasty merge conflicts. Could this be done?

If my question was unclear, maybe this is more clear? http://superuser.com/questions/653812/pulling-changes-into-remote-tracking-feature-branch-while-keeping-simple-branchi

@zimbatm I have been using Git's autosquash feature for automatically annotating commits that need to be squashed into the original commit. Here is a really good blogpost on it: http://chrismar035.com/2013/04/04/better-pull-requests-with-autosquash/. The author of that blogpost has also published a demo video: http://asciinema.org/a/3467.

--no-ff preserves feature history and easy full-feature reverts

Can you please explain how to revert a feature branch merge, fix issues on it and remerge on master ?

Thank you

The images are gone. The first image (flow) was taken from Zach Holman talk. I don't remember what are the remaining images (headless, merge bubbles). Does anyone know where to find them and restore this post?

flow:

flow

Found this on one on pandawhale.
Merge bubbles good vs bad

@shamrin, @eboto, Thanks!

I've found other images in google cache and fixed the article in my fork — https://gist.github.com/shvechikov/de39c99574488a3de12a/

Ah, sorry guys. My server got taken down. Thanks for fixing @shamrin, @eboto, @shvechikov. I merged your changes. :)

In the future, feel free to email me, as gist comments do not notify me.

# optional: push your branch for discussion (pull-request)
#           you might do this many times as you develop.
git push origin my-new-feature

Maybe you have to note that after first push, and after rebase, the command would be:

git push -f origin my-new-feature

@Elyahou: indeed, without the --force / -f option, it won't work (assuming there was any development in master)

Don't forget to cleanup your repo after you merge your feature branch into master:

git branch -d feature-branch-name
git push origin :feature-branch-name

We recently released a gem that automates this workflow. Please check out http://tech.lovewithfood.com/blog/2014/01/19/git-pretty-accept-accept-pull-requests-the-pretty-way. We'd love to hear your feedback :)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.