Go has a strong emphasis on “don’t break things” and so they somewhat discourage the idea of a “v2”. However, sometimes you legitimately need to make a breaking change to your API even though it’s hit production.

Here’s what you need to know to not have a bad time:

Go Version Bumping Cheat Sheet

  1. Update the package name in go.mod to have a /v2 (or /v3, v4, etc) suffix, and commit it.

    module github.com/example/project/v2
    
    go 1.12
    
  2. Start with v2.0.0-pre.1. If you get everything right on the first try you can delete it and retag it as v2.0.0 without any problems.

    # add the git tag
    git tag v2.0.0-pre.1
    git push --tags
    
    # remove it
    git tag -d v2.0.0-pre.1
    git push origin :v2.0.0-pre.1
    
    # add the v2.0.0 proper when you're ready
    git tag v2.0.0
    git push --tags
    
  3. Fetch your package from a dependency. Use @<tag> or @master, not @latest.

    export GO111MODULE=on
    go get -u github.com/example/project/v2@v2.0.0-pre.1
    
  4. Update import paths in your depedencies, README, GoDoc links, and other documentation.

    import (
       "github.com/example/project/v2"
    )
    

    (note that the import name stays the same)

    [GoDoc](https://pkg.go.dev/github.com/example/project/v2)
    

    I recommend using sd to do this, because it makes it a snap:

    sd 'github.com/example/project' 'github.com/example/project/v2' go.* *.go */*.go */*/*.go
    sd 'github.com/example/project' 'github.com/example/project/v2' */*/*/*.go */*/*/*.go
    
  5. Run go mod tidy to be sure no lingering non-v2 references remain.

More details below, including cache-busting and tag / version debugging, if you need it.

Have a nice day! :D

Start with v2.0.0-pre.1

That’s called a “psuedo-version”.

Typically before you push straight into a backwards-compatibility guaranteed v2.0.0, you want a little wiggle room to play around and make sure everything works. Using a psuedo-version is a good way to do this.

@latest caches, @master does not

Even after you’ve updated your project correctly, a go get like this may still fail for a while due to caching.

go get -u github.com/example/project/v2@latest

If you go get like this, the current master will be force-checked and you’ll get the expected (or at least accurate) results.

go get -u github.com/example/project/v2@master

Additionally, if you find that @v2.0.0-pre.1 isn’t working for you, it may have been negatively cached. Bump to @v2.0.0-pre.2 and try again. Remember: You’ll never run out of psuedo-versions and you can always delete them once you publish the real, working v2.0.0. :D

Debugging: Listing versions

Sometimes you just need to know what Go is seeing. Here’s how you can do that:

go list -m -json git.rootprojects.org/root/go-gitver/v2@master
{
    "Path": "git.rootprojects.org/root/go-gitver/v2",
    "Version": "v2.0.2-pre.1",
    "Time": "2020-10-10T22:33:22Z"
}

Alternative Option: A ‘v2’ Directory

If you want to simultaneously maintain v1 and v2, or to build v2 as a wrapper around v1 (or vice-versa), you can use a v2 directory instead of bumping your go.mod to v2.

github.com/example/project
├── go.mod
├── sprocket.go
└── v2
   └── sprocket.go

In this case you should keep your go.mod plain, without the v2 suffix:

module github.com/example/project

go 1.12

In this case you can use both v1.x and v2.x tags on your repo and go get will fetch the correct latest one based on the v2 suffix.

See an example

Take a look at go-gitver, which has a v2 go.mod.