Git workflow for modern projects

After four years of a break for doing some management stuff and other non-development tasks I have decided to get back into coding (or at least to start thinking about getting back to it). First thing was to prepare development environment in general and refresh git flow knowledge in particular.

This article is a mixture of both my old article about Git+Gerrit workflows and Git workflow for Yii 2 contributors article. I assumed minimalist approach to make it possible to use this new workflow for virtually any Git-based project. Thus, I have purged most of, if not all of the things that are specific to Gerrit and Yii2.

Before continuing with this article, make sure that:

In addition, you may want to change the Git Bash’s prompt to something, you are more familiar with.

Note, that this article is written from the perspective (and addressed) to a total GitHub newbies. If you have any experience with this tool, you may find a lot of sentences here obvious and unwanted.

There’s a handy summary checklist at the end of this article.

Initial steps

Please tell me who you are?

If you haven’t done so before, introduce yourself to local copy of git:

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

It could be a good idea to use the same values as in GitHub. However, keep in mind that, while this has nothing to do with security or authentication, it has a lot of with your privacy. And by default, GitHub will block all pushes that would reveal your private email address.

To resolve this problem, go to email settings at GitHub and either set your emails as public or uncheck “Block command line pushes that expose my email” checkbox. Alternatively, you may use a different or a fake emails address (i.e. [username]@users.noreply.github.com).

Fork the repo

Do this only once per each project you want to contribute to.

For obvious reasons, if you want to propose changes to someone else’s project you can’t push theses changes directly to that project’s repository. If dozens of developers would do such thing in the same time, it would make that repository (and the whole project) bloated with source code pieces coming from different sources.

Thus, to avoid this, you have to fork that project’s repository to your own GitHub account.

Clone the repository

Do this only once per each computer you’re working on.

As with every repository (your own or forked) you have to clone it to your local computer:

git clone git@github.com:[USERNAME]/[REPONAME].git

If you have trouble doing above, i.e. you’re getting errors like “Permission Denied (publickey)”, then go through checklist given in introduction to this article to make sure that you have everything in place.

Pull the newest version of the code from remote repository to your local copy of it:

git pull origin master

Add the main repository (i.e. the one where you want to actually push your changes / create a pull request; i.e. not your fork’s repository) as an additional remote repository, called an upstream:

git remote add [UPSTREAM] git://github.com/[USERNAME2]/[REPONAME].git

In the next steps it will be clarified, why we need this.

Note, that [USERNAME] and [USERNAME2] are usually different and are pointing to a two different GitHub accounts in most cases (your account with a fork vs. the account, you are contributing to with original repo). While [REPONAME] is usually the same (no matter, whether it is the original repository or your fork of it).

Adding new feature or change

You through these steps every time you are working on some new feature, some change or bug fix.

Create an issue for your work

Nearly every project, you contribute to, has an issue tracking system. This step is optional, but consider creating an issue in such system where you will be able to discuss your change, feature or fix, describe your pull request etc.

In GitHub your have all the issues at https://github.com/[USERNAME]/[REPONAME]/issues.

Pull the latest code from the upstream

Pull all the changes from the original repository, you’re contributing to.

git pull upstream

You should start at this point for every new contribution to make sure you are working on the latest code

Create a new branch for your feature or fix

The branch is another copy of your code that you can switch together. You can mess all the way around and if at some point you hit the wall you can simply delete a branch without worrying that your main code is changed in anyway. That’s one of the beauties of Git. For more details — see the book.

Working directly on master branch is wrong. If you have doubts, why then either believe me or browsing The Git Book to find the answer.

The key rules here are:

  1. Each separate bug fix or change should go in its own branch.
  2. Each new branch’s name must start with an issue number that corresponds to it.
  3. Each new branch must be based on master branch from the project you are contributing to.

For the last point we use upstream remote, you have created as a one of initial steps:

git checkout [UPSTREAM]/master
git checkout -b [ISSUENUMBER]-[BRANCHNAME]

Thus, for example:

git checkout upstream/master
git checkout -b 198-user-gender-field-is-invisible-in-user-info-page

Branch name can’t have spaces and letter case is ignored. That’s why we’re playing here with all this dashes and small letters.

You should always use branches, even for doing changes as small as few bytes of code or fixing a typo. And even, if you’re coding for your own purpose, and you’re both committer, reviewer and project manager. If you don’t know why then simply believe me — this will save your ass many times in the future.

You can also browser the famous book’s branching chapter to find all the answers

Code!

Do your magic: code, test, review, fix, code again etc. Make sure everything works as expected.

Update the changelog

This is optional, but many projects has the CHANGELOG file and requires you to update it in each change, whether it is a new feature, some update or a fix for some bug.

In most cases this is as simple as adding one line to CHANGELOG file, which usually is a text file. You can do this manually or append a new line to that file directly from the console:

echo "Bug #198: Make user gender field visible again in user info page (John Doe)" >> CHANGELOG

You can find more info and some example here or in thousand places in the Internet.

Verify and commit all changes

Verify all created or modified files, add them to index and commit all changes:

git status
git add --all
git commit -am "Commit message"

If you’re still not familiar with all the stuff like index, adding files, local repository, committing etc. then review basic git diagram again for some clarity.

