Git Tutorial

Why Version Control ?

Have you ever:

  • Made a change to code, realized it was a mistake and wanted to revert back?
  • Lost code and didn’t have a backup of that code ?
  • Had to maintain multiple versions of a product ?
  • Wanted to see the difference between two (or more) versions of your code ?
  • Wanted to prove that a particular change in code broke application or fixed a application ?
  • Wanted to review the history of some code ?
  • Wanted to submit a change to someone else’s code ?
  • Wanted to share your code, or let other people work on your code ?
  • Wanted to see how much work is being done, and where, when and by whom ?
  • Wanted to experiment with a new feature without interfering with working code ?

In these cases, and no doubt others, a version control system should make your life easier.

Version Control / Revision control / Source Control​ ​is is a software that helps software developers to work together and maintain a complete history of their work.

You can think of a version control system (“VCS”) as a kind of “​database​”. It lets you save a snapshot of your complete project at any time you want.

When you later take a look at an older snapshot (“version”), your VCS shows you exactly how it differed from the previous one.

A version control system ​records the changes ​you make to your project’s files. This is what version control is about. It’s really as simple as it sounds.

Popular VCS :

  • git
  • Apache Subversion
  • Mercurial
  • Concurrent Versions System
  • Perforce
  • Team Foundation Server

Types of VCS :

Centralized version control system (CVCS)
Ex: CVS, SVN, TFS

Distributed version control system (DVCS)
Ex: Git, Mercurial

Centralized Version Control System (CVCS)

Uses a central server to store all files and enables team collaboration. But the major drawback of CVCS is its​ ​single point of failure,​ ​i.e., failure of the central server.

Unfortunately, if the central server goes down for an hour, then during that hour, no one can collaborate at all.

Distributed Version Control System (DVCS)

DVCS does not rely on the central server and that is why you can perform many operations when you are offline. You can commit changes, create branches, view logs, and perform other operations when you are offline. You require network connection only to publish your changes and take the latest changes.

How the Centralized VCS works ?

A typical VCS uses something called​ ​Two tree architecture​, this is what a lot of other VCS use apart from git.

Usually, a VCS works by having​ ​two​ ​places to store things:

  1. Working Copy
  2. Repository

These are our two trees, we call them trees because they represent a file structure.

Working copy [CLIENT]​ ​is the place where you make your changes. Whenever you edit something, it is saved in working copy and it is a physically stored in a disk.

Repository [SERVER]​ ​is the place where all the version of the files or commits, logs etc is stored. It is also saved in a disk and has its own set of files.

You cannot however change or get the files in a repository directly, in able to retrieve a specific file from there, you have to checkout.

Checking-out​ ​is the process of getting files from repository to your working copy. This is because you can only edit files when it is on your working copy. When you are done editing the file, you will save it back to the repository by committing it, so that it can be used by other developers.
Committing​ ​is the process of putting back the files from working copy to repository.

The famous VCS with this kind of architecture is Subversion, SVN or TFS.

How the Distributed VCS works ?

Unusually, a DVCS works by having three places to store things:

  1. Working Copy
  2. Staging
  3. Repository

As Git uses Distributed version control system, So let’s talk about Git which will give you an understanding of DVCS.

Git was initially designed and developed by Linus Torvalds in 2005 for Linux kernel development. Git is an Open Source tool.

Git Architecture :

Git uses three tree architecture. Well interestingly Git has the ​Working Copy ​and ​Repository ​as well but it has added an extra tree ​Staging ​in between :

As you can see above, there is a new tree called ​Staging​, what this is for ?

This is one of the fundamental difference of Git that sets it apart from other VCS, this ​Staging tree ​(usually termed as ​Staging area​) is a place where you prepare all the things that you are going to commit.

In Git, you don’t move things directly from your working copy to the repository, you have to stage them first, one of the main benefits of this is,​ ​to break up your working changes into smaller, self-contained pieces.

To stage a file is to prepare it for a commit.
Staging allows you finer control over exactly how you want to approach version control.

