Contributing to Open Source Git Repositories in Go

Photo Credit: t1ggr

Dependencies. They’re knotty, and there really is no straight-forward, one-size-fits all solution.

Dependency hell is a colloquial term for the frustration of some software users who have installed software packages which have dependencies on specific versions of other software packages. —Wikipedia

I recently saw a comment on GitHub:

When I run [the tests] they always pass, but that is because they import the package from [the package path] and not my local fork.

What’s going on here?

In order to illustrate the problem, imagine the following hypothetical scenario.

Hellø, Wørld

You’re using my sophisticated library, which translates English into Norwegish.

    package main

    import (
            "fmt"

            "github.com/kytrinyx/norwegish"
    )

    func main() {
            fmt.Println(norwegish.Translate("Hello, World!"))
    }

You realize that the output is, incorrectly, Hellø, Wørld, so you fork my project in order to fix it so that it uses the correct translation, Hellø Vørld.

You go get your fork, and run the tests. As expected, they’re passing.
Then you add a test for the Vørld case, and (as expected) it fails.
Finally, you change the code to fix the bug and run the tests, but—no dice—they’re still failing.

Import Paths and $GOPATH

If you look closely at my test suite, it tests the package from the outside, black-box style, importing the norwegish package using the github.com/kytrinyx/norwegish import path, just like you would in any other application.

In other words, the tests are using the code which you already have on your system at $GOPATH/src/github.com/kytrinyx/norwegish.

The tests in your fork are still importing the original package, which doesn’t have the fix. You could manually change the import path, but that would be tedious and error prone.

The key is to use multiple remotes.

Mine, Yours, Origin, Upstream

Everything works out if you have both the original and your fork living on your system in the directory corresponding to the original’s import path.

When you first go get or clone a repository, you will have a single remote, with the default name origin.

    $ cd $GOPATH/src/github/kytrinyx/norwegish
    $ git remote -v
    origin  https://github.com/kytrinyx/norwegish (fetch)
    origin  https://github.com/kytrinyx/norwegish (push)

You can add new remotes at will. This is good when you want to try out some random person’s patch on a project:

    $ git remote add rando1 git@github.com:random-person1/norwegish.git
    $ git remote add rando2 git@github.com:random-person2/norwegish.git

Then you can git fetch rando1 and check out branches based on their code.

Handy.

In this case, what we want is your fork as the secondary:

    $ git remote add fork git@github.com:you/norwegish.git

Now you’ll have two remotes, origin and fork:

    $ git remote -v
    fork    git@github.com:you/norwegish.git (fetch)
    fork    git@github.com:you/norwegish.git (push)
    origin  https://github.com/kytrinyx/norwegish (fetch)
    origin  https://github.com/kytrinyx/norwegish (push)

With this setup, the tests will pass, because your fix lives at the correct import path.

Side Note

I like to use the remote name origin for my version of a project, and upstream for the upstream that I forked it from, but I’m somewhat obsessed with naming and consistency. If you have unhealthy obsessions too, you can remove and re-add remotes:

    $ git remote rename origin upstream
    $ git remote rename fork origin

Branching and Pull Requests

Assuming that the kytrinyx/norwegish repo is at origin, and your fork is at fork, you can use all the usual branching shenanigans for creating a pull request:

    git checkout -b fix-capital-w-bug
    git commit add -m "Translate capital W to capital V"
    git push fork fix-capital-w-bug

… and then easily reset master so you don’t get out of sync:

    git fetch origin
    git reset --hard origin/master

It turns out this is not too complicated after all.

About Katrina Owen:

Katrina is obsessed with refactoring, idiomatic code, and naming things. She is the creator of  Exercism.io, an open source application that combines practice problems with crowd-sourced mentorship, which is written mostly in Ruby and Go.

July 24, 2014