Commit your changes with a descriptive commit message (some ideas may be found in this (five years old!) article. Make sure to mention the issue number with #XXX so GitHub will automatically link your commit with the ticket. Here is an example:

git commit -m "Make user gender field visible again in user info page. Fix #198"

On contrary to branch name, commit message can and should have spaces and, of course, shouldn’t have any spelling mistakes.

Pull the latest code from upstream repository

This ensures you have the latest code in your branch before you open your pull request.

git pull [UPSTREAM] master

If there are any merge conflicts, you should fix them now and commit the changes again (i.e. go to step 2.3). This ensures that project’s owner will be able to merge your changes with one click.

Push your code to GitHub

Having resolved any conflicts you’re ready to go:

git push -u origin 198-user-gender-field-is-invisible-in-user-info-page

The -u parameter ensures that your branch will now automatically push and pull from the corresponding GitHub branch. That means that if you type just git push the next time it will know where to push to and you will not have to use the above code again.

This is useful if you want to later add more commits to the same pull request.

Create a pull request

You now need to create a pull request to let project’s owner know that you have some new, cool stuff for his project (i.e. a bug fix or a new feature).

Go to your repository on GitHub (your fork, not to the repository of a project you are contributing to) and click Pull Request, choose your branch on the right and enter some more details in the comment box.

To link the pull request to the issue, again, put #999 anywhere in the pull comment where 999 is the issue number (#198 in our above examples here).

The rule of thumb behind pull requests is the same as behind branches. Each pull request should always fix a single issue. For multiple, unrelated changes, please open multiple pull requests (and use multiple branches).

Wait for the review (or review yourself)

Your change will appear among other pull requests. In GitHub this is always this page:

https://github.com/[USERNAME]/[REPONAME]/pulls

At this point someone will review your change (or you will review it, if you’re using Git for your very own purpose).

If you are asked to make some changes, go to step 2.3). You don’t need to open another pull request if your current one is still open.

If your code is accepted it will be merged into the master branch and become part of the next release of the project you are contributing to. And your name will be carved on the stone wall of fame of that project and blah, blah, blah! :>

Don’t be disheartened or even surprised, if your pull request is rejected (even if it contains no bugs). Different people need different features and each project can’t be everything to everyone. Your code will still be available on GitHub as a reference for people who need it (it can be merged into some fork of the main project’s repository etc.).

This is the very same reason for which you should use branches and pull requests even for simple, two-people projects (or often even for yourself). Thanks to this you keep a clean track of all your changes, you can review everything every time, you can go back to some old, abandoned idea or rewind your project to some point etc.

Clean up your local repository and your fork

After your code was either accepted or declined you can delete branches you’ve worked with for this purpose, both from your local repository and your fork on your account in GitHub:

git checkout master
git branch -D 198-user-gender-field-is-invisible-in-user-info-page
git push origin --delete 198-user-gender-field-is-invisible-in-user-info-page

Note, that up until now you were working on a new branch, designated for given change or pull request. It is now deleted (after executing above serie of code). If your pull request is accepted then your master branch remains (most likely) behind master branch of the repository, you’ve been contributing to. To fix this, you should perform following operations:

git pull upstream
git push

This will update your local master branch (you have switched to it after executing git checkout master previously) with recent changes (after accepting your pull request) from upstream remote repository and it will also update your very own remote repository in GitHub.

These operations must only be performed, if you intend to keep your fork of the original repository in your GitHub account (i.e. for contributing more in the future?). If you have forked it only for this single change, you can safely omit last two commands and delete your fork from your GitHub account.

Quick review (checklist)

Initial steps (once)

  1. Let local copy of Git know, who you are:
    • git config --global user.email "you@example.com",
    • git config --global user.name "Your Name"
  2. Fork the original repository to your account.
  3. Clone your fork: git clone git@github.com:youraccount/repo.git.
  4. Pull the code from it: git pull origin master.
  5. Add an upstream: git remote add upstream git://github.com/originalaccount/repo.git.

Note the difference between remote (your fork) and an upstream (the original repository).

Dealing with a new change (each time)

  1. Create an issue for your work.
  2. Pull the latest code from the upstream: git pull upstream.
  3. Create a branch:
    • git checkout upstream/master,
    • git checkout -b 999-branchname.
  4. Code. Do the magic.
  5. Update the changelog: echo "Bug #999: Description of a change (Your Name)" >> CHANGELOG.
  6. Verify and commit:
    • verify: git status
    • add: git add --all
    • commit: git commit -am "Commit message".
  7. Pull again from upstream: git pull upstream master.
  8. Resolve merge conflicts, if there are any.
  9. Push your code:
    • first time: git push -u origin 999-branchname,
    • later just: git push.
  10. Create a pull request.

Wait for the review of it (or review it yourself).

Cleaning up the mess

  1. Checkout: git checkout master.
  2. Delete branch in local repository: git branch -D 999-branchname.
  3. Delete branch in remote repository (fork): git push origin --delete 999-branchname.
  4. Optionally update local repo: git pull upstream.
  5. Optionally push changes to your fork: git push.

What else? Make yourself a good coffee and read the passionate book again! :>

Leave a Reply