This article lists some experiences from working with Git on different projects.

Useful Git tips and tricks Image by Unsplash

1. Clean all updates on the current branch

I have worked on a project where due to how the project was setup, whenever I’d checkout a brand new branch, a couple of files would be automatically added. The .gitignore file was not setup to not track those files due to a number of reasons. Thus, the first thing I had to do when I’d checkout a new branch was to quickly remove add the tracked changes, so that I could have a clean slate to work with.

So, once you’ve checked out your brand new branch, the command to clean up all the automatic changes is actually really simple. Here goes:

git checkout .

2. Erase local Git branches that got merged into master

Sometimes doing a git branch locally will not be as useful as you’d expect, due to the fact that there are too many local branches checked out already. The following helpful code snippets makes it possible to erase “stale” local branches that got merged into master.

git branch --merged master | grep -v "\* master" | xargs -n 1 git branch -d

There’s a handy site that can help with understanding the above command; it’s called, and it allows us to type in the exact command that we used, and it will show us the meaning of each section of the bash command we typed in.

3. Combining several commits into one

Sometimes we need to combine several git commits into one, and for that we can use git squash.

Here’s a real-life example: I wrote a single database migration file and committed it into three different commits. Of course, when we run migrate up command, it’s not going to work, because it’s going to think that the migration already happened; i.e it’s not expected to have a migration file update for a single db migration spread across three files. So we’ll need to squash our commits.

Here’s how.

First, we’ll find our commits by running:

git log --oneline -10

This gives us an abbreviated overview of the last ten commits, on ten lines, with SHA stubs (unique ID stubs) for each of the commits.

Next, we’ll run git rebase -i HEAD~10. This will show us a list of 10 commits that we can squash (the 10 most recent commits before the HEAD commit).

On my machine, running the above code opens nano editor, with the following file:


The file looks like this:

pick SHA Commit message
pick SHA Commit message
pick SHA Commit message

There are 10 pick lines altogether here, after which there is the following message in the comments:

# Rebase SHA..SHA onto SHA (13 commands)
# Commands:
# p, pick = use commit
# r, reword = user commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit

# These lines can be re-ordered; they are execued from top to bottom.
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted
# Note that empty commits are commented out

Also note that interestingly, the last commit on the list is the newest one.

After changing to squash on the last three commits, you’ll get an updated commit message, listing the commit messages that of the commits that were squashed. That way the history of all the squashed commits is preserved in the single commit message automatically. All you have to do is save the commit with what was already offered.

If you have already previously pushed commits onto remote, you will now need to run git push -f, or, alternatively, try git push --force-with-lease.

4. How to erase a git branch locally and pull again

I had a situation where I pulled the master branch, and then rebased it onto a local feature branch. However, another developer on my team updated the feature branch remotely. Since I rebased the master branch onto the local version of the feature branch, I couldn’t add any code to it, beacuse if I did, I could possibly force push the feature branch onto the remote, but that would also mean that I’d override all the updates from the remote feature branch that my co-worker added in the meantime.

So, I decided to just delete the local feature branch, then pull it again from remote.

Here’s how I did it.

First, I deleted my local feature branch:

git branch -d feature_branch

I got back the following message:

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

It’s logical that I’d get this error, because I rebased the master branch onto it. The message above is not an error, it’s a warning.

Since I know for a fact that I’ve only rebased the master branch onto my feature_branch (i.e that’s the only difference that causes the above git error message), I can delete it with -D. The -D flag skips the check for local un-pushed commits on the feature_branch branch.

So, without fear, I’ll just run:

git branch -D feature_branch

After that, to get the remote version of the feature_branch, I’ll run:

git checkout feature_branch

That’s it, I’ve got the updates that my colleague made, and I’ve only deleted my local changes.

5. Fix the Cannot update paths and switch to branch error

Sometimes when running the following command:

git checkout --track origin/<branch-name>

…you might get a weird error that goes like this:

