Intermediate Git

Dave White



git clone
git clone

A case for terminal

Most of learning git is just figuring out what you can do
Many things difficult or impossible in GUI
GUI not always available on compute servers
Some things are easier with a client


  1. Improve basic workflow
  2. Parse and navigate the history
  3. Branching & Merging
  4. Other misc stuff

Improving workflow

Bad commit practices


#+ATTRREVEAL: :frag (appear)


#+ATTRREVEAL: :frag (appear)

Creating a branch

git branch <branch-name>
git checkout -b <branch-name>


Some things you maybe didn't know…


line 1 - title
line 2 - blank
line 3 - longer description


#+beginsrc bash -i
git commit -m "title" -m "description"


git add -i <larger>
git add -p <smaller>

Commit A Single File With Staged Files

git commit <file>
git reset <file>

Small demo…


Note that these will not affect a push; reset applies to just 1 branch.

I forgot to test my last commit:

git reset --soft

I need to add/fix something to my last commit:

git reset --mixed

My last commit is utter garbage and I want to trash it completely:

git reset --hard


Note that there is a difference between

git reset --soft


git reset --soft HEAD~1

Without specifying a commit (latter), the reset only applies to your index and workspace.
To undo a commit, you need to explicitly specify which commit you are referring to.

Undo a reset

git reset HEAD@{n}

replace n for how many times you did the git reset command
If I did 'git reset HEAD~1' or 'git reset HEAD~20', then n is 1
If you did 'git reset HEAD~1; git reset HEAD~1', then n is 2


git stash save [<message>]
git stash list
git stash show
git stash apply stash@{n} #n is a number
git stash drop stash@{n}
git stash -u
git stash pop stash@{n} #drop and apply


git revert <ref>

Like reset, but appropriate for your origin
It doesn't undo the commit, but creates a new commit that is identical to the commit you want to 'reset to'.


How would we undo two commits, but retain changes

git reset --soft HEAD~2

This also works on individual files

#+ATTRREVEAL: :frag (appear)
Other notation - next branch point:



  1. Create a branch called "cleanup"
  2. Add "Forked by <your name>" to README.txt
  3. Add this change to index
  4. Stash
  5. Add "Created by Matthew McCullough" to README.txt
  6. Commit with a message and a body
  7. Undo commit and move them to workspace only
  8. Stash these changes
  9. Apply first stash
  10. Commit with a message
  11. Apply second stash
  12. Commit with a messsage and a body
  13. Remove all your stashes


git checkout -b cleanup
echo "\nForked by Dave" >> README.txt
git add -u README.txt
git stash save Dave

git status
cat README.txt

echo "\nCreated by Matthew McCullough" >> README.txt
git commit -a -m "Signed Matthew McCullough" -m "What a great dude"
git reset --mixed HEAD~1 #HEAD~1 must be included
git stash save -u Matt #-u must come right after save
git stash list
git stash apply stash@{1}
git stash drop stash@{1}
git add -u README.txt
git commit -m  "Added name Dave"
git stash pop stash@{0}

#This last line will create a conflict, which we will talk about later
#Fix the conflict by removing lines with:
#You can do this manually or with a stream editor:
sed -i -r '/^<{3,}/d; /^>{3,}/d; /^={3,}/d' README.txt

cat README.txt

git commit -a -m "Signed Matthew McCullough" -m "What a great dude!"

History parsing


View commits in a tree

git log --graph --oneline --decorate --all

Bash alias

Git was meant to be scripted/aliased

#$HOME/.bashrc in linux
#$HOME/.bash_profile in mac
alias glg="git log --graph --oneline --decorate --all"


git log <tag, hash, branch,remote>
git log RELEASE_1.1
git log 8d2636d
git log origin/master
git log origin/feature_subtraction_polished


git log -3
git log --since="5 years ago"
git log --until="8 years ago"
git log --author="Peter"

Specific details

git log -L <startrange>,<endrange>:<file>
    git log -L 1,2:pom.xml
    git log -L 1,3:pom.xml
git log -L :funcname:file
    git log -L :repositories:build.gradle
    git log -L :repositories:build.gradle -L :dependencies:build.gradle
git log -S''
    git log -S'maven'
git log -G''
    git log -G'dep.+'
git log --grep'Search messages'
    git log --grep='script'

Combine as many options as you want

Other help

Shell tools

git log | less
git log | grep -ei "some string"

Gitk, Gitkraken, Magit, etc..


How to move around.
Try this:

git checkout master
git status

Detached head

Try this:

git checkout RELEASE_1.0
git status

Detatched - not on newest commit

Any commits without explicitly branching will not be attached to the tree

Create a new branch off the checked out branch if you are making a commit to an older commit


  1. Find where sum function was added (Trick question)
  2. Navigate to where that was committed (also tricky)
  3. Navigate to the next branch point from there
  4. create a branch called "testbranch"
  5. Create a new file called TRASH
  6. Commit the file
  7. Navigate to master
  8. Find the commit associated with testbranch


