32

A discussion has come up at work and I want to get the opinion of other programmers.

During my time in college, we only used SVN for sourcecontrol. Everybody commits to the same server and all changes are pulled to all machines. In this system, it's easy when you should push your changes: when you're done with your changes. However, this makes merging changesets by multiple developers a complete nightmare.

However, for the past year or so that I've been programming professionally, we've been using Mercurial for source control. At first, I was using it like SVN: work on a featur, commit and push in one go, but only when it's finished.

Gradually, my habits started to change. I learned about branches, how commits could be a draft, public or secret and even used a USB stick as a central repository. My commits became smaller, but they increased in quantity. Instead of committing and pushing in one go, I would make commits for every change in a named branch.

Here's what the repository for one my personal projects looks like:

Repository layout

I see a couple of advantages in this approach.

  • Small commits mean they are focused on a single subject. Every commit gets a one-line description of the changes, which makes them easy to follow even after weeks of inactivity.

  • Named branches keep you on topic. If you're working in the "refactor-text-rendering" branch, for example, you shouldn't make changes to the math library, because that's not related to the branch's topic.

However, the lead developer is annoyed by my methodology. He feels that the small commits clutter up the repository's history. His preferred methodology is to work in a patch. When the feature is done, he merges the commits and pushes them in one go.

So, what do you prefer? A flow of consciousness history of commits or a patch of changes every now and then? Or something else entirely?

3
  • 1
    have you looked into Compressing many commits into fewer, but larger, commits? You do your local git space like you want, but before you push it to the common one (if that is the model you are using), you compress it into a larger commit?
    – user40980
    Aug 2, 2013 at 18:52
  • 1
    Sorry but I have to -1 this because there are a lot of duplicates out there. No research effort shown. Aug 2, 2013 at 18:57
  • 1
    In my opinion, especially with git, commit as much as you want locally but squash and make atomic commits back to the repo that are task complete. IE, non breaking functional code. That's my school of thought and far from the only one.
    – Rig
    Aug 2, 2013 at 18:57

6 Answers 6

28

I went looking for book references for this, but sadly cannot find them.

In general, I believe that small commits are widely considered to be best practice. Or more accurately: the smallest commit that represents a cohesive feature that is in a build-able state.

Why?

  1. Small, frequent check-ins prevents you from losing work in case of hardware failure, earthquake, zombie apocalypse.
  2. Your peers can get your changes sooner than if you wait until the end and make one huge commit.
  3. If a feature needs to be rolled back, you can rollback one changeset not "half" of a changeset.
  4. Smaller check-ins mean less area of change, which means less likely conflicts with other people's work.
6
  • 9
    This is a common misnomer: Smaller commits will have no discernible affect on the quality of your source in the event of a zombie apocalypse. Aug 2, 2013 at 18:58
  • 13
    I think you meant "misconception", not "misnomer". Aug 2, 2013 at 20:46
  • 6
    They do, what if a commit in your repository initiated the zombie apocalypse? You need blame and rollback quickly AND you don't want to lose data Aug 3, 2013 at 6:42
  • 1
    With frequent commits and a battle axe on my wall, I'm completely ready for the zombie apocalypse.
    – Esqarrouth
    Oct 1, 2017 at 11:31
  • 1
    One more point - Smaller commits help others to easily find specific changes. Example. We have automation tests which hit only server-x. We want to provide a run time parameter to select which server to hit. We will have to modify 10 test classes to use the run time parameter. Commits - We could have 1 commit for simply accepting the param and making it available to tests. Then, one or more commits for updating the tests to use this param. That way, it becomes easy for other people to figure out what that param should be. Better yet, put that param in a read me file and add it to your code.
    – MasterJoe
    Oct 16, 2019 at 1:43