Advantages Of Git

  • Team Collaboration
  • Defect Tracking
  • Fast compared to other VCS
  • No single point of failure
  • Sleek, Reliable and Secure
  • Open Source Cross-platform

Git works on most of OS : Linux, Windows, Solaris and MAC.

Configuring Git

Installation can be done with the help of following command on CentOS :

$ sudo yum install git

Check if git is available or not :

$ git --version
git version 1.8.3.1

$ rpm -qa | grep git
git-1.8.3.1-20.el7.x86_64

Next, we will set our configuration :

$ git config --global user.name "Vishnu Dadhich"

$ git config --global user.email "vishnuharidadhich@gmail.com"

$ git config --list
user.name=Vishnu Dadhich
user.email=vishnuharidadhich@gmail.com

The above info is not the authentication information.

What is the need of git config ?

When we setup git and before adding bunch of files, we need to fill up username & email and it’s basically git way of creating an account.

This is important because every Git commit uses this information, and it’s immutably baked into the commits you start creating.

Basic Git Workflow

  1. You modify files in working directory.
  2. You stage files, adding snapshots of them to your staging area.
  3. You do a commit, which takes the files as they are in the staging area and stores that snapshot to your git repository.

Working with Git

git init :

Initialising a repository into directory :

$ mkdir website

$ cd website/

$ git init
Initialized empty Git repository in /home/dvops/website/.git/

The purpose of Git is to manage a project, or a set of files, as they change over time. Git stores this information in a data structure called a repository.

git init is only for when you create your own new repository from scratch.

It turns a directory into an empty git repository.

git status :

$ git status
# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)

The git status command displays the state of the working directory and the staging area. It lets you see which changes have been staged, which haven’t, and which files aren’t being tracked by GitStatus output does not show you any information regarding the committed project history.

git add :

Lets add a file in the working directory :

$ vi index.html

$ cat index.html
<!DOCTYPE html>
<html>
<body>

<h1>Hello World!</h1>

</body>
</html>

Get the current status :

$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       index.html
nothing added to commit but untracked files present (use "git add" to track)

Let’s add the file to staging :

$ git add index.html

Now, move file from staging area to local repository :

$  git commit -m "adding index.html"
[master (root-commit) 37ae38f] adding index.html
 1 file changed, 9 insertions(+)
 create mode 100644 index.html

Check the status :

$ git status
# On branch master
nothing to commit, working directory clean

You can alternatively skip the staging step by :

$ git commit -a -m "New Changes" 

It basically commits all files changed since your last commit, except untracked files (ie. all files that are already listed in the staging).

git log :

To see what commits have been done so far we use this command :

$ git log
commit 37ae38f9b653c1e33395583fea6199e802a71005
Author: Vishnu Dadhich <vishnuharidadhich@gmail.com>
Date:   Tue Mar 26 15:42:34 2019 +0000

    adding index.html

It gives commit history basically commit number, author info, date and commit message.

Lets add another file and view the logs :

$ touch test.txt
$ git add .
$ git commit -m "adding test.txt"
[master 5886ba1] adding test.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test.txt

$ git log
commit 5886ba19ed48e1cdb212d3656975ea8eb30ed539
Author: Vishnu Dadhich <vishnuharidadhich@gmail.com>
Date:   Tue Mar 26 15:53:01 2019 +0000

    adding test.txt

commit 37ae38f9b653c1e33395583fea6199e802a71005
Author: Vishnu Dadhich <vishnuharidadhich@gmail.com>
Date:   Tue Mar 26 15:42:34 2019 +0000

    adding index.html

Which gives the latest commit on top and old will get down.

Some other helpful log commands include :

# git log --since YYYY-MM-DD 
# git log --author dvops 
# git log --grep HTML  { commit message } 
# git log --oneline 

git show :

Want to see what happened at this commit, zoom in info we use:

