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 workgit 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, theshow
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
usegit 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 donegit 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
- yesn
- nod
- no to all remaining hunkss
- 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).