My Recommended Git Workflow
The first thing I do is fork the project I want to contribute to.
Go into Github Enterprise (GHE) and fork the original project.
This will likely cause a fork to a new repo (under your username) for example: https://scm.kevininscoe.com/inscoek/aws.my-infrastructure
If you look under Forks for original project you should see your forked project listed under your Github username.
Once the project on Github is forked, I clone the fork on my machine:
$ git clone https://scm.kevininscoe.com/inscoek/aws.my-infrastructure
Git automatically names my remote forked project origin.
$ git remote -v origin email@example.com/inscoek/aws.my-infrastructure (fetch) origin firstname.lastname@example.org/inscoek/aws.my-infrastructure (push)
Then I need to add an upstream reference to the original project:
$ git remote add upstream email@example.com/Pistons/aws.my-infrastructure $ git remote -v origin firstname.lastname@example.org/inscoek/aws.my-infrastructure (fetch) origin email@example.com/inscoek/aws.my-infrastructure (push) upstream firstname.lastname@example.org/Pistons/aws.my-infrastructure(fetch) upstream email@example.com/Pistons/aws.my-infrastructure(push)
Now my local repository references my fork under the name origin and the original project under the name upstream.
In this workflow, I never work on the master branch. So, when I need to fix a bug for example, I create a new branch:
$ git checkout -b bugfix
To see what is your current branch look for the asterisk "*":
$ git branch * Pistons-1580 master
I can then make changes, test my code, make sure everything is ok, stage and commit my changes:
$ git add . $ git commit -m "commit message"
Now I need to push this local branch to my repository on Github:
$ git push -u origin bugfix
Working on more than one branch at a time
In the same repo perhaps you are making multiples changes due to multiple tickets or issues. If you use a new branch all modified code will be tracked in the new branch causing confusion.
I used to just clone the fork into a separate directory say aws.my-infrastructure.Pistons-2204 and switch directories.
However I now recommend using worktrees. See https://stackoverflow.com/questions/6270193/multiple-working-directories-with-git/30185564 and https://git-scm.com/docs/git-worktree.
Worktrees are implemented using hard links (so this only works on unix or unix like systems only which include Mac or Cygwin). Worktrees are lightweight and fast (whereas the git clone command copies down a whole nether copy of the repo). You can share changes between worktrees as long as they are committed to the local repo). With multiple clones you would have to push commits to the remote and then pull to the other. You can also use worktrees to do ‘what-if’s’ or testing things.
Note that you must have Git 2.5 (or your IDE must support) or higher to support worktrees.
Say you have already checked out a branch called 'Pistons-1580' and made changes however for whatever reason work on this branch/ticket has stalled. Now you want to work on a new ticket say 'Pistons-2204'.
$ git branch * Pistons-1580 master $ git branch Pistons-2204 $ git branch * Pistons-1580 Pistons-2204 master $ git worktree list /Users/inscoek/Projects/Pistons/aws.my-infrastructure f51e918 [Pistons-1580] Note: You can create this directory anywhere on the same filesystem (remember it’s a Unix [https://en.wikipedia.org/wiki/Hard_link hardlink]), but it should be somewhere outside of your main repository directory! $ git worktree add /Users/kevin/Projects/Pistons/aws.my-infrastructure.Pistons-2204 Pistons-2204 4 Preparing worktree (checking out 'Pistons-2204') HEAD is now at f51e918 Updated remote-states file updated due to Atlantis migration $ git worktree list /Users/inscoek/Projects/Pistons/aws.my-infrastructure f51e918 [Pistons-1580] /Users/inscoek/Projects/Pistons/aws.my-infrastructure.Pistons-2204 f51e918 [Pistons-2204]
Now to start working on code in issue Pistons-2204 you switch branches now by changing directories. Git automatically know by the worktree directory which branch to track code in. We no longer use git branch command when switching while using worktrees.
$ pwd /Users/inscoek/Projects/Pistons/aws.my-infrastructure $ git branch * Pistons-1580 Pistons-2204 master
Now to work on Pistons-2204
$ cd /Users/inscoek/Projects/Pistons/aws.my-infrastructure.Pistons-2204 $ git branch Pistons-1580 * Pistons-2204 master
Follow normal workflow at this point.
After your worktree branch is committed and merged you can remove it
$ git worktree list /Users/inscoek/Projects/Pistons/aws.my-infrastructure f51e918 [Pistons-1580] /Users/inscoek/Projects/Pistons/aws.my-infrastructure.Pistons-2204 f51e918 [Pistons-2204] $ git worktree remove /Users/inscoek/Projects/Pistons/aws.my-infrastructure.Pistons-2204
Conflicts and rebasing
Eventually everyone will come into conflicts with multiples changes on the same repo. These conflicts will always be on different branches (or should be).
"In general the way to get the best of both worlds is to rebase local changes you’ve made but haven’t shared yet before you push them in order to clean up your story, but never rebase anything you’ve pushed somewhere." See also https://github.blog/2016-09-26-rebase-and-merge-pull-requests/
$ git rebase master $ git push origin Pistons-3389 To scm.kevininscoe.com:inscoek/terraform-modules-common.git ! [rejected] Pistons-3389 -> Pistons-3389 (non-fast-forward) error: failed to push some refs to 'firstname.lastname@example.org:inscoek/terraform-modules-common.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
In this case it is ok to use --force option if the commits are only your own.
$ git push origin Pistons-3389 --force Enumerating objects: 13, done. Counting objects: 100% (13/13), done. Delta compression using up to 8 threads Compressing objects: 100% (7/7), done. Writing objects: 100% (7/7), 1.36 KiB | 1.36 MiB/s, done. Total 7 (delta 5), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (5/5), completed with 5 local objects. To scm.kevininscoe.com:inscoek/terraform-modules-common.git + f78b146...57f5039 Pistons-3389 -> Pistons-3389 (forced update)
Since I forked the original project, Github knows that origin and upstream are linked. If there are no conflicts, github will show me a big green button to create a pull request. Once the pull request is created, I just have to wait for the maintainer to merge it in upstream’s master branch. Then, I need to sync both my local copy and my fork on github with the original project. In order to do that, on my local copy, I checkout my master branch, fetch upstream’s changes, and merge them.
Examples of good pull requests:
Working with someone else's branch
Check out the (pull request) PR in question
$ git fetch upstream pull/197/head:DAP-942 remote: Enumerating objects: 8, done. remote: Counting objects: 100% (8/8), done. remote: Total 11 (delta 8), reused 8 (delta 8), pack-reused 3 Unpacking objects: 100% (11/11), 2.05 KiB | 261.00 KiB/s, done. From scm.kevininscoe.com:Pistons/aws-accounts * [new ref] refs/pull/197/head -> DAP-942 $ git checkout DAP-942
This will checkout the upstream PR number 1234 and create a branch called PR-1234 locally
Merge pull request on Github via button.
Bringing fork up to date again
Then to bring my local repo (and then fork) current with upstream.
$ git checkout master $ git fetch upstream $ git rebase -i upstream/master
Now my local master branch (Forked) is ahead of origin’s master branch, so I push those changes to Github to the forked repo:
$ git push
I don’t need the bugfix branches (the local one and the Github one), so I can delete those :
This is optional as you can continue to use the same branch but I personally prefer to clean up afterwards.
$ git branch -d bugfix $ git push origin -d bugfix And now, my local repository is even with both origin and upstream, and I can start again.
$ git rebase -i HEAD~4 (or the number of commits to squash)
Change pick to squash on lines unnecessary
$ git push --force origin branch
Tagging commits as releases
To tag a commit (annotated):
$ git add <some files> $ git commit -m "This is release V1.5" $ git tag -a v1.5 -m "Release V1.5"
By default, the git push command doesn’t transfer tags to remote servers. You will have to explicitly push tags to a shared server after you have created them. This process is just like sharing remote branches — you can run git push origin <tagname>.
$ git push origin v1.5
To summarize, here’s the complete workflow :
$ git checkout -b myawesomefeature $ git add . $ git commit -m "Awesome commit message" $ git push -u origin myawesomefeature
Create a pull request, wait for the maintainer to merge it.
$ git checkout master $ git fetch upstream $ git merge upstream/master $ git push $ git branch -d myawesomefeature $ git push origin -d myawesomefeature