Git Tips

I thought I would get down in a blog post the different Git commands and tips that I find really useful, because every now and then it seems I need to refer back to these notes (which up until this point have been in a txt file in my Dropbox) if I’ve not used a particular command in a while.

Hopefully you’ll find them useful too.

Show where Git is installed

which git

Show the Git version installed

git version

Update your global user details

git config --global user.name "Your Name"
git config --global user.email "Your Email"
git config --global apply.whitespace nowarn # ignore white space changes!

Set-up a global ignore file

First create the global ignore file…

touch ~/.gitignore_global

Then add the following content to it (this is a standard ignore file but I’ve added some Sass CSS pre-processor files to it)…

# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
*.sass-cache
*.scssc

# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip

# Logs and databases #
######################
*.log
*.sql
*.sqlite

# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db

You can let Git know about your global ignore file by editing your global .gitconfig file…

nano ~/.gitconfig

…then adding the following to it…

[core]
    excludesfile = /Users/<home-directory>/.gitignore_global

…or once the .gitignore_global file is created you can just tell git by using this short-hand command…

git config --global core.excludesfile ~/.gitignore_global

Adding all files (inc. those marked as deleted)

git add -A

Writing a long commit

A short git commit message would look like this…

git commit -m "My short commit message"

…but you should really be writing longer more descriptive commit messages which you do like so:

git commit

…what this does is open up the default editor for commit messages (which for most is Vim). Now Vim is a bizarre editor with all sorts of odd shortcuts for adding text. I’ve only used Vim to write commit messages (nothing else) so I have a very focused set of commands to write my commands…

Press i which puts Vim into ‘insert’ mode (meaning you can actually write)

This is my short description for this commit
- Here is a break down of my changes
- Another note about a particular change

After I’ve written my commit I just need to save the commit and exit Vim…

  • Press Esc
  • Press :wq (the colon means you can execute more commands, w = write, q = quit)

Viewing file changes while writing your commit

git commit -v

Viewing what files have been committed

git ls-files

Improving git log with git lg

To get a better looking git log we need to write an alias called git lg that is just made up of standard Git commands/flags but when put together (along with specific colour settings) means we can have a short git command that provides us lots of useful information.

What we need to do is open the ~/.gitconfig file and then add the following content…

[alias]
    lg = log --color --graph \
    --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' \
    --abbrev-commit --date=relative

Shorter git status

As per the above tip, we can create two extra alias’ which give us a shorter command to type (I don’t know about you but when typing really fast I seem to always misspell the word ‘status’) and doesn’t show us all the unnecessary crap that someone new to Git needs to see.

What we need to do is open the ~/.gitconfig file and then add the following content…

[alias] 
    st = status
    sts = status -sb

…you don’t need to specify [alias] if it’s already in the file (see previous tip).

Now typing git st will be the same as git status, and typing git sts will be the same as git status -sb.

Finding a commit that includes a specific phrase

git log --grep=<your-phrase-here>

For example:

git log --grep=CSS

…will display all commits that contain the word ‘CSS’ in the message.

Only merging the files you want

git checkout <branch-name> <file1> <file2> <file3>

Stashing changes you’re not ready to commit

If you make changes to your branch and then want to quickly change branches without first having to commit your current ‘dirty state’ then run:

git stash

To apply a stashed state (git assumes the most recent stashed state if none specified) use:

git stash apply

To see which stashes you’ve stored (on any branch) use:

git stash list

When viewing a list of stashes it can be useful if the stashes had corresponding messages (so you know what each stash holds), for that to happen you’ll need to create stashes with an associated message using the save command:

git stash save "my message here"

If you have multiple stashes under a branch (e.g. stash@{1} stash@{2} stash@{3}) then you can reference a particular stash using:

git stash apply stash@{2}

Note: pre git 2.0 the following would work git stash apply@{2}.

If you want to stash only specific changes then use the patch mode:

git stash -p

To view the contents of a stash use:

git stash show -p stash@{n}

Note: with the -p to print the output of the files, the show subcommand would simply print the filenames.

…where ‘n’ is the numeric index of the stash (you can also use git show stash@{n})

Applying the stash doesn’t mean it’s removed from your list of stashes though(!) so you need to run:

git stash drop stash@{<index>}

For example:

git stash drop stash@{2}

You can also apply and drop the stash at the same time:

git stash pop

You can also specify an exact stash to pop:

git stash pop stash@{2}

If you stash some work, leave it there for a while, and continue on the branch from which you stashed the work, you may have a problem reapplying the work. If the apply tries to modify a file that you’ve since modified, you’ll get a merge conflict and will have to try to resolve it. If you want an easier way to test the stashed changes again, you can run git stash <branch> which creates a new branch for you, checks out the commit you were on when you stashed your work, reapplies your work there, and then drops the stash if it applies successfully.

If you need to stash only specific files then first git add the files you don’t want to stash, then run:

git stash --keep-index

…finally you can then git reset the files you originally added (if you don’t plan on committing them yet).

Revert all changes back to last commit

git reset --hard

Note: you can do a ‘soft’ reset git reset --soft <hash>. The difference between --hard and --soft is with --hard the specified commit hash’s files are moved into the working directory and the staging area (as if there were no changes since that specified commit). But using --soft will leave whatever changes you’ve made in your working directory/staging area but will restore the specified commit you’ve selected.

Imagine you have a file called foo.txt and your Git history looked like this:

A -> B -> C (HEAD)

Let’s see each commit we made:

A == foo
B == FOO
C == Foo

The following examples explain the different reset flags:

git reset --soft B  == move HEAD to B but keep C's changes staged (i.e. added to the index)

git reset --mixed B == move HEAD to B but keep C's changes unstaged

git reset --hard B  == move HEAD to B but completely delete C (you've lost those changes forever)

Note: to undo a reset use git reflog to find the previous state (e.g. HEAD@{1}) and then reset again (e.g. git reset HEAD@{1}).

Unstaging files

To unstage files we’ve added to the staging area we need to run the command reset HEAD but that’s a bit ugly and awkward to remember. What would be easier is if we could just say git unstage, so let’s create an alias to help make that easier!

Open up the file ~/.gitconfig and then add the following content…

[alias]
    unstage = reset HEAD

Note: you don’t need to specify [alias] if it’s already in the ~/.gitconfig file.

You can also unstage a single file using:

git reset <file>

If you’ve staged files before any commits have been set (e.g. right at the start of your project) then you’ll find the above wont work because technically there are no commits to revert back to. So instead you’ll need to remove the files like so…

git rm --cached <file>

Note: you might need to replace --cached with --staged in newer versions of git.

Untrack a file without deleting it

If you want to have Git stop tracking a file it’s already tracking then you would think to run:

git rm <file>

…but the problem with that command is that Git will also delete the file altogether!? Something we usually don’t want to have happen.

The work around to that issue is to use the --cached flag:

git rm --cached <file>

Amend your last commit

If you make a commit and then realise that you want to amend the commit message then don’t make any changes to the files and just run…

git commit --amend

…which will open up the default editor for handling commits (usually Vim) and will let you amend the commit message.

If on the other hand you decide that after you’ve written a commit that you want to amend the commit by adding some more files to it then just add the files as normal and run the same command as above and when Vim opens to let you edit the commit message you’ll see the extra files you added as part of that commit.

Show the files within a commit

git show <hash> --name-only

See differences between files

To see the difference between the current working directory and the last commit:

git diff

If your files have been added to the staging area already then you can use the --cached flag:

git diff --cached

Note: the use of --cached has now been replaced with the more appropriate --staged.

To show specific changes use the --word-diff flag:

git diff --word-diff

To see the diff between the working directory and a specific commit:

git diff <hash> <file-name>

Note: the file name is optional

To see the difference between branches:

git diff <branch-1>..<branch-2>

See changes between two commits

git diff <more-recent-hash> <older-hash>

Creating a branch and moving to it at the same time

git checkout -b <branch-name>

Deleting a branch

git branch -D <branch-name>

Viewing all branches of a remote

git branch -a

Checkout a remote branch

What normally happens is this: you clone down a repository from GitHub and this repo will have multiple branches, but if you run git branch locally all you see is the master branch.

If you run git branch -a you can see all the branches for that remote repository but you just can’t access them or check them out?

So if you want to access the other branches within that repo then run the following command:

git checkout -b <new-local-branch-name> origin/<remote-branch-name>

…this will create a new branch named whatever you called it and contains the content of the remote branch you specified.

Remove a remote

git remove rm <remote>

Revert a specific file back to an earlier version

git checkout <hash|tag|HEAD> <file-name>

Note if you’ve staged your file and then started making changes to the file which you no longer want applied you can use: git checkout -- <file-name> to revert to the version of the file in the staging area.

Viewing all commits for a file and who made those changes

git blame <file>

Viewing complete history of a file (even when deleted)

git log --full-history  -- <path/to/file>

To see the last change you’d use a negative numeral like so –full-history -1 and that should indicate when/where the file was deleted.

Note: if you didn’t use --full-history I believe that git gives you a modified version of its history where the deleted file doesn’t even show up (e.g. if you had done git log -- <path/to/file>).

Commiting only parts of a file rather than the whole file

If you have a file with lots of changes made, you might not want to have all the changes logged under one single commit.

To split the single file into multiple commits you need to use Git’s patch mode…

git add <file> -p

…Git will attempt to split a file into separate hunks (Git terminology for a chunk of code). You can then press ? to see what options you have available, the most common being:

  • y - yes
  • n - no
  • d - no to all remaining hunks
  • s - split current hunk into more hunks

Sometimes you can’t split a hunk into more hunks automatically, you have to do it manually. To do so you press e to edit and then use Vim to manually make changes.

So if you have a line removed that you want to keep as part of the commit then you’ll remove the - so there is just a space instead, and if you have a line added that you want to not have included as part of the commit then you remove the entire line. BUT the most important part it also updating the line numbers at the top of the file so that the number of lines in the file match what you are looking to commit (otherwise the commit will fail). To make the edit to the hunk final (pre-commit) press esc then :wq and then you’ll be able to commit the selected changes.

Modifying your Git history with rebase

To change multiple commits you must use the interactive mode of the rebase command and you must tell Git how many commits back you want to go (because it’ll start from there and keep moving through the commits until it reaches the HEAD).

REMEMBER: when using rebase every commit in the range specified is changed whether you change the message or not. So don’t use rebase on commits that have already been pushed to a remote server as other users might have those commits pulled down and your changing of the commits will cause havoc for those users in the near future.

To amend the last 3 commits we use: git rebase -i HEAD~3 and follow the instructions.

The principle is if you want to merge two commits then you’ll need to have a commit to merge into and then change pick to squash on the other commits that you want to have squashed into the previous commit.

You can also re-order commits and other things like change commits (add files, rename the message) and remove commits completely.

Push branch without specifying its name

If you have a long branch name then you’ll know how tedious it is to type out:

git push origin bug-fix/cache-nodes-expiration

Instead you can rely on the fact that git will retrieve the current branch name from its head tag:

git push origin head

Display verbose branch information

git branch -vv

* integralist/foo     58a472b6e3a bump minor version
  integralist/bar     fc5184faaf3 no-op this service to see if anyone or anything complains
  integralist/baz     23a273f7001 move from timing metric to distribution
  master              d0cd3e3a334 [origin/master] 5.0.0 - remove write_stats (#57413)

Display concise status information

git status -sb

## master...origin/master
 M content/posts/git-tips.md

Staged files that were never commited

git fsck --lost-found

Checking object directories: 100% (256/256), done.

dangling blob 0d80705a8f09dbc9ef0dd9f5799061b9ec9c0f05
dangling commit 20e04d00aa1eba1d0f19fa7d1865c6a011715288
dangling blob 49c0238e07d183407466c54f8ac5f7aa89889ae2
dangling blob 5380475369f22f6021d94c636ae4b778d3a0c050

Note: git will extract data to .git/lost-found, use --dangling instead of --lost-found if you don’t want that to happen.

Useful when git reflog fails you (e.g. you executed git reset --hard while uncommitted files were staged).

Finding commit that introduced a bug

git bisect start
git bisect bad           # to indicate current commit is broken
git bisect good <commit> # to indicate the last good commit
git bisect <bad|good>
git bisect reset

But you can also automate this by calling git bisect run and passing a script:

git bisect run ./some-script

If you want to test specific directories and files then use -- <dir>:

git bisect start -- ./sub_directory

The bisect command uses a binary search which makes it very efficient, but if you want a linear search then you can use git rebase:

git rebase -i --exec "./some-script" d294ae9

Your script returning an error exit code will cause the rebase to stop on the commit that triggered the failure.

Finding a commit that added/removed content

git log -S "bisect"
commit c38d5a5d7e5b32a8f6ef8d6ef2d84ad003537862
Author: Integralist <mark.mcdx@gmail.com>
Date:   Mon Mar 30 20:07:22 2020 +0100

    git internals

If you’re looking for a change that doesn’t result in a new line being added/removed (e.g. a change to an existing line), then you can use the -G flag instead which will accept a regex pattern to search (see man git-log for a useful example).