Contributing to git projects on github

I spent some time this week helping a colleague learn the ins and outs of Git rebase. He mentioned that he could not find a git introduction that summarized the basic workflow you might use when contributing to an open source project on Github. This article attempts to fill the gap.

Note that there are many possible ways to interact with git and github and none of those ways are canonically correct. The steps here will help a newcomer to git or to collaborative editing on git. I outline the steps in short form first, then summarize alternatives below.

  1. Fork the remote project on github.
  2. Clone your new fork.
    git clone ssh://git@github.com/[yourname]/[reponame]/
    cd [reponame]
  3. Add a shortcut to upstream.
    git remote add upstream https://git@github.com/[developername]/[reponame]/
  4. Optionally choose a branch to hack on.
    git checkout development_branch_name
  5. Create a local copy of that branch.
    git checkout -b working_branch_name
  6. Hack.
  7. Commit your changes
    git add [-p] filenames
    git commit

    Repeat the hack,add,commit steps until your commits look the way you want them to.

  8. Grab any commits that were added upstream while you were working.
    git checkout development_branch_name
    git pull upstream development_branch_name
  9. Rebase your work onto to the end of the development branch.
    git checkout working_branch_name
    git rebase development_branch_name
  10. Push your commits to your github fork for the world to see.
    git push origin working_branch_name
  11. Make a pull request on github. Pick development_branch_name as the one you want your code pulled into, and working_branch_name as the one you are pulling from in your repo.
  12. Oh nos! The upstream devs didn’t like your changes. That’s fine. Repeat the hack/commit step as needed. You may
    be able to just append commits to your previous commits. If you need to alter your history (e.g. because they don’t like how one of your commits looks), check the man pages for git commit --amend and git rebase -i.
  13. Once you have the working_branch in a state you like, repeat the remote pull and rebase steps to ensure your commits “look like” they happened at the end of the remote developer’s branch.
  14. Push your changes to your github fork, forcing an overwrite of any existing changes.
    git push -f origin
  15. Issue another pull request on github; your old pull request will be updated to the new working branch.
  16. Your code has been accepted upstream, congratulations on your contribution to an important open source project!

Notes

  1. This represents a branch the upstream developer has pushed to their remote repository. The branch chosen really depends on where you want to develop. Some projects do all their development on master. Others push a remote branch named ‘devel’ or branches named after the feature being developed on that branch. Either way, your ultimate goal is to have your commits added to this branch on the remote repository.
  2. It is possible to do your development on a single branch and rearrange commits as needed. However, branching is free in git, and storing your changes on a different logical branch will really help you identify where everything fits together.
  1. Each commit should contain one logical change.
    A commit message should not contain the word “and”.
    If you have been working on two logical changes at the same time, you can index only relevant hunks using git add -p.
  2. You’re checking out the original branch here. You don’t want to pull all the foreign commits into the branch you’ve been developing on; that branch should only contain your commits at this time.
  3. This is the same as “moving” all your commits
    to the end of development. The idea is to have your commits as a “tail” that all come after the upstream changes. Upstream will love this because they can do a fast-forward merge of your commits. Hopefully
    there aren’t any conflicts. If there are, resolve them so that your commits can apply cleanly. You’ll want to review the rebase documentation before continuing. Also, use three way merging, it makes life so much easier.
  1. There is a philisophical debate as to whether it is acceptable to rewrite public history, as Dieter has discussed in detail. In this case, since you have communicated (via the pull request) an intent to rewrite history, it’s not only acceptable, but desirable.Most upstream developers will consider it good form to edit older commits
    so that they reflect what the history “should have” looked like so that people reading your history can understand it.

10 Comments

  1. Dieter says:

    > There is a philisophical debate as to whether it is acceptable to rewrite public history, but that is fodder for another blog post.

    I can save you some typing, as I wrote a post about that:
    http://dieter.plaetinck.be/why-rewriting-git-history-and-why-commits-imperative-present-tense.html

  2. Whitney says:

    Slightly confused by #10…

    In order to push our branch changes into our repo, shouldn’t we use
    “git push origin working_branch_name” ? Am I getting this backwards?

  3. #2 & #3: the url of github is github.com, not github.org

  4. Chad Whitacre says:

    I’m confused by steps 4 and 5. What’s the difference between a development branch and a working branch?

    Also, on step 4 I get:

    $ git checkout packaging_fix
    error: pathspec ‘packaging_fix’ did not match any file(s) known to git.

    I worked around this like so:

    $ git branch packaging_fix
    $ git branch
    * master
    packaging_fix
    $ git checkout packaging_fix
    Switched to branch ‘packaging_fix’
    $ git branch
    master
    * packaging_fix
    $

    • Chad Whitacre says:

      I think I get it now. The development_branch_name is a branch that exists in the other person’s repository (as well as mine). The working_branch_name is a branch unique to my repository. Do I have that right?

      • dusty says:

        That’s correct. I have altered the explanation a bit. Sometimes you may have to git checkout upstream/some_branch. If you do this, you have checked out a commit on your local repository that is not attached to a branch. You can then attach it to a local branch using git branch some_branch.