diff-tree.con commit Make 'fsck' able to take an arbitrary number of parents on the (bcee6fd)
   1#include "cache.h"
   2
   3static int recursive = 0;
   4
   5static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base);
   6
   7static void update_tree_entry(void **bufp, unsigned long *sizep)
   8{
   9        void *buf = *bufp;
  10        unsigned long size = *sizep;
  11        int len = strlen(buf) + 1 + 20;
  12
  13        if (size < len)
  14                die("corrupt tree file");
  15        *bufp = buf + len;
  16        *sizep = size - len;
  17}
  18
  19static const unsigned char *extract(void *tree, unsigned long size, const char **pathp, unsigned int *modep)
  20{
  21        int len = strlen(tree)+1;
  22        const unsigned char *sha1 = tree + len;
  23        const char *path = strchr(tree, ' ');
  24
  25        if (!path || size < len + 20 || sscanf(tree, "%o", modep) != 1)
  26                die("corrupt tree file");
  27        *pathp = path+1;
  28        return sha1;
  29}
  30
  31static char *malloc_base(const char *base, const char *path, int pathlen)
  32{
  33        int baselen = strlen(base);
  34        char *newbase = malloc(baselen + pathlen + 2);
  35        memcpy(newbase, base, baselen);
  36        memcpy(newbase + baselen, path, pathlen);
  37        memcpy(newbase + baselen + pathlen, "/", 2);
  38        return newbase;
  39}
  40
  41static void show_file(const char *prefix, void *tree, unsigned long size, const char *base);
  42
  43/* A whole sub-tree went away or appeared */
  44static void show_tree(const char *prefix, void *tree, unsigned long size, const char *base)
  45{
  46        while (size) {
  47                show_file(prefix, tree, size, base);
  48                update_tree_entry(&tree, &size);
  49        }
  50}
  51
  52/* A file entry went away or appeared */
  53static void show_file(const char *prefix, void *tree, unsigned long size, const char *base)
  54{
  55        unsigned mode;
  56        const char *path;
  57        const unsigned char *sha1 = extract(tree, size, &path, &mode);
  58
  59        if (recursive && S_ISDIR(mode)) {
  60                char type[20];
  61                unsigned long size;
  62                char *newbase = malloc_base(base, path, strlen(path));
  63                void *tree;
  64
  65                tree = read_sha1_file(sha1, type, &size);
  66                if (!tree || strcmp(type, "tree"))
  67                        die("corrupt tree sha %s", sha1_to_hex(sha1));
  68
  69                show_tree(prefix, tree, size, newbase);
  70                
  71                free(tree);
  72                free(newbase);
  73                return;
  74        }
  75
  76        printf("%s%o\t%s\t%s\t%s%s%c", prefix, mode,
  77               S_ISDIR(mode) ? "tree" : "blob",
  78               sha1_to_hex(sha1), base, path, 0);
  79}
  80
  81static int compare_tree_entry(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
  82{
  83        unsigned mode1, mode2;
  84        const char *path1, *path2;
  85        const unsigned char *sha1, *sha2;
  86        int cmp, pathlen1, pathlen2;
  87        char old_sha1_hex[50];
  88
  89        sha1 = extract(tree1, size1, &path1, &mode1);
  90        sha2 = extract(tree2, size2, &path2, &mode2);
  91
  92        pathlen1 = strlen(path1);
  93        pathlen2 = strlen(path2);
  94        cmp = cache_name_compare(path1, pathlen1, path2, pathlen2);
  95        if (cmp < 0) {
  96                show_file("-", tree1, size1, base);
  97                return -1;
  98        }
  99        if (cmp > 0) {
 100                show_file("+", tree2, size2, base);
 101                return 1;
 102        }
 103        if (!memcmp(sha1, sha2, 20) && mode1 == mode2)
 104                return 0;
 105
 106        /*
 107         * If the filemode has changed to/from a directory from/to a regular
 108         * file, we need to consider it a remove and an add.
 109         */
 110        if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
 111                show_file("-", tree1, size1, base);
 112                show_file("+", tree2, size2, base);
 113                return 0;
 114        }
 115
 116        if (recursive && S_ISDIR(mode1)) {
 117                int retval;
 118                char *newbase = malloc_base(base, path1, pathlen1);
 119                retval = diff_tree_sha1(sha1, sha2, newbase);
 120                free(newbase);
 121                return retval;
 122        }
 123
 124        strcpy(old_sha1_hex, sha1_to_hex(sha1));
 125        printf("*%o->%o\t%s\t%s->%s\t%s%s%c", mode1, mode2,
 126               S_ISDIR(mode1) ? "tree" : "blob",
 127               old_sha1_hex, sha1_to_hex(sha2), base, path1, 0);
 128        return 0;
 129}
 130
 131static int diff_tree(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
 132{
 133        while (size1 | size2) {
 134                if (!size1) {
 135                        show_file("+", tree2, size2, base);
 136                        update_tree_entry(&tree2, &size2);
 137                        continue;
 138                }
 139                if (!size2) {
 140                        show_file("-", tree1, size1, base);
 141                        update_tree_entry(&tree1, &size1);
 142                        continue;
 143                }
 144                switch (compare_tree_entry(tree1, size1, tree2, size2, base)) {
 145                case -1:
 146                        update_tree_entry(&tree1, &size1);
 147                        continue;
 148                case 0:
 149                        update_tree_entry(&tree1, &size1);
 150                        /* Fallthrough */
 151                case 1:
 152                        update_tree_entry(&tree2, &size2);
 153                        continue;
 154                }
 155                die("diff-tree: internal error");
 156        }
 157        return 0;
 158}
 159
 160static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base)
 161{
 162        void *tree1, *tree2;
 163        unsigned long size1, size2;
 164        char type[20];
 165        int retval;
 166
 167        tree1 = read_sha1_file(old, type, &size1);
 168        if (!tree1 || strcmp(type, "tree"))
 169                die("unable to read source tree (%s)", sha1_to_hex(old));
 170        tree2 = read_sha1_file(new, type, &size2);
 171        if (!tree2 || strcmp(type, "tree"))
 172                die("unable to read destination tree (%s)", sha1_to_hex(new));
 173        retval = diff_tree(tree1, size1, tree2, size2, base);
 174        free(tree1);
 175        free(tree2);
 176        return retval;
 177}
 178
 179int main(int argc, char **argv)
 180{
 181        unsigned char old[20], new[20];
 182
 183        while (argc > 3) {
 184                char *arg = argv[1];
 185                argv++;
 186                argc--;
 187                if (!strcmp(arg, "-r")) {
 188                        recursive = 1;
 189                        continue;
 190                }
 191                usage("diff-tree [-r] <tree sha1> <tree sha1>");
 192        }
 193
 194        if (argc != 3 || get_sha1_hex(argv[1], old) || get_sha1_hex(argv[2], new))
 195                usage("diff-tree <tree sha1> <tree sha1>");
 196        return diff_tree_sha1(old, new, "");
 197}