$ git show 5886ba19ed48e1cdb212d3656975ea8eb30ed539
commit 5886ba19ed48e1cdb212d3656975ea8eb30ed539
Author: Vishnu Dadhich <vishnuharidadhich@gmail.com>
Date:   Tue Mar 26 15:53:01 2019 +0000

    adding test.txt

diff --git a/test.txt b/test.txt
new file mode 100644
index 0000000..e69de29

Let’s understand this ​commit number.

This is SHA-1 value randomly generated number which is 40 character hexadecimal number which will be unique.

git diff :

The diff command gives the difference b/w two commits. To showcase this, lets make a change in our existing file and commit :

$ echo "dvops.cloud" > test.txt
$ git add .
$ git commit -m "updating test.txt"
[master 888ba56] updating test.txt
 1 file changed, 1 insertion(+)

Now lets diff :

$ git diff 888ba563ed2836fn5ce3fjd66tc9ae195c37a10f..5886ba19eh58ekcdy212d3656975ea8eb30ed539
diff --git a/test.txt b/test.txt
index 268cfd3..e69de29 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +0,0 @@
-dvops.cloud

Or if you want to diff the changes before adding it to the staging area :

$ echo "This is another line" >> test.txt

$ git diff
diff --git a/test.txt b/test.txt
index d55e518..a430df2 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
 devops.cloud
+This is another line

Git Branching

In a collaborative environment, it is common for several developers to share and work on the same source code.

Some developers will be​ ​fixing bugs​ ​while others would be​ ​implementing new features​.

Therefore, there has got to be a manageable way to​ ​maintain different versions​ ​of the same code base.

This is where the branch function comes to the rescue.​ ​Branch allows​ ​each developer​ ​to branch out from the original code base and isolate their work from others.​ ​Another good thing about branch is that​ ​it helps Git to easily merge​ ​the versions later on.

It is a common practice to create a new branch for each task (eg. bug fixing, new features etc.)

Branching means you diverge from the main line (​master-working copy of application​) of development and continue to do work without messing with that main line.

Basically, you have your master branch and you don’t want to mess anything up on that branch.

In many VCS tools, ​branching ​is an ​expensive process​, often requiring you to create a new copy of your source code directory, which can take a long time for large projects. Some people refer to ​Git’s branching model as its “​killer feature​” ​and it certainly sets Git apart in the VCS community.

Why is it so special?

The way Git branches is incredibly ​lightweight​, making branching operations nearly ​instantaneous​, and switching back and forth between branches generally just as fast.

When we make a commits, this is how git stores them :

A branch in Git is simply a lightweight ​movable pointer​ to one of these commits. The default branch name in Git is ​master​. As you start making commits, you’re given a master branch that points to the last commit you made. Every time you commit, it moves forward automatically.

GIT branch

It will tell you the current branch you are working on :

$ git branch
* master

What happens if you create a new branch? Well, doing so creates a new pointer for you to move around.

Let’s say you create a new branch called testing :

$ git branch testing

This creates a new pointer to the same commit you’re currently on.

Two branches are pointing into the same series of commits.

How does Git know what branch you’re currently on?

It keeps a ​special pointer ​called ​HEAD​.

HEAD is a pointer to the latest commit id and is ​always moving​, not stable.

$ git show HEAD
commit 0974c7838af3b37013aa1f760e7a6a6c02ecce49
Author: Vishnu Dadhich <vishnuharidadhich@gmail.com>
Date:   Tue Mar 26 16:28:25 2019 +0000

    updating test.txt

diff --git a/test.txt b/test.txt
index 268cfd3..d55e518 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-dvops.cloud
+devops.cloud

In Git, this is a pointer to the local branch you’re currently on.

In this case, you’re still on master. The git branch command only created a new branch — it didn’t switch to that branch.

This command shows you ​where the branch pointers are pointing:

$ git log --oneline --decorate
0974c78 (HEAD, testing, master) updating test.txt
888ba56 updating test.txt
5886ba1 adding test.txt
37ae38f adding index.html