10
  • Never deliberately make a commit that will leave the repository in a broken state, even if you intend to shortly make another that will fix it.

  • Prefer small commits to large commits. When tracking down the source of some issue by chopping through the repository, it is easier to track down the source of a problem if the steps are small; and it is easier to quickly understand the changes in a small commit.

  • Prefer a single commit for a single feature, fix or other logical unit of work; avoid "partial" commits that introduce changes that are not functional without further commits, and avoid compound commits that contain several unrelated changes. If you are making a stylistic change that does not change functionality, commit that separately. All this increases the likelihood of leaving the build unbroken, and makes it much easier for another person unpicking codebase issues to understand the sequence of functional changes that took place and to grasp each individual change quickly.

  • If you are working on a very complex feature or refactor that cannot be made safe or useful to commit while partially written, work on a private branch, commit to that often, try to stay up to date with trunk, and if at all possible, when finally merging to trunk, do so in a manner that leaves future maintainers a way to get at the private branch's history.

So, to sum up: always aim to make the smallest commit you can that is both necessary and sufficient to complete a single logical unit of work.

7

I'm almost certain there is a decent amount of opinion in this space, but I tend to follow some rules from which tend to dictate the size of commits, some thoughts about this:

  1. Single commits should be "rollback-able", does your feature impact the working ability of the system if the commit is rolled out? Commits should be cohesive units, and those cohesive units should "map" nicely (as possible) into the feature.
  2. Branch privately where possible to shield the "ugly-ness" from your team, ultimately your commits, large or small, shouldn't impact their work on Feature B.

I think if you follow both of those rules of thumb, your commits may or may not necessarily be "large" or "small" (relatively speaking). Sure small commits are nice, and sometimes easier to reason about, but if they're all strongly coupled together, it might make it a bit tougher to deal with. I am ok with a "large" commit (within reason) as long as it's reduced to it's smallest cohesive functional grouping.

7

Take your commit history, for your personal project as an example. There's 13 commit messages for 1 day (ie. the "2 days ago"). Now imagine what that history would look like for the same time period if 50-200+ developers worked on the project in the same manner. That's 650-2600 commit messages a day. The commit history for a week would intractable for a human.

So it depends on the team size: On small teams, it's good detailed information. As the team gets larger it quickly explodes into a mess of noise, making the history functionally useless.

1
  • 17
    I'd expect teams of 200+ developers to break their project into multiple modules, each of which would have its own repo. I'd also expect them to be committing on multiple different branches, which means you can ignore the detailed history for a given branch if you aren't interested in it. But you're right: If you're on a spaghetti project with a large team, the history will be just as messy as the code.
    – Ant
    Aug 2, 2013 at 20:42
3

I prefer your approach when using Mercurial. Modern source control systems like Git and Mercurial are built around the idea that fast commits enable you to commit frequently. You shouldn't save up a large number of changes and commit them in one go.

Git allows you to condense multiple commits down into a single commit using its "squash" feature, but this isn't provided by default in Mercurial. Changing commit history goes against the Mercurial ethos, which states that history should be immutable. However, if you do want to try and replicate Git's squashing feature - so that you can continue to use your tools properly whilst still adhering to the nutty behaviour of your lead dev - this question might help:

https://stackoverflow.com/questions/1725607/can-i-squash-commits-in-mercurial

3

There are valid arguments either way, on the one hand, source control isn't a backup mechanism, you should use shelving, a local repository or plain old backup to make sure your work is safe until committed to production. On the other hand, if a set of changes represent a complete and discrete bug fix, feature, etc. then it seems reasonable to commit them when ready. In the future, reviewing smaller, more "modular" commits may prove easier rather than wading through a revision with 100s of changed files.

Also, trying to maintain purity in a source control log seems like a losing game in the long run, and is focused on managing an artifact of the development process rather than the software itself.

1
  • 4
    All valid points, but with regards to the last one: nobody likes to write documentation but everybody likes to read it. Writing a single line in the log for every commit doesn't take much time and serves as a paper trail for how you were thinking and where you were going.
    – knight666
    Aug 2, 2013 at 19:12

Not the answer you're looking for? Browse other questions tagged or ask your own question.