Skip to content

Instantly share code, notes, and snippets.

@nzakas
Created November 24, 2013 23:01
Show Gist options
  • Save nzakas/7633640 to your computer and use it in GitHub Desktop.
Save nzakas/7633640 to your computer and use it in GitHub Desktop.
Versioning in a repo

Versioning in a Repository

Lately I've been doing a lot of thinking around versioning in repositories. For all the convenience and ubiquity of package.json, it does sometimes misrepresent the code that is contained within a repository. For example, suppose I start out my project at v0.1.0 and that's what's in my package.json file in my master branch. Then someone submits a pull request that I merge in - the version number hasn't changed even though the repository now no longer represents v0.1.0. The repository is actually now in an intermediate state, in between v0.1.0 and the next official release.

To deal with that, I started changing the package.json version only long enough to push a new release, and then I would change it to a dev version representing the next scheduled release (such as v0.2.0-dev). That solved the problem of misrepresenting the version number of the repository (provided people realize "dev" means "in flux day to day"). However, it introduced a yucky workflow that I really hated. When it was time for a release, I'd have to:

  1. Manually change the version in package.json.
  2. Tag the version in the repo.
  3. Publish to npm.
  4. Manually change the version in package.json to a dev version.
  5. Push to master.

There may be some way to automate this, but I couldn't figure out a really nice way to do it.

That process works well enough when you have no unplanned releases. However, what if I'm working on v0.2.0-dev after v0.1.0 was released, and need to do a v0.1.1 release? Then I need to:

  1. Note the current dev version.
  2. Manually change the version to v0.1.1.
  3. Tag the version in the repo.
  4. Publish to npm.
  5. Change the version back to the same version from step 1.
  6. Push to master.

Add on top of this trying to create an automated changelog based on tagging, and things can get a little bit tricky.

My next thought was to have a release branch where the last published release would live. Essentially, after v0.1.0, the release branch remains at v0.1.0 while the master branch becomes v0.2.0-dev. If I need to do an intermediate release, then I merge master onto release and change versions only in the release branch. Once again, this is a bit messy because package.json is guaranteed to have different versions on master and release, which always causes merge conflicts. This also means the changelog is updated only in the release branch. This solution turned out to be more complex than I anticipated.

I'm still not sure the right way to do this, but my high-level requirements are:

  1. Make sure the version in package.json is always accurate.
  2. Don't require people to change the version to make a commit.
  3. Don't require people to use a special build command to make a commit.
  4. Distinguish between development (in progress) work vs. official releases.
  5. Be able to auto-increment the version number (via npm version).

How about you? How do you deal with versions in your repository?

@mikeal
Copy link

mikeal commented Nov 25, 2013

% cat /Users/mikeal/.bin/release

npm version minor && npm publish && npm version patch && git push --tags && git push origin master

I use that release script. Every other minor release only exists in master and is never published on its own so the current HEAD is a single version ahead of the latest published release.

@twolfson
Copy link

I keep master as my stable branch; it will always have the latest proper git tag and package.json version. If I am developing features on the side, I leave them in a branch until they are ready to be released.

If people are eager to contribute to the development branch, they can use a git reference to the branch

"dependencies": {
  "eslint": "git://github.com/nzakas/eslint#dev-1.0.0"
}

or git clone + npm link.

I would also add that small, frequent releases are another way around this problem; no large features build up and branches have very short life cycles.

@tfnico
Copy link

tfnico commented Nov 25, 2013

We've been doing a similar approach in Java/Maven world for a while, using the -SNAPSHOT version suffix instead of -dev. I'm not sure if this is a good fit for npm though, as -dev probably doesn't have any semantic meaning there.

One thing struck me as odd in your approach: It sounds like you base the development of v0.1.1 off master, while it should be based off the v0.1.0 release.

Once you decide v0.1.0 needs a fix release, create the v0.1.x release branch based on the tag v0.1.0, and set the packaging.json version to be v0.1.1-dev. This will leave master in peace (staying v0.2.0-dev).

Once you merge fixes from v0.1.x, you'll just have to ignore any conflicts you get in the version information.

All this aside, what I would find ideal would be to keep version numbers like this outside the source code, and use tags for storing this information, then generate the necessary information inside package.json at release/build time. Here's an example of heuristics one could use: http://hegsti.blogspot.de/2011/03/rethinking-automated-release-management.html

@nzakas
Copy link
Author

nzakas commented Nov 25, 2013

