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