Posts tagged 'git'

Use git reflog to split two squashed commits

published on February 08, 2019.

Today I was using interactive git rebase to squash some commits together to clean up the commit history of a git branch. At one point I went a bit overboard with it and squashed together two commits by mistake.

I’ve heard somewhere from someone that in git you pretty much can’t lose code because everything is in the git reflog. Today I decided to put that to the test.

Spoiler alert: git reflog saved the day.

The setup

We start with two separate commits, “Commit 1” and “Commit 2”:

> git hist
* c3fa1d6 -  (HEAD -> reflog-undo-squash) Commit 2 [Robert Basic 4 seconds ago]
* db73960 -  Commit 1 [Robert Basic 56 seconds ago]

Using git rebase -i HEAD~2 we start the interactive rebase for the last two commits:

pick db73960 Commit 1
f c3fa1d6 Commit 2

We choose to fixup “Commit 2” with “Commit 1”, which means that the second commit will be squashed with the first commit, discarding the commit message of the second commit.

Uh oh, that was a mistake, and our commit history now looks like this:

> git hist
* 786865f -  (HEAD -> reflog-undo-squash) Commit 1 [Robert Basic 5 minutes ago]

Our nice little “Commit 2” is now gone and before we start panicking and making more damage, let’s take a look at the git reflog to see what we have there.

Looking at the reflog

The git reflog right after the bad rebase shows us the following:

> git reflog
786865f (HEAD -> reflog-undo-squash) HEAD@{0}: rebase -i (finish): returning to refs/heads/reflog-undo-squash
786865f (HEAD -> reflog-undo-squash) HEAD@{1}: rebase -i (fixup): Commit 1
0a8f291 HEAD@{2}: rebase -i (pick): Commit 1
684d689 HEAD@{3}: rebase -i (pick): Commit 1
5761a21 HEAD@{4}: rebase -i (start): checkout 5761a21b0c0a1f12ab1c60bfef1f1d111ba699c0
c3fa1d6 HEAD@{5}: commit: Commit 2
db73960 HEAD@{6}: commit (initial): Commit 1

The thing at HEAD@{6} was the first thing that happened and the thing at HEAD@{0} is whatever is going on right now.

What we want the current state to be is the state before we started the rebase process, that is the state at HEAD@{5}. To do that we “reset” our state to that point in time:

> git reset HEAD@{5}

And now our git history is back to it’s pre-rebase state:

> git hist
* c3fa1d6 -  (HEAD -> reflog-undo-squash) Commit 2 [Robert Basic 15 minutes ago]
* db73960 -  Commit 1 [Robert Basic 16 minutes ago]

Note that even the commit shas are back to their pre-rebase values, db73960 and c3fa1d6.

The Atlassian git reflog tutorial goes into more detail, so make sure to read that as well.

Happy hackin’!

Tags: git, reflog, squash, rebase.
Categories: Development, Software.

Anatomy of a git diff

published on December 20, 2016.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

I’m looking at git diffs every day, all day. Diffs hold a lot of information that can be valuable, and I think it’s a good thing to know how to fully read a git diff.

A simple diff looks something like this:

diff --git a/example.php b/example.php
index a5174a9..11aeb84 100644
--- a/example.php
+++ b/example.php
@@ -11,7 +11,10 @@ class Greeter
         $this->name = $name;
     }
 
-    public function greet()
+    /**
+     * Return the greeting message.
+     */
+    public function greet() : string
     {
         return sprintf("Hello %s" . PHP_EOL, $this->name);
     }

This simple example holds most of the information that is needed from a diff.

The first line tells that the diff is in the git format and the filename(s) before and after the changes.

The second line tells about the type of file and its permissions (100644) and the two hashes are just that - shortened hashes of the pre- and post-images. AFAIK, this line is used when doing 3-way merges.

The lines 3 and 4 again deal with the name of the file(s). If it’s a new file, the source - --- - is /dev/null, and if an existing file was deleted, the target - +++ - will be /dev/null.

I’ll skip line 5 for a moment and come back to it later.

The next part, the actuall diff, shows what lines in the current hunk were added and what lines were removed:

         $this->name = $name;
     }
 
-    public function greet()
+    /**
+     * Return the greeting message.
+     */
+    public function greet() : string
     {
         return sprintf("Hello %s" . PHP_EOL, $this->name);
     }

Lines that start with a + sign were added, and lines with a - sign were removed. Lines with no + or - are here just to give us some context of the code. Looking at this diff we see that one line was removed and 4 lines were added.

Now back to line 5 as this is probably the hardest part to understand:

@@ -11,7 +11,10 @@ class Greeter

These numbers always seemed random to me.

This line is, I belive, called “unified diff hunk identifier”, and the format of this line is:

@@ from-file-range to-file-range @@ [header]

which to be honest, isn’t that helpful. The @ signs are just delimiters.

The first pair of numbers, -11,7, means that the current hunk in the source file starts at line 11 and has a total of 7 lines.

The starting line can be confirmed in any editor: $this->name = $name; really is the 11th line in the edited file. That’s easy.

The number 7 means that there are 7 lines in total that have a - sign or no sign at all (contextual lines). If we count the number of contextual lines and lines with a -, skipping the lines with the + at the beginning, we see the total is 7.

The second pair of numbers, +11,10 means that the current hunk in the target file starts at line 11 and has a total of 10 lines.

The number 10 means that there are 10 lines in total that have a + sign or no sign at all (contextual lines). If we count the number of contextual lines and lines with a +, skipping the lines with the - at the beginning, we see the total is 10.

Finally, class Greeter, the [header] part of this line, tells us were did the change happen. This may, or may not, be present.

This example is a simple one, but I think it covers most of the use cases of a git diff output, and it helps us understand the unified diff hunk identifier line (the line with @@s), which is useful to know, especially when editing git hunks manually.

Happy hackin’!

Tags: git, diff, hunk.
Categories: Development, Software.

Verbose commiting

published on December 12, 2016.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

One thing I recently learned about git, is the -v or --verbose flag for the git commit command. It shows the diff of what is being commited in $EDITOR below the commit message template. Taken directly from man git commit:

Show unified diff between the HEAD commit and what would be committed at the bottom of the commit message template to help the user describe the commit by reminding what changes the commit has. Note that this diff output doesn’t have its lines prefixed with #. This diff will not be a part of the commit message.

I keep double checking the code that I commit, so prior to discovering this flag, I was constantly switching between writing the commit message and seeing what’s in the diff. This now gives me the diff inside vim, as that is my specified $EDITOR. I can navigate the diff using vim motions, use search, etc, which greatly improves my workflow.

Happy hackin’!

Tags: git, message, verbose, diff.
Categories: Software, Development.
Robert Basic

Robert Basic

Software engineer, consultant, open source contributor.

Let's work together!

If you require outsourcing or consulting help on your projects, I'm available!

Robert Basic © 2008 — 2019
Get the feed