In this post I will to demonstrate how you can contribute to an Open Source project hosted on GitHub. I previously blogged about how you can make and apply patches, which is certainly one way. But on GitHub there is an even cooler method, one that is also less work on the project maintainer.
For the sake of this tutorial I shall pretend that there is an issue with a project by Matt Galloway, then I’ll fork his project. After fixing the issue I will push my changes online to my fork and finally notify Matt about these changes via a so-called “Pull Request”.
A prerequisite for this tutorial is to have your GitHub account and local git set up previously.
Step 1 – We have an Issue
Your first thought should be to document that in GitHub’s excellent bug tracking system. Each Open Source project there has a list of Issues (which are the bugs and feature requests). I found two things to improve in the project and so I added two issues.
We could hope for the project’s owner to fix that for us, but – being smart developers – we can also proceed to fix the issues ourselves. From the get go we won’t have writing access to the GitHub repository (unless the owner gives it to us, which is rare). You can see that this is the case if you only see the HTTP and Git Read-Only options on the repo URL:
The usual method is to make a copy of this project in our own area on GitHub where we CAN write to. This copy is what they call a “Fork”.
Step 2 – Stick a Fork in it
To make a fork you klick the “Fork” button at the top right of the project main page. After some “hardcore forking action” you end up on your own copy of the project. And there is now an extra option “SSH” telling you that you have read and write access to that.
A fork is a complete copy of the original project including all previous history. The reason for this that with Git every copy of a repository is always a full clone, so that each clone can also serve as a backup. This illustrates the distributed nature of Git, as opposed to centralized Source Code Management (SCM) systems like Subversion which has a central server.
Step 3 – Clone Wars
To work on our copy of the project we need to clone it to our hard disk. You can use the excellent official GitHub app but personally I prefer to work in terminal. I open terminal, go to a place where I want the copy of the project to be and issue the command:
git clone firstname.lastname@example.org:Cocoanetics/ios-library-with-resources.git
The URL used is the SSH one I copied from the web page. This results in a ios-library-with-resources folder appearing in the current working directory.
Step 4 – Fix-Tours
Now we can do our changes. So I open the Xcode project.
The first thing I am fixing for this example is to add the two sub-project products as dependency so that they get automatically built if somebody builds the containing app.
You see an M (for Modified) appear next to the root of the project tree which means that the project file has been modified. Each such fix should become one commit. So we right-click on MyLibraryTest, Source Control, Commit Selected Files. This opens a dialog that shows which files were modified and prompts for a commit message.
Since we fixed issue number #001 with that we also add this tag, because later we will see that GitHub conveniently links this commit with the issue via this number.
This committing basically saves the current state in your local Git repository (your clone) and adds your message to that. Actually it did a bit more than that, because Git has a an extra step called “staging” before committing.
In staging you select the modifications you want to be part of the commit by Adding them to the staging area. The second part then does this saving. If you do this via the Xcode interface you don’t need this staging phase because you specifically selected the files to commit already via the project navigator.
The second issue to fix is to change a build setting on the sub-project.
This time the M appears next to the MyLibrary.xcodeproj which really means the project.pbxproj file contained in the MyLibrary.xcodeproj bundle. (Remember that a bundle is a folder that looks like a file)
This time let’s do the committing on the command like, like a real pro. First let’s see the status.
git status # On branch master # Your branch is ahead of 'origin/master' by 1 commit. # # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: MyLibrary/MyLibrary.xcodeproj/project.pbxproj # no changes added to commit (use "git add" and/or "git commit -a")
This shows that we are ahead of our fork (origin/master) by 1 commit, which is the previous one we did. And it shows that the project file for the sub-project was modified. We issue a “git commit -a” which stages all modified files for the commit and immediately prompts us for a commit message in the currently set up text editor.
There is a second shortcut that GitHub recognizes besides the #002 tag. If you write “closes” or “fixes” in front of the hashtag it not only references the issue, but also marks it as closed. How convenient!
“Set Skip Install YES for resource bundle to prevent copying that into Archive. closes #002”
I type my message into the VIM that opened and save/exit via ESC, :wq, ENTER.
Now we have two local commits (git log to see them), next we need to push them to our online repository. We just had modifications to the project file in these two examples, but – trust me – it works just as well for code changes.
Step 5 – Push Up
When we cloned the project to our local hard disk Git was nice enough to automatically configure the location where it came from as so called “Remote”, the default name is “origin”. You can have multiple remote places set up and choose which to push.
The command for us is:
git push origin master
“origin” is the remote I told you about, “master” means that you are pushing this to the master branch, which is the default branch.
To check if our changes have really arrived online I like to check the commits view on GitHub. And indeed they have.
We could end here and many people do. They just make a copy of an Open Source project for the case that the original repository disappears. But since we are playing nice we want to offer our improvements to the original project’s owner.
Step 6 – I just called to say “Please Pull Me!”
Just like for the fork we find a “Pull Request” button at the top right of the GitHub website of our fork. Push it and you get a nice overview of what is contained in the request.
There are some advanced options regarding milestones and assignments, but we’ll ignore these for now. You’ll notice that the pull request automatically generates a new number, #3 in our case. So you can not only reference issues with your commit messages, but the same thing is possible for pull requests.
The final step is not for us, but for the original project’s owner to perform.
Step 7 – Accepted!
As owner of a GitHub repo it is up to you whether you want to accept the changes into your repository. If you kept your commits small and – for code changes – added some good comments to aid understanding then the owner will be happy to accept them. It saves him from doing the work that you did and all he needs is to push the green merge button.
The button to accept the changes is only green if the merge of the code can be performed automatically. If the author did some changes on his own that conflict with yours then the button is gray stating that the changes cannot be merged automatically. In this unfortunate situation either one of you needs to pull the other’s changes and manually resolve the merging conflicts. But this is a topic for another day.
I suggest you find yourself a a GitHub project to contribute to and try out this process to shed the timid feeling. It really is quite idiot proof.
People put their projects on GitHub not only so that you get free source code to use in your apps. They also hope that other developers might find and fix bugs. And every once in a while you see a pull request that makes you think “OMG I’m not worthy!” because those are the gems that really teach you something.
And this is why they call it collaborative coding.