My Recommended Git Workflow

From Public wiki of Kevin P. Inscoe
Jump to navigation Jump to search

Note: This was written and taught at my previous employer where Github Enterprise was used. However the principles will work just fine with public Github or ay Git server really.

Initial forking

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://kevininscoe.com/scm/inscoek/aws.my-infrastructure

Git automatically names my remote forked project origin.

$ git remote -v
origin git@scm.kevininscoe.com/scm/inscoek/aws.my-infrastructure (fetch)
origin git@scm.kevininscoe.com/scm/inscoek/aws.my-infrastructure (push)

Then I need to add an upstream reference to the original project:

$ git remote add upstream git@scm.kevininscoe.com/Pistons/aws.my-infrastructure
$ git remote -v
origin git@scm.kevininscoe.com/inscoek/aws.my-infrastructure (fetch)
origin git@scm.kevininscoe.com/inscoek/aws.my-infrastructure (push)
upstream git@scm.kevininscoe.com/Pistons/aws.my-infrastructure(fetch)
upstream git@scm.kevininscoe.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.

Branches

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

See https://git-scm.com/book/en/v2/Git-Branching-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 'git@scm.kevininscoe.com: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)

Pull request

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

Merging

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

Cleanup

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.

Squashing commits

https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History

$ 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

See https://git-scm.com/book/en/v2/Git-Basics-Tagging and https://stackoverflow.com/questions/11514075/what-is-the-difference-between-an-annotated-and-unannotated-tag for futher.

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

Workflow

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