grep -Ri "sum"
git log -L :sum:src/Sum.groovy
git checkout 333fd9
git checkout HEAD^
git checkout -b "testbranch"
touch TRASH
git add TRASH
git commit -a -m "created trash"
git checkout master
git log testbranch

Branching & Merging

1. Branching discussion

Branch/Merge fear

People don't like branching because it means merging


#+ATTRREVEAL: :frag (appear)

Problems clean branching helps with

Simply, branches allow for


Think of commits as a sections in a textbook.
A branch is going to be chapters in your textbook
You can work on multiple chapters of a book at a time.

#+ATTRREVEAL: :frag (appear)
1 feature per branch, rule of thumb

Smallish change for many

Switch your usual large commits, with a merged branch.
Actually will make things easier to merge

2. Basic Merging

Remotes basics

Master & origin/master are different branches
Feature1 & feature1/master are different branches
Push/pull is just fetch + merge

Pushing, pulling, merging

Same thing in practice, but different vantage point
If you are not a manager you would need to do a pull request

#+ATTRREVEAL: :frag (appear)
How to pull request

  1. Push your branch
  2. Submit a pull request
  3. Wait for me to merge it


Send me a pull request to merge "cleanup" with master

Ways of Merging

Merging is always towards the head
Think of master as a giant git beast

Two main ways of merging

#+ATTRREVEAL: :frag (appear)


Why set your default to no-ff? keeps things separated by feature/user

git config --global --add merge.ff false
git config --add merge.ff false


Only merge if nothing new at merge point

git merge ff-only


  1. create a backup of your repo
  2. merge "cleanup" with master, no-ff
  3. undo your merge
  4. merge "cleanup", ff
  5. try to merge tesbranch with –ff-only


cp ../hellogitworld ../hellgitworld_Backup #linux
cp -a ../hellogitworld ../hellgitworld_Backup #mac
git checkout master
git merge cleanup --no-ff
git reset --hard HEAD~1
git merge --ff-only

Merge vs rebase

Rebase is ugly
Few cases when you should use it

Pull –rebase

Makes things easier, eliminates many merge issues as you go along

git config --global pull.rebase true

When you push, it will look like…

Pull –rebase note

You do NOT need your repo to look exactly like remote
Nor should you try to do this, or expect it.
What matters is that both are clear
In a merge it doesn't matter where code came from
Small differences in master, larger differences between your branches


Pull master into testbranch


checkout testbranch
git pull –rebase master

Deleting a branch

git branch -D <branch>

Restore a branch

git checkout -b <branch> <sha>


  1. Delete testbranch
  2. Look at where test branch was
  3. restore test branch


git branch -D testbranch
git checkout -b testbranch <sha>

3. Tag/Notes


git tag <tag-name>
git push --follow-tags
git push <remote> <tag-name> --force #if moved
git tag -a <tag-name> -m <message>
git checkout <tag-name>
git tag -d <tag-name> #local
git push --delete origin <tag-name> #remote

Note on tags/branches

When a ref is created, it needs to be push - this includes new branches and tags
Everything point to point (one branch at a time), unless required or specified
eg. commits have dependencies

Explicitly need to push:

Branch example



