Git reflog – The git lifesaver

Git command is a very powerful tool, allowing you to do practically everything to the git repository you’re using. As it is powerful, it is also very non-user friendly, even compared to other Linux commands. And so, you can easily f!^k things up.

I’m here to show you a very useful tool, for the case you did something stupid… localy… if you were talented enough to push your mistake (we all have done it at some point), then only the repository’s owner will help you, and you can only hope that he’s mercyfull.
[Actually, you can probably do something like “git revert” and push, but you’re mistake(s) will be visible to all. If someone knows of a “quiet” way of reverting erroneous commits from the remote repository, I’ll be glad to learn]

Introducing “git reflog” – This command will show you a list of the recent operation you have done on the local repository, similar to “git log –oneline”, except instead of a list of commits, you see a list of changes, and each change has a “SHA” – The long hex which uniquely identify it – Just like a commit. All that is left to do in order to revert to the desired point is “git reset –hard <change #>”.

And now, an example.
I created a dummy repository, and a main.c that prints one line of text, here is the our repository’s relog so far:

$ git reflog
0f26690 HEAD@{0}: commit: Add printout
aacbdb6 HEAD@{1}: commit: Add new file: "main.c"
0fc0894 HEAD@{2}: commit (initial): Initial commit

Now I made a “light mistake”, and amended the last commit with changes I did not intend on inserting:

$ vim main.c 
$ git add main.c 
$ git commit --amend
[master e6ea730] Add printout
 Date: Sun Dec 4 00:25:06 2016 +0200
 1 file changed, 3 insertions(+)

Oops… We can fix this by “reset HEAD~1”, erasing the unwanted text, and committing (again) only the wanted code. But it would be easier to fix with relog.
Here’s the reflog:

$ git reflog
  e6ea730 HEAD@{0}: commit (amend): Add printout
  0f26690 HEAD@{1}: commit: Add printout
  aacbdb6 HEAD@{2}: commit: Add new file: "main.c"
  0fc0894 HEAD@{3}: commit (initial): Initial commit

We can see the diff between the last good commit and the “amend” that messed it up:

$ git diff 0f26690 e6ea730
diff --git a/main.c b/main.c
index a29c03d..6c261d7 100644
--- a/main.c
+++ b/main.c
@@ -2,7 +2,7 @@

int main()
{
    -    printf("This is a dummy program\n");
    +    printf("This is a dummy program\n"); this is a mistake

        return 0;
}

And now we can revert to the desired point:

git reset --hard 0f26690
HEAD is now at 0f26690 Add printout

Looking at the log we can see these commits:

$ git log --oneline
0f26690 Add printout
aacbdb6 Add new file: "main.c"
0fc0894 Initial commit

But only the desired changes are in the last commit:

$ git diff 0f26690^!
diff --git a/main.c b/main.c
index 31a1337..a29c03d 100644
--- a/main.c
+++ b/main.c
@@ -1,5 +1,8 @@
+#include 

int main()
{
    +    printf("This is a dummy program\n");
    +
        return 0;
}

This is, of course, just a simple example. But we can use “reflog” to fix real damage we made by, for example, using “git rebase” (excellent tool for messing up your repository) or by any other means.

Till next time,
Amnon.

Detecting defective tarballs (from a list of files)

There’s this build system, where tar files are downloaded into a specific folder, and then extracted, compiled and whatever. There are several thread, each one downloads, extracts, compiles and installs, and suddenly, there is no more space left on the hard drive. After a little cleanup for extra space, trying to rebuild fails, because some threads were in the middle of downloading when stopped violently, so a few tar files were corrupted – We need to delete them before going on.

Now, there are tens of files, checking them one by one will take a long time, and will be very tedious, better clean and rebuild, even if it takes a day ­čÖé

However, we can use a bash script for detecting defected tarballs!

First, how can we tell if a tar file is defected?

Tar’s “-t” option gives us a list of the stored files. It’s pretty quick, and when the file is corrupted, it fails, like so:

$ tar -tf not_a_tar_file.tar.gz 
tar: This does not look like a tar archive

gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now

$ echo $?
2

The bash variable “$?” gives us the result of the last execution. “Pass” is (obviously) zero, and any other value is “Fail”. So we can use this information.

Improving this for a single file, we’ll send the output, including stderr, to /dev/null, using a┬áredirection: “&> /dev/null”

Then, all we have to do is to put it into a loop, add some command line input, and we get this short script:

#!/bin/bash

if [ -z "$1" ] ; then
    DIR=`pwd`
else
    ls $1 &> /dev/null
    if [ "$?" -ne "0" ] ; then
        echo "No such path: "$1
        exit 1
    fi

    DIR=$1
fi

FILES_LIST=`ls $DIR`
for FILE in $FILES_LIST ; do
    FULLNAME=$DIR/$FILE 
    tar -tf $FULLNAME &> /dev/null
    if [ "$?" -eq "0" ] ; then
        echo $FULLNAME" is OK" 
    else
        echo "[!]" $FULLNAME "is defiective"
    fi
done

And its execution looks like this:

$ ./verify_my_tarballs.sh ./list

./list/backup.tar.gz is OK
[!] ./list/not_a_tar_file.tar.gz is defiective
./list/one_tar.tar.gz is OK
./list/onther_one.tar.gz is OK
./list/project1.tar.gz is OK

Notice that this script is good only for tar files. We can probably expand it to handle zip files and other archive file types, by using the same concept

* No tar files were harmed during the making of this post.

Amnon.