9

Git is based on content and no file so I currently understand the following behavior but I want to know if there is a special option or hack to detect such thing:

git init
mkdir -p foo/bar
echo "test" foo/a.txt
echo "test2" foo/bar/b.txt
git add -A
git commit -m "test"

rm -fr foo
git add -A
git commit -m "delete whole dir"
git log --name-status

When I check log, Git will not clearly said me that foo was deleted but all file foo/a.txt and foo/bar/b.txt was deleted

commit d1513a9b36cd546371a194e798566c49e779e3a9
Date:   Tue Sep 8 16:58:21 2015 +0200

    delete whole dir

D       foo/a.txt
D       foo/bar/b.txt

commit 135f7ae52dfddcee5eeb7bdfa9f0d5c924fed3af
Date:   Tue Sep 8 16:58:10 2015 +0200

    test

A       foo/a.txt
A       foo/bar/b.txt

Thus if I create following commits:

mkdir -p foo/bar
echo "test" > foo/a.txt
echo "test2" > foo/bar/b.txt
echo "test3" > foo/bar/c.txt
git add -A
git commit -m "test2"

rm -f foo/a.txt foo/bar/b.txt
git add -A
git commit -m "delete just 2 files"
git log --name-status

name-status between commit delete whole dir and delete just 2 files are similar

commit 92564fb59464fd6bba2766a6d488c2ff8ca967ea
Date:   Tue Sep 8 17:03:09 2015 +0200

    delete just 2 files

D       foo/a.txt
D       foo/bar/b.txt

commit 3b13f27e960c4ec66464e8a1e0d23f038a872564
Date:   Tue Sep 8 17:02:50 2015 +0200

    test2

A       foo/a.txt
A       foo/bar/b.txt
A       foo/bar/c.txt

Is there any way to detect difference when whole directory is deleted?

Kakawait
  • 3,929
  • 6
  • 33
  • 60

2 Answers2

6

Assuming a sample tree structure in your repository as:

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a/b/c/d/e/q.txt
    new file:   a/b/c/m.txt
    new file:   a/b/c/n.txt
    new file:   a/b/f/o.txt
    new file:   a/b/f/p.txt
    new file:   a/b/k.txt
    new file:   a/b/z.txt
    new file:   a/x.txt
    new file:   a/y.txt

Let's make the following commits:

commit 0bb4d4d50072c1eac1c3cb2b14b670deba8ee31b
Author: Site User <user@site.com>
Date:   Tue Sep 8 19:27:58 2015 +0100

    removed a/b/c/d/e/q.txt

D       a/b/c/d/e/q.txt

-

commit 3b53f16eb2fd7d3d605180ccabcfa71eb9e9225a
Author: Site User <user@site.com>
Date:   Tue Sep 8 19:28:55 2015 +0100

    removed a/b/c/m.txt and a/b/c/n.txt (full a/b/c)

D       a/b/c/m.txt
D       a/b/c/n.txt

-

commit 3af8ace473944996fb8b21135106360305e8b89a
Author: Site User <user@site.com>
Date:   Tue Sep 8 19:30:30 2015 +0100

    added a/b/g/w.txt

A       a/b/g/w.txt

-

Commit 3b53f16eb2fd7d3d605180ccabcfa71eb9e9225a is the one that sees the folder "a/b/c" disappear, as the last file in it is removed.

To find the SHA# of the commit when the folder a/b/c has been deleted, you can find the last commit involving the "a/b/c" folder by using a combination of:

#> git log --name-status -- a/b/c 

and

#> git ls-tree -r [commit] -- a/b/c

Something like:

for cmt in $(git log --pretty=%H -- a/b/c); \
    do X=$(git ls-tree -r "${cmt}" -- a/b/c); \
    [[ -z "${X}" ]] && echo "The folder a/b/c has been deleted in commit: ${cmt}"; \
done

Output:

The folder a/b/c has been deleted in commit: 3b53f16eb2fd7d3d605180ccabcfa71eb9e9225a

In a form of simple BASH script (which I name deleted.sh, should have executable permissions):

#!/bin/bash

P="$1"
GIT=$(which git)

for cmt in $($GIT log --pretty=%H -- "${P}"); do
    X=$($GIT ls-tree -r "${cmt}" -- "${P}");
    [[ -z "${X}" ]] && echo "The folder a/b/c has been deleted in commit: ${cmt}";
done

Usage:

./deleted.sh a/b/c

Output:

The folder a/b/c has been deleted in commit: 3b53f16eb2fd7d3d605180ccabcfa71eb9e9225a

How does it work

The first commit, going back in history, that has no files in tree matching the folder path, should do.

That is what the shell script is doing.

It iterates backwards in history, retrieving all of the SHA#s that are related to any files in the folder, and then finds - among those commits - the first, that does not have any files matching the path provided, in the Git tree.

The fact that that commit is coming up in the list to inspect, insures that there are changes, in it, that involve the folder.

The fact that there are no files matching the folder in its tree (filtered "ls-tree" returns an empty string), insures that it is the commit where le last file in that folder has been deleted.

Dmitri Sologoubenko
  • 2,909
  • 1
  • 23
  • 27
  • But last commit from folder does not necessary means that folder had been deleted? – Kakawait Sep 09 '15 at 07:28
  • The first commit, going back in history, that has no files in tree matching the folder path, should do. That is what the shell script is doing. It iterates backwards in history, retrieveing all of the SHA#s that are related to any files in the folder, and then finds - among those commits, the first that does not have any files matching the path provided in the Git tree. – Dmitri Sologoubenko Sep 09 '15 at 08:37
  • @Kakawait, please see notes on "How does it work" as explanation. – Dmitri Sologoubenko Sep 09 '15 at 08:47
  • You saved the day! Thanks! :-) – inf3rno Feb 27 '18 at 14:31
1

As far as I know, there is no special git command to detect if a directory has been deleted/moved.

However, if the whole directory has moved to a new location within a tracked git repository it should show up under the Untracked files: section of the git status command. Plus, all of its files will show as deleted.

As for the deleted directory, if you expect the directory to no longer exist you can run the ls command to double check that the directory has, in fact, been removed. If the directory isn't there, it isn't being tracked.

If the directory exists but contains nothing, then git is also not tracking it. Still, you could try

git ls-files dirName/ --error-unmatch; echo$?

This solution was originally mentioned on this StackOverflow question. The command is supposed to check if a particular file is being tracked by git but in this situation git will check if there are any files where dirName is part of the file path. If the folder isn't being tracked then an error will show up.

Community
  • 1
  • 1