git notes add -m "message"
git log -p notes/commits
git push <remote> refs/notes/*
git fetch origin refs/notes/*:refs/notes/*
git notes --help


  1. Tag commit 435ffce as 'RELEASE_1.1.1'
  2. Add a note to RELEASE 1.1


git checkout 435ffce
git tag RELEASE_1.1.1
git checkout RELEASE_1.1
git notes add -m "Missing log files"

Different branch models

Industry standard:

Everything gets pulled top to bottom

#+ATTRREVEAL: :frag (appear)
New project/single individual/small project

#+ATTRREVEAL: :frag (appear)
Why make MASTER stable only? Default

Merge conflicts


  1. Fetch
  2. Diff
  3. Blame
  4. Cherry-pick/merge parts
  5. Merge
  6. Branch delete

Fetch & Diff

Fetch, then diff

#commit or stash then...
git fetch
git log ..<branch> #list changes that will be merged
git diff ..<branch>
git difftool ..<branch>

diff Notation

difference between a & b


difference between common ancestor and b


Log Notation

everything that is in b but not in a


only in one or other


Fetch & Diff

Many different diff/merge conflict tools

Many graphical git wrappers have their own

Diff tool setup and use

git config --global merge.tool <merge-tool>
git config --global diff.tool <merge-tool>
#git config --global diff.external <merge-tool>

git difftool <COMMIT_HASH> file_name
git difftool <BRANCH_NAME> file_name
git difftool <COMMIT_HASH_1> <COMMIT_HASH_2> file_name

git difftool RELEASE_1.1 RELEASE_1.0
git difftool RELEASE_1.1 RELEASE_1.0


git blame <file>
git blame -e <file>
git blame -L <start>,<end><file>
git blame -L :function <file>
git blame -L ... -M <file>

Who do I talk to, yell at?
Merges should often be a group effort


get the email of whoever added line three to


git blame -e -L 3,3

Cherry pick

git cherry-pick (commits in order to append, first to last)


  1. Create a branch 'hotfix' off of Release 1.1
  2. Cherry-pick commits from cleanup
  3. Tag as RElEASE_1.1h


git checkout Release_1.1
git checkout -b hotfix
git cherry pick <SHA> <SHA>
git tag Release_1.1h1

Initial merging

Complete failure to merge - updated index, not pushed

git stash
git reset etc..


merge -s <strategy>

Strategy option

merge -s <strategy> --strategy-option <option>

Don't confuse with -s

Conflict mode

git log --merge


git diff --ours
git diff --theirs
git diff --base


git show :1:<filename> #common ancestor
git show :2:<filename> #your versions
git show :3:<filename> #their version
git clean -f #remove any extra files used for merge resolution

These can be piped.

#+ATTRREVEAL: :frag (appear)

checkout --theirs <filename>

Reset or start over

git merge --abort  #RESET
git checkout -m -- <filename> #START MERGE CONFLICT OVER FOR A FILE
git checkout -m -- .* #for everything

conflict markers

conflictstyle diff3 (shows common ancestor too)

Towards you, you are always first (left)

#+beginsrc bash -i
git config –global merge.conflictstyle diff3
git checkout –conflict=diff3 <file>

Handle merge conflicts systematically with a merge tool (see diff tools from before)

git mergetool <file1> <file2>
git mergetool <branch1> <branch2>
git mergetool <sha> <sha> <file>


git clean -n #check what clean will do
git clean -f #clean


  1. Move into mergeConflict repo
  2. Set your config to use diff3
  3. View the differences between all three branches
  4. Merge testMerge1 into test2Merge2
  5. View the differences between ours and thiers using diff
  6. Make some changes to test.h
  7. Start conflict mode over
  8. Abort the merge
  9. Merge testMerge2 into testMerge1
  10. Pipe the different versions into files for viewing side by side
  11. Abort the merge
  12. Merge theirs
  13. Cleanup


git branch mergeTests
git config --global merge.conflictstyle diff3
git diff testMerge1 testMerge2 testMerge3
git difftool testMerge1 testMerge2
git checkout testMerge2
git merge testMerge1
git diff --ours
git diff --theirs
echo "this" >> test.h
git checkout -m -- .*
git merge --abort
get checkout testMerge1
git merge testMerge2
git show :2:test.h > testOURS.h
git show :3:test.h > testTHEIRS.h
git merge --abort
git merge -s theirs
git clean -n
git clean -f


Set back to normal:

git config --global merge.conflictstyle merge


Record how you applied conflict resolutions before and automatically apply them
Simply enable:

git config --global rerere.enabled true
git config --global rerere.autoupdate true
Handle merge conflicts the same way as you did before
Recorded preimage for ...
Recorded resolution for ...
Resolved ... using previous resolution.

Review: sane defaults

git config –list

git config --list

git config --global \
    merge.conflictstyle diff3 (shows common ancestor too) \
    merge.ff false \
    merge.tool <meld,p4merge,ediff(emacs,smerge-mode)> \
    diff.tool <...> \
    core.fileMode=false \
    pull.rebase true \
    rerere.enabled true \
    rerere.autoupdate true


Other than a clean workflow, proper conventions and communication will solve most potential conflicts

Some conventions we went over

Conventions to consider



Regression debugger's best friend

git checkout <commit that you know has regression>
git bisect start
git bisect bad
git bisect good <commit that you know doesn't have regression>

Git will automatically checkout a commit and expect you to label it as good:

git bisect good

or bad

git bisect bad

Git will automatically checkout another commit.
This process continues until it reaches the point where the regression was found

Automation can be done with

git bisect run ./somescript

When you are done:

git bisect reset

Sharing incomplete chunks

sync/backup your workspace with dropbox/nextcloud


…of some things in future advanced class

Large files

Terrible idea to track large binary files (can't open in a text editor) in the default way

Solution git-lfs

git-lfs track <*.filetype>
git add <largefile.filetype>


If you push your lfs to a remote and somebody clones your repo:
The data files will NOT be there.
They would need to get the files from your local machine.
You need to set:

git config lfs.url = ""

And this would also mean setting up your local machine to point to the www in a secure manner.
With this in mind, its wise to have a lab server that can

  1. Store the data
  2. Act as origin for your data
  3. Provide web services

If this is the case, you do not need to set the above configuration.


exmples located there

Shared Templates

git config --global init.templateDir <directory>
git init


Subtrees - If you don't plan on editing dependencies
Very straightforward to use, not very flexible

Submodules - If you don't plan on editing dependencies
Very flexible, requires very careful usage