Git is amazing. At Unlaunch, we use GitHub to store our code (in various repositories for backend service, various SDKs, frontend) and collaborate. While Git is versatile, it is also very complex and it is easy to make mistakes with getting immediate feedback (even experts with several years of experience do!) Over the past few weeks, I have forgot to add a file to the commit, had to fix the commit message, had to discard changes to a file, to name a few.

In this post, we’ll explore a few basic commands for undoing changes that you have made and save your neck.

Fix commit message or add missing files

If you accidently run git commit with the wrong commit message or too early forgetting to add some files to it, you can easily redo the commit using the --amend option. It basically takes your staging area and merges it with the previous commit creating a single commit which replaces the previous commit entirely (won’t show up in the history.) Use git ammend for minor improvements to your last commit without cluttering up the commit history.

Example

In the example below, suppose we forgot to add index.css to the commit.

$ git add index.html
$ git commit -m 'updated the web app'
$ git add index.css
$ git commit --amend


Notes

  • Use --amend for minor improvements to your last commit such as fixing commit messages like these Oops... forgot to add a file or forgot to add ticket num to the last commit.
  • It only works if you haven’t pushed your changes to the remote repository.
  • Use case: Undo commit message.
  • Use case: Forgot to add files in the commit.

Remove files from staging area aka undo git add

If you have accidently staged a file using git add, you can unstage it using git reset HEAD <filename>. It will just unstage files and you’ll get to keep your changes in the working directory.

Example

In the example below, suppose we accidently stage overview.jsp and want to remove it.

$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
  modified:   index.html
  modified:   overview.jsp


In the output of git status we see that overview.jsp has been staged. Let’s unstage it usign git reset.

$ git reset HEAD overview.jsp
Unstaged changes after reset:
M overview.jsp
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
  modified:   index.html
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  modified:   overview.jsp
</pre>
The file `overview.jsp` is removed. We can now go ahead with the commit.
<pre class="prettyprint lang-sh">
$ git commit -m 'updated index.html'
[master f34a21e] updated index.html
 1 file changed, 1 insertion(+)


Notes

  • The git status output tells you how to unstage your changes. So if you ever forget the syntax, just use git status.
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
  • To unstage all files, just run git reset without any arguments.
$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
  modified:   index.html
  modified:   overview.jsp
$ git reset
Unstaged changes after reset:
M index.html
M overview.jsp
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  modified:   index.html
  modified:   overview.jsp
no changes added to commit (use "git add" and/or "git commit -a")


Undo all changes to a file

If you want to discard all your changes to a file and go back to what it looked like in the last commit, you can use the git checkout -- <filename> command.

Example

In the example below, suppose we messed up index.html and want to undo all changes and revert it back to what it looked like when we last pulled or cloned it.

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  modified:   index.html
no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout -- index.html
$ git status
On branch master
nothing to commit, working tree clean


Notes

  • Be careful. git checkout is a dangerous command and your changes will be lost forever.

Discard partial changes to a file

Let’s say you want to only commit some lines in a file while ignoring others. Maybe you only like to keep partial changes or had to disable some functionality (comment out some lines) to make the code run on your local machine and only want to choose which lines to stage and commit. To do this you can run git add --patch <filename> and choose which portions (known as hunks) of files to keep and which ones to discard. It will bring up an interactive prompt asking you which changes you’d like to keep or discard. This command will only remove your changes from going to the staging area but keep them in your working directory. If you run git status after this, it’ll still show the file as modified.

Let’s see an example where we’d select what changes to keep in a file. Git usually does a good job of grouping things together but you can use s to split even further as I did in the example below.

$ git add --patch index.js
diff --git a/index.js b/index.js
index 4e4300f..18d5a2f 100644
--- a/index.js
+++ b/index.js
@@ -41,14 +41,14 @@ class NameValueFields extends React.Component {
     }
     removeField = event => {
-        let indexToRemove = event.target.id;
-        this.fields = this.fields.filter( (v,i) => i != indexToRemove)
+        let index = event.target.id;
+        this.fields = this.fields.filter( (v,i) => i != index)
         this.props.onChange(this.fields);
     }
     updateText = (index, name, value) => {
-        const o = this.fields[index];
-        o[name] = value;
+        const txt = this.fields[index];
+        txt[name] = value;
         this.props.onChange(this.fields);
     }
Stage this hunk [y,n,q,a,d,s,e,?]? s
Split into 2 hunks.
@@ -41,9 +41,9 @@
     }
     removeField = event => {
-        let indexToRemove = event.target.id;
-        this.fields = this.fields.filter( (v,i) => i != indexToRemove)
+        let index = event.target.id;
+        this.fields = this.fields.filter( (v,i) => i != index)
         this.props.onChange(this.fields);
     }
     updateText = (index, name, value) => {
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -46,9 +46,9 @@
         this.props.onChange(this.fields);
     }
     updateText = (index, name, value) => {
-        const o = this.fields[index];
-        o[name] = value;
+        const txt = this.fields[index];
+        txt[name] = value;
         this.props.onChange(this.fields);
     }
Stage this hunk [y,n,q,a,d,K,g,/,e,?]? n
$ git commit -m 'updates for 6785'
[master c4690d2] updates for 6785
 1 file changed, 2 insertions(+), 2 deletions(-)
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  modified:   index.js
no changes added to commit (use "git add" and/or "git commit -a")


That’s all. I hope you enjoyed this post. Please check out unlaunch.io. Unlaunch is a free feature flag service for software developers with a mission to make feature releases less stressful and more pleasant. It is being developed by a passionate team of software developers.

Author: This blog was written by Umer Mansoor.