Directory Tree Diff
Another month, another tool. Here is a small program that recursively compares two directory trees and prints the changes. Specifically; missing files/directories, additional files/directories and changed files are listed with a different color and prefix, like this screenshot example shows:
Files are compared by first checking the size, then a byte by byte comparison.
Here's the code:
#include <stdio.h> #include <stdlib.h> #include <dirent.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <limits.h> static void diff_print(int level, char symbol, char *name, int is_dir) { while (level-- > 0) printf(" "); #ifdef WINNT printf("%c%s", symbol, name); #else switch (symbol) { case '=': printf("%c%s", symbol, name); break; case '*': printf("%c\e[1;31m%s\e[0m", symbol, name); break; case '+': printf("%c\e[1;34m%s\e[0m", symbol, name); break; case '-': printf("%c\e[1;35m%s\e[0m", symbol, name); break; } #endif if (is_dir) printf("/"); printf("\n"); } static int diff_file(char *path1, char *path2) { FILE *fh1, *fh2; int c1, c2; fh1 = fopen(path1, "rb"); if (fh1 == NULL) { fprintf(stderr, "Warning: Unable to open file: %s\n", path1); return 0; } fh2 = fopen(path2, "rb"); if (fh2 == NULL) { fprintf(stderr, "Warning: Unable to open file: %s\n", path2); return 0; } while ((c1 = fgetc(fh1)) != EOF) { c2 = fgetc(fh2); if (c1 != c2) { fclose(fh1); fclose(fh2); return 1; } } fclose(fh1); fclose(fh2); return 0; } static void diff_dir(char *path1, char *path2, int level) { DIR *dh; struct dirent *entry; struct stat st1, st2; char fullpath1[PATH_MAX], fullpath2[PATH_MAX]; dh = opendir(path1); if (dh == NULL) { fprintf(stderr, "Warning: Unable to open directory: %s\n", path1); return; } while ((entry = readdir(dh))) { if (entry->d_name[0] == '.') continue; /* Ignore files with leading dot. */ #ifdef WINNT snprintf(fullpath1, PATH_MAX, "%s\\%s", path1, entry->d_name); snprintf(fullpath2, PATH_MAX, "%s\\%s", path2, entry->d_name); #else snprintf(fullpath1, PATH_MAX, "%s/%s", path1, entry->d_name); snprintf(fullpath2, PATH_MAX, "%s/%s", path2, entry->d_name); #endif if (stat(fullpath1, &st1) == -1) { fprintf(stderr, "Warning: Unable to stat() path: %s\n", fullpath1); continue; } if (S_ISDIR(st1.st_mode)) { if (stat(fullpath2, &st2) == -1) { diff_print(level, '+', entry->d_name, 1); } else { if (S_ISDIR(st2.st_mode)) { diff_print(level, '=', entry->d_name, 1); diff_dir(fullpath1, fullpath2, level + 1); } else { diff_print(level, '+', entry->d_name, 1); } } } else if (S_ISREG(st1.st_mode)) { if (stat(fullpath2, &st2) == -1) { diff_print(level, '+', entry->d_name, 0); } else { if (S_ISREG(st2.st_mode)) { if (st1.st_size == st2.st_size) { if (diff_file(fullpath1, fullpath2)) { diff_print(level, '*', entry->d_name, 0); } else { diff_print(level, '=', entry->d_name, 0); } } else { diff_print(level, '*', entry->d_name, 0); } } else { diff_print(level, '+', entry->d_name, 0); } } } } closedir(dh); dh = opendir(path2); if (dh == NULL) { fprintf(stderr, "Warning: Unable to open directory: %s\n", path2); return; } while ((entry = readdir(dh))) { if (entry->d_name[0] == '.') continue; /* Ignore files with leading dot. */ #ifdef WINNT snprintf(fullpath1, PATH_MAX, "%s\\%s", path1, entry->d_name); snprintf(fullpath2, PATH_MAX, "%s\\%s", path2, entry->d_name); #else snprintf(fullpath1, PATH_MAX, "%s/%s", path1, entry->d_name); snprintf(fullpath2, PATH_MAX, "%s/%s", path2, entry->d_name); #endif if (stat(fullpath2, &st2) == -1) { fprintf(stderr, "Warning: Unable to stat() path: %s\n", fullpath2); continue; } if (S_ISDIR(st2.st_mode)) { if (stat(fullpath1, &st1) == -1) { diff_print(level, '-', entry->d_name, 1); } else { if (! S_ISDIR(st1.st_mode)) { diff_print(level, '-', entry->d_name, 1); } } } else if (S_ISREG(st2.st_mode)) { if (stat(fullpath1, &st1) == -1) { diff_print(level, '-', entry->d_name, 0); } else { if (! S_ISREG(st1.st_mode)) { diff_print(level, '-', entry->d_name, 0); } } } } closedir(dh); return; } int main(int argc, char *argv[]) { if (argc < 3) { fprintf(stderr, "Usage: %s <directory 1> <directory 2>\n", argv[0]); return 1; } diff_dir(argv[1], argv[2], 0); return 0; }
I have also compiled a Windows binary binary for convenience, but note that this does not support colorized output!