fatal: Cannot update paths and switch to branch '<branch-name>' at the sam e time. Do you intend to checkout 'origin/<branch-name>' which can not be resolved as commit?

The solution is pretty easy.

First, as a verification step, make sure to inspect the remote with:

git remote -v

You’ll get back the remote, which looks something like:

origin git@........ .git (fetch)
origin git@........ .git (push)

Next, run the git fetch to update the branches. Likely, it will result in your <branch-name> being fetched:

	* [new branch]    <branch-name> -> origin/<branch-name>

Now you can re-run the command:

git checkout --track origin/<branch-name>

And this time,it should work: you should be switched to <branch-name> and it’s remote should also be set up accordingly.

6. Add only selected files to staging area

This is another scenario that I had at work recently. I had a file that needed updating, but there was no way to see the changes in the frontend.

Luckily, the functionality that I needed to add was not tightly coupled to that specific file, so instead of working on that file directly, I decided to work in a different file that I was able to verify on the frontend.

Let’s name our two files from this real-life example:

  1. unreachable.js
  2. reachable.js

So, my idea was to add code to reachable.js in order to test how the code will behave and what the result will be on the frontend. Then I could just copy those changes to unreachable.js.

Once I’ve writen code that worked as I wanted, I simply copied it over from reachable.js into unreachable.js.

However, I was facing an issue: while the code in unreachable.js was the code that I needed to commit, the changes in reachable.js - the changes that lead to the solution - were now redundant.

This now left me with the following question: How do I commit only the changes in one of these two files?

I was aware of one solution to the problem:

git add unreachable.js
git commit -m "Bugfix unreachable.js"
git push

The above commands in Git do this:

  1. The first command adds unreachable.js to the staging area
  2. The second command commits the staged unreachable.js with a commit message
  3. The third command pushes local changes to the remote repository

After running the above commands, I could just do git checkout . to “undo” the changes made to reachable.js, and it would all be great.

However, it was difficult to copy-paste the file path for the unreachable.js file, so it was a bit time-consuming to run git add unreachable.js (note that this is abbreviated; the path was very long).

The solution is the second approach to committing only specific files. This approach works if you only have a couple of files, or a few files at the most. Here it goes:

git add -p

The git add -p command does the following:

  1. It lists the file location and a single change in the file
  2. It asks the following question: Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?

If you want to keep the change (“stage this hunk”), type the y letter. If you don’t want to keep the change, type the n letter.

Understanding git add --patch

The above command, git add -p, is just an alias for git add --patch. The --patch flag makes it possible for us to make more granular commits.

To understand all the options that are available to us, we can just type a question mark instead of a y or n that we listed above.

Here’s the full output:

Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? ?
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
e - manually edit the current hunk
? - print help

There is an option that we can use but is not listed: the s option.

The s stands for split the hunk. If the active hunk has a section of code that hasn’t been changed between edits, the s will split the hunk and thus make it possible to stage the edits separately.

Besides the story of why and how I used git add -p, there are other legitimate reasons to use the git add --patch:

  1. If you’ve written a lot of code, but forgot to spread it out over several commits, this is an alternative way to achieve the desired effect without much effort
  2. It makes it easy to go over your code hunk by hunk one more time before committing

7. How to preview an earlier commit

Let’s say you’re working on a webapp and you’d like to inspect the frontend of your app as it was 5 or 6 commits ago. You’re not trying to revert the changes; you only want to see what the app looked like at some point in the past.

Doing this includes four steps:

  1. list the last several commits using git log --oneline -N (where N is the number of commits in the past)
  2. get the SHA-1 of the specific commit (the unique hash in front of each line obtained in the previous step; the hash looks like, for example: abcdef123)
  3. go back to the previous state of your app with this command: git checkout <unique SHA-1 hash of the commit>; once you do that, you will see a message that reads: “You are in ‘detached HEAD’ state”, along with some additional info. This detached state means you are not checked out in any specific branch anymore.
  4. Once you’ve looked around and are ready to go back to the newest commit (the HEAD of the current branch), use this command: git checkout <current branch name>

