I destroyed all the work I had done in a project for the last 2 months
tl;dr:GIT doesnt consider the files in .gitignore important and will happily replace them
Im pretty careless with my local git commands
Ive been trained by git to be this careless. Unless i use --force
on a command, git will always alert me if im about to do something destructive. Even then, worse case scenario, you can use git reflog
to get back in time after a bad merge or something not easily accesible with a normal git flow
What happened?
I had a link to a folder in my master branch. I branched to do some work and decided to replace the link with the actual folder to untangle some other mess and added it to .gitignore
to avoid git complaining about it
Then happily worked on in for 2 months
I was ready to merge it, so I made a final commit and I checked out master
So far, pretty normal git flow… right?
But wait, something was wrong. My folder was missing!
Wait, what?! what happened!
The folder existed as a syslink on master, so git happily replaced my folder with a now broken syslink
It seems git doesnt consider files under .gitignore
as important
You can see by yourself and reproduce this behaviour by typing the following commands. It doesnt matter if links doesnt exists:
[~/tmp]
$ mkdir gitdestroy/
[~/tmp]
$ cd gitdestroy/
[~/tmp/gitdestroy]
$ cat > file1
hi, im file1
[~/tmp/gitdestroy]
$ ln -s nofile link
[~/tmp/gitdestroy]
$ ll
total 48K
drwxr-xr-x. 26 alberto alberto 36K Jan 29 15:18 ..
-rw-r--r-- 1 alberto alberto 13 Jan 29 15:19 file1
lrwxrwxrwx 1 alberto alberto 6 Jan 29 15:19 link -> nofile
drwxr-xr-x 2 alberto alberto 4.0K Jan 29 15:19 .
[~/tmp/gitdestroy]
$ git init
Initialized empty Git repository in /home/alberto/tmp/gitdestroy/.git/
[~/tmp/gitdestroy (master #%)]
$ git add -A
[~/tmp/gitdestroy (master +)]
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: file1
new file: link
[~/tmp/gitdestroy (master +)]
$ git commit -m "link on repo"
[master (root-commit) 5001c61] link on repo
2 files changed, 2 insertions(+)
create mode 100644 file1
create mode 120000 link
[~/tmp/gitdestroy (master)]
$ git checkout -b branchwithoutlink
Switched to a new branch 'branchwithoutlink'
[~/tmp/gitdestroy (branchwithoutlink)]
$ git rm link
rm 'link'
[~/tmp/gitdestroy (branchwithoutlink +)]
$ mkdir link
[~/tmp/gitdestroy (branchwithoutlink +)]
$ cat >link/file2
hi im file2
[~/tmp/gitdestroy (branchwithoutlink +%)]
$ cat > .gitignore
link
[~/tmp/gitdestroy (branchwithoutlink +%)]
$ git status
On branch branchwithoutlink
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: link
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
[~/tmp/gitdestroy (branchwithoutlink +%)]
$ git add -A
[~/tmp/gitdestroy (branchwithoutlink +)]
$ git commit -m "replace link with folder"
[branchwithoutlink 2cfb06c] replace link with folder
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 .gitignore
delete mode 120000 link
[~/tmp/gitdestroy (branchwithoutlink)]
$ ll
total 60K
drwxr-xr-x. 26 alberto alberto 36K Jan 29 15:18 ..
-rw-r--r-- 1 alberto alberto 13 Jan 29 15:19 file1
drwxr-xr-x 2 alberto alberto 4.0K Jan 29 15:21 link
drwxr-xr-x 4 alberto alberto 4.0K Jan 29 15:22 .
-rw-r--r-- 1 alberto alberto 5 Jan 29 15:22 .gitignore
drwxr-xr-x 8 alberto alberto 4.0K Jan 29 15:22 .git
[~/tmp/gitdestroy (branchwithoutlink)]
$ git checkout master
Switched to branch 'master' <--- NO ERROR???
[~/tmp/gitdestroy (master)]
$ ll
total 52K
drwxr-xr-x. 26 alberto alberto 36K Jan 29 15:18 ..
-rw-r--r-- 1 alberto alberto 13 Jan 29 15:19 file1
lrwxrwxrwx 1 alberto alberto 6 Jan 29 15:22 link -> nofile <--- WHAT
drwxr-xr-x 8 alberto alberto 4.0K Jan 29 15:22 .git
drwxr-xr-x 3 alberto alberto 4.0K Jan 29 15:22 .
[~/tmp/gitdestroy (master)]
$ git checkout branchwithoutlink
Switched to branch 'branchwithoutlink'
[~/tmp/gitdestroy (branchwithoutlink)]
$ ll
total 56K
drwxr-xr-x. 26 alberto alberto 36K Jan 29 15:18 ..
-rw-r--r-- 1 alberto alberto 13 Jan 29 15:19 file1
-rw-r--r-- 1 alberto alberto 5 Jan 29 15:23 .gitignore
drwxr-xr-x 8 alberto alberto 4.0K Jan 29 15:23 .git
drwxr-xr-x 3 alberto alberto 4.0K Jan 29 15:23 .
Aftermath
I analyzed what git
was doing underneath in hopes to gain some insight on how to recover these files. It seems git unlinkat(2)
everyfile and finally rmdir(2)
the folder
By contrasts rm(1)
just uses unlinkat(2)
in every file and folder
Not sure what difference this makes, but it was quite useless. I tried some EXT undelete
tools to try to recover the missing files, but everything was gone
Actually I was able to undeleted some files i had removed 3 years ago that i didnt need :/
Future
This directory was under git as well and remotely hosted. But my last push was 2 months ago. I will be more careful on the future
Recently theres been some discussion on git about something that could prevent this behaviour. They are introducing the concept of “precious ignored” files
But for me the damage was done
This was unexpected behaviour for me. Maybe it was also for you. Be safe out there!