Alok Menghrajani

Security engineer at Square. Previously co-author of Hack and put the 's' in https at Facebook. Maker of CTFs.

Home | Contact me | Github | Twitter | Facebook

I was migrating a large set of configuration files from one format to another and I discovered that git's ORIG_HEAD is a useful variable when dealing with merge conflicts, specifically if the merge conflict involves deleted files.

The migration was partially manual. The process took a few days and fellow engineers were editing the same files, hence the conflicts when rebasing / merging.

Here is a recreation of the conflicts I encountered and how I solved them.

Create an empty git repo:

$ mkdir my_repo; cd my_repo
$ git init
$ echo 'hello' > a.txt
$ echo 'world' >> a.txt
$ git add a.txt
$ git commit -m 'first commit'

Migrate a.txt to b.txt in a branch:

$ git co -b my_branch
$ tr '[:lower:]' '[:upper:]' < a.txt > b.txt
$ git rm a.txt
$ git add b.txt
$ git commit -m 'work in progress'

Simulate changes by other engineers:

$ git co master
$ echo 'mountain' >> a.txt
$ sort -o a.txt a.txt
$ git add a.txt
$ git commit -m 'second commit'

$ echo "yolo" >> a.txt
$ git add a.txt
$ git commit -m 'third commit'

Rebase and solve conflict:

$ git co my_branch
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: work in progress
Using index info to reconstruct a base tree...
M a.txt
Falling back to patching base and 3-way merge...
CONFLICT (modify/delete): a.txt deleted in work in progress and modified in HEAD. Version HEAD of a.txt left in tree.
Failed to merge in the changes.
Patch failed at 0001 work in progress
The copy of the patch that failed is found in:
   .../my_repo/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

$ cat a.txt (shows the whole file)
hello
world
mountain
yolo

$ cat .../my_repo/.git/rebase-apply/patch (shows the changes I made)
c699b5960ea45b6a712a495fa15ac6bde1f00abf
diff --git a/a.txt b/a.txt
deleted file mode 100644
index 94954abda49de8615a048f8d2e64b5de848e27a1..0000000000000000000000000000000000000000
--- a/a.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-hello
-world
diff --git a/b.txt b/b.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d10a988a38fa4a3ab59866f5105d3264e4649c79
--- /dev/null
+++ b/b.txt
@@ -0,0 +1,2 @@
+HELLO
+WORLD

$ git diff ORIG_HEAD^..HEAD (shows what actually changed)
diff --git a/a.txt b/a.txt
index 94954ab..d48f0a4 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,4 @@
 hello
 world
+mountain
+yolo

We can now resolve the conflict without having to process the entire file again, which saves time if the migration involves a manual process.

$ echo 'mountain' | tr '[:lower:]' '[:upper:]' >> b.txt
$ echo 'yolo' | tr '[:lower:]' '[:upper:]' >> b.txt
$ git rm a.txt; git add b.txt; git rebase --continue