8. How to solve the warning “your branch and origin have divered”

Working in a team with Git can be tricky, or not, depending on the situation.

Recently, I’ve checked out a branch I’ve worked on, only to discover someone else in the team rebased the origin branch. That’s what caused the following notification on my local machine when I’ve checked out the branch again:

Switched to branch 'xyz'
Your branch and 'origin/xyz' have diverged, and have 4 and 9 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

Basically, this is why the above message appears:

  1. You submit a pull request in GitHub, or GitLab, or BitBucket
  2. A team member sees your pull request, and thinks, “Oh, I can rebase the merge request!”. Then, they push the “rebase” button and that adds more changes on the remote.
  3. Now we need to rebase our local branch as well, so that both the local branch and the remote are in sync.

Here’s the fix.

First, we check if we already have this solution set up in our .git/config local project file.

nano .git/config

Then, inside the nano editor, go to the very bottom, and look for these lines:

	rebase = true

If that line does not exist, go back to the console and run:

git config pull.rebase true

Now you can confirm that it has been added to the .git/config file by again running:

nano .git/config

The reason why this works: with the config update, every git pull will also do a rebase - i.e. it will make our local branch always rebase, just like the remote branch gets rebased by other team members.

Now, all you have to do on your local branch, is run: git pull. Once you do, this is what happens:

First, rewinding head to replay your work on top of it...

And now, if we do a git status on our local branch, we’ll get the expected message:

On branch xyz
Your branch is up to date with 'origin/xyz'.

nothing to commit, working tree clean

The above message means that we’ve successfully resolved our issue.

9. How to see the actual changes in code between two commits without using SHA stubs

Let’s say the commit history looks a bit like this:

abcdef05 commit 05
abcdef04 commit 04
abcdef03 commit 03
abcdef02 commit 02
abcdef01 commit 01

And let’s say that we would like to compare the changes between the current HEAD and the commit before it. Here’s a simple way to do it, without using the SHA unique IDs of commits.

git diff HEAD HEAD~1

The HEAD "tilde" 1 argument above means “one commit before the HEAD”. If we wanted to go two commits in the past, we’d do:

git diff HEAD HEAD~2

Additionally, to quickly inspect the number of affected files and the number of changed lines in those files, we can do:

git diff --stat HEAD HEAD~1

10. Finding the parent branch that a new branch was checked out from

It seems that this is not as straightforward as one would expect. However, there is a simple command that might help:

git reflog

With this command, we’ll get a list of all the commits on the current branch, followed by commits from the parent branch, etc.

11. Removing the most recent commit from remote branch and local branch

Scenario: You had two files altered locally, but you wanted to push only one. Well, it was late at night, and your concentration was down, so instead of doing git add, you did git add --all, and then merrily pushed your changes to the remote with a nice commit message. Oops.

So what to do now? If you are happy with removing the commit both on the remote and on the local branch, you can simply do this:

git reset HEAD^

That removes the commit locally. Now we can force-push the update:

git push origin +HEAD

Note: make sure that no one else checked out your branch before you did this correction, otherwise their history and yours will be in conflict.

12. Use git reset to undo the last commit

If we run the git reset command with the --soft flag, that will keep the current uncommitted changes in our project, but it will remove the most recent commit from git. That means that the HEAD will now be one commit in the past, and the previous, more recent HEAD position will be gone. The command is pretty easy:

git reset --soft HEAD~1

The git reset command is practically the reversal of the git add command.

13. Quickly switch to the branch you were previously on

This handy tip will save you some typing.

Let’s say you’re currently checked out on the-branch-with-a-very-long-name.

For some reason you need to switch to the master branch, so you go:

git checkout master

Now you’d like to switch to the previously checked out the-branch-with-a-very-long-name.

Here’s a shortcut way to doing just that:

git checkout @{-1}

Because the above command saves us some typing, we’ll get just a bit faster. Getting faster means being more productive, and that’s always good.