You can see the “HEAD“, “​master​” and “​testing​” branches that are right there next to the 0974c78 commit, which is also the latest one.

git checkout :

To ​switch to an existing branch​, you run the git checkout command :

$  git checkout testing
M       test.txt
Switched to branch 'testing'

This ​moves HEAD​ to point to the ​testing​ branch.

What is the significance of that ?

Well, let’s do another commit:

$ echo "Hello again." > hello.txt

$ git commit -a -m 'added hello.txt'
[testing 0c413b0] added hello.txt
 1 file changed, 1 insertion(+)

Lets look at the status of various branches now :

$ git log --oneline --decorate
0c413b0 (HEAD, testing) added hello.txt
0974c78 (master) updating test.txt
888ba56 updating test.tx
5886ba1 adding test.txt
37ae38f adding index.html

The HEAD branch moves forward when a commit is made.

This is interesting, because now your ​testing​ branch has ​moved forward, but your master branch still points to the commit you were on when you ran git checkout to switch branches.

Let’s switch back to the master branch :

$ git checkout master
Switched to branch 'master'

HEAD moves when you checkout.

That command (git checkout master) did two things. It ​moved​ the ​HEAD pointer back to point to the ​master branch​, and it ​reverted the files​ in your working directory ​back to the snapshot​ that ​master points to​.

Which means, if you now do a git status, it should show you an untracked file (hello.txt) :

$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       hello.txt
nothing added to commit but untracked files present (use "git add" to track)

Let’s make a few changes and commit again:

$ echo "Hello there." > hello.txt
$ git add .
$ git commit -m 'added hello.txt'
[master 13bd01b] added hello.txt
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt

Now your project history has diverged.

You created and switched to a branch, did some work on it, and then switched back to your main branch and did other work.

Both of those changes are isolated in separate branches: you can switch back and forth between the branches and merge them together when you’re ready.

And you did all that with simple ​branch​, ​checkout ​and ​commit commands.

To see all available branches :

$ git branch -a
* master
  testing

To see short logs of your recent activities :

$ git reflog
13bd01b HEAD@{0}: commit: added hello.txt
0974c78 HEAD@{1}: checkout: moving from testing to master
0c413b0 HEAD@{2}: commit: added hello.txt
0974c78 HEAD@{3}: checkout: moving from master to testing
0974c78 HEAD@{4}: commit: updating test.txt
888ba56 HEAD@{5}: commit: updating test.tx
5886ba1 HEAD@{6}: commit: adding test.txt
37ae38f HEAD@{7}: commit (initial): adding index.html

Merging :

Attach changes from one branch to another.
There are two types of merges:

  • Fast forward merge
  • Recursive merge

Merging is very important feature in git, when we want to club our work with other developers merging is needed.

$ git checkout master
$ git merge <branch-name>

Fast Forward :

  • Doesn’t create a Commit ID.
  • Uses previous latest Commit ID of a particular branch to do a merge.

Recursive :

  • Creates a new Commit ID.
  • Merging is not in our control and can go disastrous.

Git Merge Conflict

A merge conflict happens when two branches modify the same region of a file and are subsequently merged. Git can’t know which of the changes to keep, and thus it needs human intervention to resolve the conflict.

# On master branch

$ echo "This is DevOps" > devops.html
$ git branch development
$ git branch operations

# On development branch

$ git checkout development
Switched to branch 'development'

$ echo "This is Development" > devops.html
$ git add .
$ git commit -m "development commit"
[development 3f3285a] development commit
 1 file changed, 1 insertion(+)
 create mode 100644 devops.html

# On master branch

$ git checkout master
Switched to branch 'master'

$ git merge development
Updating 13bd01b..3f3285a
Fast-forward
 devops.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 devops.html

As we can see, we have merged the changes in a Fast-forward fashion on to master branch from development branch.

# On operations branch

$ git checkout operations
Switched to branch 'operations'