@domenic - In my experience, people blindly install from GitHub and when I ask them what version they're using, they say, "the latest." I usually have to dig in to figure out if they installed from npm or from GitHub. :(

@mikeal - that's interesting. Do you then only publish minor releases?

@isaacs
Copy link

isaacs commented Nov 25, 2013

Here's how I do it for "simple" modules, where I'm unlikely to have multiple versions in development at the same time. (Note, this is almost all my programs, almost all the time, including npm.)

# work work work, pull, merge, etc.
git commit -a -m "merge all teh codez"

# This bumps the package.json version, and does a commit and tag
# Since I have sign-git-tag=true in my npmrc file, it also gpg signs
npm version patch  # or minor, or major

# This is aliased to `gps` in my bash env
git push origin master --tags

# publish!
npm pub

master is often a few commits ahead of the latest release. I see basically no downside to this. If you want the latest release, it's easy to find by grabbing the latest tag.

The package.json version reflects the latest release, not the forthcoming release. Since I don't know at this point whether the next release will BE a major or minor or patch bump, it makes no sense to put that into the package.json anyway.

In the rare occasion that I do know that a bigger version bump is coming (eg, I'm starting on a rewrite or something), I'll do something like the following:

git branch v0.2
# Again, this updates package.json, and creates a signed commit
npm version 0.2.0-alpha
vim the-codez.js
git commit -a -m "zomg codez"
# tag and bump on the branch, so that
# git checkout v0.2 gets the v0.2 code
npm version 0.2.0
git checkout master

# The --no-ff is optional here, but it it was a big enough change to
# go through the added ceremony, I probably want the merge-commit
git merge --no-ff v0.2

Contributors to my libs are not allowed to change the package.json file version, unless they're signing and publishing. (I usually just back out any such changes that they do make, which are typically well-intentioned.)

I don't require that contributors do any fancy tagging/branching/etc, or that they use npm to manage the package.json, or basically anything else. I want their contributions, so I think it should be easy for them to contribute.

In my opinion, if you find that you have to branch for releases often, or that your package.json is painfully out of date with the actual facts of your project, then there's a good chance that either your releases are too big, or your project itself is too big.

@isaacs
Copy link

isaacs commented Nov 25, 2013

Regarding your merge conflicts, it's actually really easy to resolve this. After all, we have this "problem" in Node every time we merge from stable to master. Do something like this:

# Say that master is v0.2.2 and the v0.1 release branch has v0.1.11
# Get the bugfix from v0.1, but not the package.json bump.
git checkout master
git merge v0.1
# conflicts in package.json
git checkout master -- package.json
git commit

With Node, I basically never want to pull in changes to deps/{v8,uv,cares}, or src/node_version.h, so I just merge, get the conflict, resolve by explicitly checking those files out from master, and then moving on.

Merge commits aren't something you should be afraid of. They are cracks on the sidewalk. Yes, you don't want to go around smashing up the sidewalk, or walk over a mess of gnarly cracked rocks, but a few reasonably small ones are fine, even if they happen on a regular basis.

@cschram
Copy link

cschram commented Nov 25, 2013

It seems to me like you wouldn't worry about the version in package.json until you actually need to publish a new version. If someone isn't using a specific tagged version then you're going to need to know what revision id they're working off of anyways if it matters to you at all.

@onury
Copy link

onury commented Dec 22, 2017

Old post but anyway...

As Linus Torvalds talks and this GitHub guide explains it;

one rule: anything in the master branch should always be deployable.

Because of this, it's extremely important that your new branch is created off of master when working on a feature or a fix. Your branch name should be descriptive (e.g., refactor-authentication, user-content-cache-key, make-retina-avatars), so that others can see what is being worked on.

@gmoz22
Copy link

gmoz22 commented May 10, 2018

Indeed, sometimes you think you're just going to work on a patch and end up with a minor version, or from a minor version having to push out a major. You shouldn't have to worry about which type of version you will be pushing next.

Here's what I started doing:

  1. Start my project at 1.0.0 in package.json
  2. Initial dev work
  3. Push to develop branch
  4. Push to master branch
  5. Tag as v1.0.0
  6. Dev
  7. Push to 'feature/name-of-feature' or 'bug/name-of-bug' branches
  8. Merge pull request of feature and bug branches into develop
  9. When it's ready for release, determine the version number (1.0.1 or 1.1.0) and update package.json
  10. Push package.json to develop branch
  11. Merge develop into master
  12. Tag as v1.0.1 or v1.1.0
  13. Repeat from 6)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment