Viewing commit history in Git

git log command shows the commit logs. It provides many useful options to help you view and find the commit history.

Show commit history in default format

# Show the whole commit history
$ git log
commit 69c0b0bd7ff8cba857cae89a01c9da4a27b43632
Author: xxx <xxx@xxx.com>
Date:   Sun Dec 9 21:30:11 2018 +0800

    feat: add main php file

commit 9438770e0d9ba6d775a61d9b4d1d4030a7ee682b
Author: xxx <xxx@xxx.com>
Date:   Sun Dec 9 19:34:45 2018 +0800

    Initial commit

Show commit history in short format with one log in one line

$ git log --oneline
69c0b0b feat: add main php file
9438770 Initial commit

Show commits containing specified string

# Show only commits whose commit messages contain 'main menu'
$ git log --grep 'main menu'

Show commit history with limited number

# Show only the last two commit logs
$ git log -2

Show commit history of limited time period

# Show commit logs that are made in the last 2 weeks
# --after or --since works the same
$ git log --after=2.week

# Show commit logs that are made after 2018-12-01
$ git log --after=2018-12-01

# Show commit logs that are made before 2018-12-01
$ git log --before=2018-12-01

Show commits related to a string

# Show only those commits that changed the number of occurrences of update_post_meta,
# means the commits add or remove 'update_post_meta'
$ git log -S update_post_meta

Show commits that changed a file / files

# show commits that introduced changes to my-plugin.php
$ git log -- my-plugin.php

# show commits that introduced changes to files in inc/ directory
$ git log -- inc/

Show commit history with difference/changes

# Show commit logs with differemce introduced in each commit
$ git log -p 

Show commits of specified author or committer

# Show only commits made by john
$ git log --author john

# Show only commits committed by john
$ git log --committer john

Show commits in graph displaying the commit structure

# Show all commits of branches in a graph structure
# --all,
# --graph,
# --oneline,
# --decorate, 
$ git log --all --graph --oneline --decorate

Copy changes of commits in Git

If you just want to apply changes of some certain commits in other branch, cherry-pick command works for you. It picks changes from the selected commits and apply them on another branch:

# Apply the changes introduced by commit 95f2137 and
# create a new commit with the change
$ git cherry-pick 95f2137

# Apply the changes introduced by tip commit of develop branch
$ git cherry-pick develop

You can also specify multiple commits :

# Apply the changes introduced by the third last commit and 
# the tip commit on develop branch and create 2 new commits.
$ git cherry-pick develp~2 develop~

# Apply the changes introduced by all commits that are ancestors of develop or topic, but not any commits on master.
$ git cherry-pick develop topic ^master

If you want to copy changes in working directory and staging area without committing them, add -n or --no-commit option :

# Apply the changes introduced by tip commit of develop branch 
# in working tree and staging area, but not commit the changes.
$ git cherry-pick --no-commit develop

Partial clone in Git

If you want to clone only a part of history in Git, use --depth option to create a shallow clone with only specified number of commits :

# Create a shallow clone.
$ git clone --depth <depth> <repository-url>

# Examples:
# Clone only 1 commit
$ git clone --depth 1 https://github.com/libgit2/libgit2

You can also create a shallow clone by specifying a date :

# Create a shallow clone with history after 2018-05-01
$ git clone --shallow-since=2018-05-01 https://github.com/libgit2/libgit2

And more, -shallow-exclude=<revision> allows you to create a shallow clone with history after a commit.

Downloading a new remote branch in Git

The most easiest way to download a new branch from remote repository is :

# Download a new remote branch named topic and switch to topic
$ git checkout topic

What above command do includes :

  1. Fetch topic branch from remote

  2. Set up its upstream branch :

    Here topic is tracking <remote>/topic. Thus when you execute git pull, Git will know where to get update.

  3. Switch to topic branch

Note: $ git checkout --track origin/topic does the same thing with $ git checkout topic .

--track sets up which remote branch the new branch is tracking.

Here -b is ignored, meaning the new branch is derived from the remote branch.

git chekcout <remote-branch-name> a shortcut, if you want to give a different branch name with the remote one, use the full format :

# Create a new branch from a remote branch and switch to it.
$ git checkout -b <branch> <remote>/<branch>

# Examples:
# Create a new branch setupfix from origin/issue13 and switch to setupfix.
$ git checkout -b setupfix origin/issue13

Checking local branch is ahead or behind remote branch

To see a local branch is ahead or behind its upstream branch, use:

# List all branches with more information:
# id, message and relationship with their upstream branches
$ git branch -vv
develop 99914fb [origin/develop: ahead 2] fix: missing comma
* master 04a4f6a [origin/master: ahead 1, behind 2] feat: add menu
temp 94f4439 feat: add custom post type
test ac5446a [origin/test] initial commit

Above information illustrates the following relationship with remote ones:

  • develop branch is ahead origin/develop by 2 commits
  • master branch is ahead by 1 commit, and behind by 2 commit
  • temp branch is not tracking any remote branch
  • test branch stays up to date

Note that the remote information may not be up to date, it just present situations the last time you communicated with remote server.

To see up to date information, you can first update changes from remote :

# Fetch update from all remote tracking branches. Note it does not
# merge them to the local branches.
$ git fetch --all
$ git branch -vv

Stopping tracking a file in Git

This article shows you how to stop tracking a file/folder that has been tracked before.

To untrack a file that has been committed, what you do is adding a new ignoring rule and removing the file in a commit.

Below is a full example of ignoring /assets/my-style.css .

Step 1: add below content to .gitignore file to add an ignore rule.

/assets/my-style.css

Note: See ignoring files for how to create your  .gitignore file and write ignore rules.

Step 2: remove it from repository and commit the changes .

# Stage .gitignore
$ git add .gitignore

# Remove my-style.css file from staging area, my-style.css
# in working directory is left alone.
# --cached, only apply to staging area
$ git rm --cached -- assets/css/my-style.css

# Commit them
$ git commit

Thus, my-style.css will not be tracked any more. Be aware that it still remains in previous history.

To stop tracking a directory is similar, just replace above file with a folder in .gitignore file and git rm command.

Ignoring files in Git

Ignore files in a git repository

Use .gitignore to allow sharing ignoring rules (version controlled)

If you want some files being untracked in a repository, create an .gitignore file at its root directory and commit it. .gitignore file is used to list all the file patterns to ignore, standard glob patterns work in it.

Basic rules for patterns have been included in below example .gitignore file :

# ignore all .class files
*.class

# exclude lib.class from '*.o', meaning all lib.class are still tracked
!lib.class

# ignore all json files whose name begin with 'temp-'
temp-*.json

# only ignore the build.log file in current directory, not those in its subdirectories
/build.log

# specify a folder with slash in the end
# ignore all files in any directory named temp
temp/

# ignore doc/notes.txt, but not doc/server/arch.txt
bin/*.txt

# ignore all .pdf files in the doc/ directory and any of its subdirectories
# /** matches 0 or more directories
doc/**/*.pdf

Remember to commit it:

$ git add .gitignore
$ git commit 

Note: Adding new patterns in .gitignore won’t affect the files already tracked by Git. You can stop tracking a file.

You can have more than one .gitignore file in subdirectories. Its content will only apply to the folder it resides.

Use .git/info/exclude to only ignore files locally (not version controlled)

If you just want a personal and local ignoring configuration without committing a .gitignore file, use .git/info/exclude to achieve that.

What you do is just adding exclude rules in .git/info/exclude within in the root of a repository.

Any rule added in the file will not be pushed to a remote repository. This way enables you to ignore locally generated files and no need to worry about other users.

Ignore files in all repositories

If you want to ignore some files for all repositories, you can set a global gitignore file. Below is an example to ignore .DS_Store files :

File ~/.gitignore_global :

*~
.*.swp
.DS_Store

Then configure it as your global gitignore settings in Git :

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

Debug gitignore files

check-ignore command can be used to debug if a file/folder is excluded by some ignoring/exclude rules you’ve set up :

$ git check-ignore -v <pathname>

If the pathname matches an exclude pattern, it outputs the gitignore file, line number, pattern and your query pathname. Otherwise, nothing is output.

Note: By default, tracked files are not output since they are not subject to exclude rules. If a file is tracked before and untracked currently, it can be output.

Below example to debug if user.csv is excluded:

# Debug whether user.csv is exclude by some ignoring rule 
$ git check-ignore -v users.csv
.gitignore:3:*.csv      user.csv

Here user.csv is excluded by *.csv pattern in .gitignore.

Read more:

Undoing merge or pull in Git

Undo pull and recover to previous HEAD commit

If you want to undo git pull action which has been executed and go back to the previous state, use :

# Undo pull action
# --hard, not only reset the current branch and staging area, but also working directory
# ORIG_HEAD, the old HEAD, here it is the HEAD before pull action
$ git reset --hard ORIG_HEAD

Undo pull but keep unstaged changes in working directory

Note the command above will discard changes you have made in working directory. If you have not staged them and want to keep, use --merge option instead :

# Undo pull action but keep unstaged changes in working directory
$ git reset --merge ORIG_HEAD  

Discarding changes in working directory in Git

To discard changes in working tree to make it clean, a safer way is to use stash command. stash takes the changes in working directory away and you can get them back later.

# Stash changes in working directory
$ git stash

# Apply the latest stashed changes
$ git stash apply

Read stashing changes for more about how to use stash.

If you still want to discard the changes directly, use below command (Changes can not be recovered):

# Discard the changes in working directory and staging area.
# Be aware that changes can not be recovered.
$ git reset --hard

Deleting a local or remote branch in Git

Delete a local or remote branch in Git. Last updated: 2018-12-22

Delete a local branch

Make sure you are not on the branch to be deleted :

# Delete local branch 'test'

# First switch to another branch if you are on 'test' branch
$ git checkout master

# Do delete action
$ git branch -d test

If your branch has not been merged, use -D option to force delete it :

$ git branch -d test
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.

$ git branch -D test
Deleted branch test (was 4f10b4a).

Delete a remote branch

Use --delete option to push command :

# Delete a remote branch 'test' on Git version above v1.7.0
$ git push origin --delete test

The command above is available after Git v1.7.0. If you are using an earlier version :

# Push empty source to remote branch of 'test', meaning deleting it
$ git push origin :test