$ echo "This is Operations" > devops.html
$ git add .
$ git commit -m "operations commit"
[operations bc43ce9] operations commit
 1 file changed, 1 insertion(+)
 create mode 100644 devops.html

# On master branch

$ git checkout master
Switched to branch 'master'
$ git merge operations
Auto-merging devops.html
CONFLICT (add/add): Merge conflict in devops.html
Automatic merge failed; fix conflicts and then commit the result.

As we can see, automatic merge failed. It recommends us to fix conflicts and then commit the result.

To resolve we will have to get rid of the changes merged from development branch and merge these ones :

$ git status
# On branch master
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#
#       both added:         devops.html
#
no changes added to commit (use "git add" and/or "git commit -a")

$ git add .

$ git commit -m "resolving conflicts"
[master 905f6d3] resolving conflicts
$ git merge operations
Already up-to-date.

Git Ignore

It’s a list of files you want git to ignore in your working directory.

It’s usually used to avoid committing transient files from your working directory that aren’t useful to other collaborators such as temp files IDE’s create, ​Compilation files, ​OS files etc.

A file should be ignored if any of the following is true:

  • The file is not used by your project
  • The file is not used by anyone else in your team
  • The file is generated by another process

Example :

db.properties
server.properties

$ vi .gitignore {add *.properties}

Now, all .properties files will not be tracked.

To ignore all php files add following to the .gitignore file :

*.php

but not index.php :

!index.php

Ignore all text files that start with aeiou :

[aeiou]*.txt

Merge vs. Rebase

Rebasing and merging are both designed to integrate changes from one branch into another branch but in different ways.

For ex. let’s say we have commits like below, the merge will result as a combination of commits, whereas rebase will add all the changes in feature branch starting from the last commit of the master branch:

Source
  • When you do rebase a feature branch onto master, you move the base of the feature branch to master branch’s ending point.
  • Merging takes the contents of the feature branch and integrates it with the master branch. As a result, only the master branch is changed. The feature branch history remains same.
  • Merging adds a new commit to your history.

Stashing

Consider this situation, you are working on some feature and didn’t commit changes yet and suddenly your manager comes and wants you to work on a bug fix which is in another branch, in this scenario we use git stash. Stashing is handy if you need to quickly switch context and work on something else. but you’re mid-way through a code change and aren’t quite ready to commit

Stashing is handy if you need to quickly switch context and work on something else. but you’re mid-way through a code change and aren’t quite ready to commit

git stash command takes your uncommitted changes (both staged and unstaged), saves them away for later use and then reverts them when u want.

Lets add another file and add it to the staging area :

$ echo "DevOps : Culture and Process" > dvops.cloud

$ git add .

Currently the git status shows us one uncommitted change :

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   dvops.cloud
#

Stash it using the command :

$ git stash
Saved working directory and index state WIP on master: 905f6d3 resolving conflicts
HEAD is now at 905f6d3 resolving conflicts

Now the git status is clean :

$ git status
# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)

We can list the stashes with :

$ git stash list
stash@{0}: WIP on master: 905f6d3 resolving conflicts

To apply the top most stashed changes :

$ git stash apply
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   dvops.cloud
#

git status should again show us the uncommitted changes :

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   dvops.cloud
#

Some other operations that we can perform are :

# git stash apply stash@{2}​ ​{apply particular stashed changes} 
# git stash show <stash> 
# git stash pop​ ​{apply 2nd stash and remove it} 
# git stash pop stash@{2}​ ​{pop the stash at 2nd reference} 
# git stash drop stash@{3}​ ​{remove the stash} 
# git stash clear​ ​{Delete all stash entries}

Tagging

In release management we are working as a team and I’m working on a module and whenever I’m changing some files I’m pushing those files to remote master.

Now I have some 10 files which are perfect working copy, and I don’t want this files to be messed up by my other team members, these 10 files they can directly go for release.

But if I keep them in the repository, as my team is working together, there is always a chance that, somebody or other can mess that file, so to avoid these we can do​ ​TAGGING​.

You can tag till a particular commit id : ​

$ git tag 1.0 -m "Release 1.0" 905f6d31adfdb8ba1934047c34df246aa2f41cbd
$ git show 1.0
tag 1.0
Tagger: Vishnu Dadhich <vishnuharidadhich@gmail.com>
Date:   Tue Mar 26 20:16:08 2019 +0000

Release 1.0

commit 905f6d31adfdb8ba1934047c34df246aa2f41cbd
Merge: 3f3285a bc43ce9
Author: Vishnu Dadhich <vishnuharidadhich@gmail.com>
Date:   Tue Mar 26 19:31:26 2019 +0000

    resolving conflicts

diff --cc devops.html
index 9f60528,2437097..3488320
--- a/devops.html
+++ b/devops.html
@@@ -1,1 -1,1 +1,5 @@@
++<<<<<<< HEAD
 +This is Development
++=======
+ This is Operations
++>>>>>>> operations

Lets add our remote repository to push the changes to it.

$ git remote add origin https://github.com/vishnuhd/git-quickie.git

Now lets push our tag to remote repo :

$ git push --tags
Username for 'https://github.com': vishnuhd
Password for 'https://vishnuhd@github.com':
Counting objects: 25, done.
Compressing objects: 100% (18/18), done.
Writing objects: 100% (25/25), 2.35 KiB | 0 bytes/s, done.
Total 25 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
To https://github.com/vishnuhd/git-quickie.git
 * [new tag]         1.0 -> 1.0

Goto GitHub and see release click on it, you can download all the files till that commit.

TAGGING​ helps you in ​release management​.

Difference between git fetch and git pull?

Before we talk about the differences between these two commands, let’s stress their similarities: both are used to download new data from a remote repository.

Downloading data is an essential step in your daily work – because the remote data you are looking at in your local repository is just a “snapshot”. It’s only as up-to-date as the last time you explicitly downloaded fresh data from the remote with “fetch” or “pull”. It’s vital to keep this fact in mind when inspecting remote branches and commits!

Let’s now look at the fine but important differences between “fetch” and “pull”.

Fetch

$ git fetch origin

git fetch really only downloads new data from a remote repository – but it doesn’t integrate any of this new data into your working files. Fetch is great for getting a fresh view on all the things that happened in a remote repository.
Due to it’s “harmless” nature, you can rest assured: fetch will never manipulate, destroy, or screw up anything. This means you can never fetch often enough.

Pull

$ git pull origin master

git pull, in contrast, is used with a different goal in mind: to update your current HEAD branch with the latest changes from the remote server. This means that pull not only downloads new data; it also directly integrates it into your current working copy files. This has a couple of consequences:

  • Since “git pull” tries to merge remote changes with your local ones, a so-called “merge conflict” can occur.
  • Like for many other actions, it’s highly recommended to start a “git pull” only with a clean working copy. This means that you should not have any uncommitted local changes before you pull. Use Git’s Stash feature to save your local changes temporarily.

In simplest terms :

When you use pull, Git tries to automatically do your work for you. It is context sensitive, so Git will merge any pulled commits into the branch you are currently working in. pullautomatically merges the commits without letting you review them first. If you don’t closely manage your branches, you may run into frequent conflicts.

When you fetch, Git gathers any commits from the target branch that do not exist in your current branch and stores them in your local repository. However, it does not merge them with your current branch. This is particularly useful if you need to keep your repository up to date, but are working on something that might break if you update your files. To integrate the commits into your master branch, you use merge.

Undoing with Git Commands

Git toolbox provides multiple unique tools for fixing up mistakes during your development. Commands such as git resetgit checkout, and git revert allow you to undo erroneous changes in your repository.

Because they perform similar operations, it is very easy to mix them up. There are a few guidelines and rules for when each command should and should not be used. Let’s take a look!

Be careful! You can’t always redo after an undo. This is one of the few areas in Git where you may lose some work if you do it wrong.

I will start off by clarifying the main differences between these three commands.

Checkout:

  • Use this to move the HEAD pointer to a specific commit or switchbetween branches.
  • It rollbacks any content changes to those of the specific commit.
  • This will not make changes to the commit history.
  • Has potential to overwrite files in the working directory.

Revert:

  • Rollback changes you have committed.
  • Creates a new commit from a specified commit by inverting it. Hence, adds a new commit history to the project, but it does not modify the existing one.
  • Has the potential to overwrite files in the working directory.

Reset:

  • Use this to return the entire working tree to the last committed state. This will discard commits in a private branch or throw away uncommitted changes!
  • Changes which commit a branch HEAD is currently pointing at. It alters the existing commit history.
  • Can be used to unstage a file.

Every command lets you undo some kind of change in your repository, only checkout and reset can be used to manipulate either commits or individual files.

Using the Commands

There are many different ways you can undo your changes, it all depends on the current scenario. Selecting an appropriate method depends on whether or not you have committed the change by mistake, and if you have committed it, whether you have shared it or not.

Undo Public Changes

Scenario: Image that you did git push in hotfix branch for commits you didn’t want to make yet.

Solution: The safest way to fix this is by reverting your changes since it doesn’t re-write the commit history.

$ git checkout hotfix
$ git revert HEAD~1

Result: You have successfully undone committed changes! Everything that was changed in the old commit will be reverted with this new commit. Git forces you to commit or stash any changes in the working directory that will be lost during the checkout.

You can think of git revert as a tool for undoing committed changes, while git reset HEAD is for undoing uncommitted changes.

Undo Local Changes

Scenario: You started working on a feature, but you didn’t like the end result. These changes haven’t been **shared* with anyone else.*

Solution: You want to undo everything in that files to the previous state, just the way it looked in the last commit.

$ git checkout file_name.rb

Result: File file_name.rb has been reverted to a state previously known to Git. Note that this removes all of the subsequent changes to the file!

You can use git checkout branch_name to switch between branches. Git forces you to commit or stash any changes in the working directory that will be lost during the checkout operation.

Undo Private Changes

Scenario: You’ve made some commits locally in the hotfix branch but everything is terrible! You want to remove the last two commits from the current branch.

Solution: Reset the hotfix branch backward by two commits as if those commits never happened.

$ git checkout hotfix
$ git reset HEAD~2

Result: Your git repository has been rewinded all the way back to the specified commit. Those left out commits are now orphaned and will be removed the next time Git performs a garbage collection. For now, then their contents are still on disk.

You can tell Git what to do with your index (set of files that will become the next commit) and working directory when performing git reset by using one of the parameters:

  • --soft: Tells Git to reset HEAD to another commit, so index and the working directory will not be altered in any way. All of the files changed between the original HEAD and the commit will be staged.
  • --mixed: Just like the soft, this will reset HEAD to another commit. It will also reset the index to match it while working directory will not be touched. All the changes will stay in the working directory and appear as modified, but not staged. >The main difference between --mixedand --soft is whether or not your index is also modified. Check more on git-reset-guide.
  • --hard: This resets everything – it resets HEAD back to another commit, resets the index to match it, and resets the working directory to match it as well.

Tips and Tricks

There are two additional things that can come in handy during your Git adventures.

Fix the Previous Commit Message

Scenario: Everyone makes typo mistakes when writing commits and this is completely fine! It can be easily fixed before you do a git push.

Solution: Just run git commit --amend or git commit --amend -m 'The new message'. This will update and replace the most recent commit with a new commit.

Redo After Undo

Scenario: You have done a git reset --hard for some unwanted changes, but then you realized that you actually needed them.

Solution: git reflog comes to your rescue! It is an amazing command for recovering project history and it can recover almost anything.


Hope this three tools will help you whenever you need to undo your recent changes.

Undoing with Git Commands section of this article is originally published on Kolosek Blog.

Interactive Git Cheatsheet

Check out this interactive Git Cheatsheet by NDP Software.

Thanks for reading out !

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s