diff-tree.con commit git-pull-script: do automatic merges (9831d8f)
   1#include "cache.h"
   2
   3static int recursive = 0;
   4static int line_termination = '\n';
   5
   6// What paths are we interested in?
   7static int nr_paths = 0;
   8static char **paths = NULL;
   9static int *pathlens = NULL;
  10
  11static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base);
  12
  13static void update_tree_entry(void **bufp, unsigned long *sizep)
  14{
  15        void *buf = *bufp;
  16        unsigned long size = *sizep;
  17        int len = strlen(buf) + 1 + 20;
  18
  19        if (size < len)
  20                die("corrupt tree file");
  21        *bufp = buf + len;
  22        *sizep = size - len;
  23}
  24
  25static const unsigned char *extract(void *tree, unsigned long size, const char **pathp, unsigned int *modep)
  26{
  27        int len = strlen(tree)+1;
  28        const unsigned char *sha1 = tree + len;
  29        const char *path = strchr(tree, ' ');
  30
  31        if (!path || size < len + 20 || sscanf(tree, "%o", modep) != 1)
  32                die("corrupt tree file");
  33        *pathp = path+1;
  34        return sha1;
  35}
  36
  37static char *malloc_base(const char *base, const char *path, int pathlen)
  38{
  39        int baselen = strlen(base);
  40        char *newbase = xmalloc(baselen + pathlen + 2);
  41        memcpy(newbase, base, baselen);
  42        memcpy(newbase + baselen, path, pathlen);
  43        memcpy(newbase + baselen + pathlen, "/", 2);
  44        return newbase;
  45}
  46
  47static void show_file(const char *prefix, void *tree, unsigned long size, const char *base);
  48
  49/* A whole sub-tree went away or appeared */
  50static void show_tree(const char *prefix, void *tree, unsigned long size, const char *base)
  51{
  52        while (size) {
  53                show_file(prefix, tree, size, base);
  54                update_tree_entry(&tree, &size);
  55        }
  56}
  57
  58/* A file entry went away or appeared */
  59static void show_file(const char *prefix, void *tree, unsigned long size, const char *base)
  60{
  61        unsigned mode;
  62        const char *path;
  63        const unsigned char *sha1 = extract(tree, size, &path, &mode);
  64
  65        if (recursive && S_ISDIR(mode)) {
  66                char type[20];
  67                unsigned long size;
  68                char *newbase = malloc_base(base, path, strlen(path));
  69                void *tree;
  70
  71                tree = read_sha1_file(sha1, type, &size);
  72                if (!tree || strcmp(type, "tree"))
  73                        die("corrupt tree sha %s", sha1_to_hex(sha1));
  74
  75                show_tree(prefix, tree, size, newbase);
  76                
  77                free(tree);
  78                free(newbase);
  79                return;
  80        }
  81
  82        printf("%s%o\t%s\t%s\t%s%s%c", prefix, mode,
  83               S_ISDIR(mode) ? "tree" : "blob",
  84               sha1_to_hex(sha1), base, path,
  85               line_termination);
  86}
  87
  88static int compare_tree_entry(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
  89{
  90        unsigned mode1, mode2;
  91        const char *path1, *path2;
  92        const unsigned char *sha1, *sha2;
  93        int cmp, pathlen1, pathlen2;
  94        char old_sha1_hex[50];
  95
  96        sha1 = extract(tree1, size1, &path1, &mode1);
  97        sha2 = extract(tree2, size2, &path2, &mode2);
  98
  99        pathlen1 = strlen(path1);
 100        pathlen2 = strlen(path2);
 101        cmp = cache_name_compare(path1, pathlen1, path2, pathlen2);
 102        if (cmp < 0) {
 103                show_file("-", tree1, size1, base);
 104                return -1;
 105        }
 106        if (cmp > 0) {
 107                show_file("+", tree2, size2, base);
 108                return 1;
 109        }
 110        if (!memcmp(sha1, sha2, 20) && mode1 == mode2)
 111                return 0;
 112
 113        /*
 114         * If the filemode has changed to/from a directory from/to a regular
 115         * file, we need to consider it a remove and an add.
 116         */
 117        if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
 118                show_file("-", tree1, size1, base);
 119                show_file("+", tree2, size2, base);
 120                return 0;
 121        }
 122
 123        if (recursive && S_ISDIR(mode1)) {
 124                int retval;
 125                char *newbase = malloc_base(base, path1, pathlen1);
 126                retval = diff_tree_sha1(sha1, sha2, newbase);
 127                free(newbase);
 128                return retval;
 129        }
 130
 131        strcpy(old_sha1_hex, sha1_to_hex(sha1));
 132        printf("*%o->%o\t%s\t%s->%s\t%s%s%c", mode1, mode2,
 133               S_ISDIR(mode1) ? "tree" : "blob",
 134               old_sha1_hex, sha1_to_hex(sha2), base, path1,
 135               line_termination);
 136        return 0;
 137}
 138
 139static int interesting(void *tree, unsigned long size, const char *base)
 140{
 141        const char *path;
 142        unsigned mode;
 143        int i;
 144        int baselen, pathlen;
 145
 146        if (!nr_paths)
 147                return 1;
 148
 149        (void)extract(tree, size, &path, &mode);
 150
 151        pathlen = strlen(path);
 152        baselen = strlen(base);
 153
 154        for (i=0; i < nr_paths; i++) {
 155                const char *match = paths[i];
 156                int matchlen = pathlens[i];
 157
 158                if (baselen >= matchlen) {
 159                        /* If it doesn't match, move along... */
 160                        if (strncmp(base, match, matchlen))
 161                                continue;
 162
 163                        /* The base is a subdirectory of a path which was specified. */
 164                        return 1;
 165                }
 166
 167                /* Does the base match? */
 168                if (strncmp(base, match, baselen))
 169                        continue;
 170
 171                match += baselen;
 172                matchlen -= baselen;
 173
 174                if (pathlen > matchlen)
 175                        continue;
 176
 177                if (strncmp(path, match, pathlen))
 178                        continue;
 179
 180                return 1;
 181        }
 182        return 0; /* No matches */
 183}
 184
 185static int diff_tree(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
 186{
 187        while (size1 | size2) {
 188                if (nr_paths && size1 && !interesting(tree1, size1, base)) {
 189                        update_tree_entry(&tree1, &size1);
 190                        continue;
 191                }
 192                if (nr_paths && size2 && !interesting(tree2, size2, base)) {
 193                        update_tree_entry(&tree2, &size2);
 194                        continue;
 195                }
 196                if (!size1) {
 197                        show_file("+", tree2, size2, base);
 198                        update_tree_entry(&tree2, &size2);
 199                        continue;
 200                }
 201                if (!size2) {
 202                        show_file("-", tree1, size1, base);
 203                        update_tree_entry(&tree1, &size1);
 204                        continue;
 205                }
 206                switch (compare_tree_entry(tree1, size1, tree2, size2, base)) {
 207                case -1:
 208                        update_tree_entry(&tree1, &size1);
 209                        continue;
 210                case 0:
 211                        update_tree_entry(&tree1, &size1);
 212                        /* Fallthrough */
 213                case 1:
 214                        update_tree_entry(&tree2, &size2);
 215                        continue;
 216                }
 217                die("diff-tree: internal error");
 218        }
 219        return 0;
 220}
 221
 222static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base)
 223{
 224        void *tree1, *tree2;
 225        unsigned long size1, size2;
 226        int retval;
 227
 228        tree1 = read_tree_with_tree_or_commit_sha1(old, &size1, 0);
 229        if (!tree1)
 230                die("unable to read source tree (%s)", sha1_to_hex(old));
 231        tree2 = read_tree_with_tree_or_commit_sha1(new, &size2, 0);
 232        if (!tree2)
 233                die("unable to read destination tree (%s)", sha1_to_hex(new));
 234        retval = diff_tree(tree1, size1, tree2, size2, base);
 235        free(tree1);
 236        free(tree2);
 237        return retval;
 238}
 239
 240static char *diff_tree_usage = "diff-tree [-r] [-z] <tree sha1> <tree sha1>";
 241
 242int main(int argc, char **argv)
 243{
 244        unsigned char old[20], new[20];
 245
 246        for (;;) {
 247                char *arg = argv[1];
 248
 249                if (!arg || *arg != '-')
 250                        break;
 251
 252                argv++;
 253                argc--;
 254                if (!strcmp(arg, "-r")) {
 255                        recursive = 1;
 256                        continue;
 257                }
 258                if (!strcmp(arg, "-z")) {
 259                        line_termination = '\0';
 260                        continue;
 261                }
 262                usage(diff_tree_usage);
 263        }
 264
 265        if (argc < 3 || get_sha1_hex(argv[1], old) || get_sha1_hex(argv[2], new))
 266                usage(diff_tree_usage);
 267
 268        if (argc > 3) {
 269                int i;
 270
 271                paths = &argv[3];
 272                nr_paths = argc - 3;
 273                pathlens = xmalloc(nr_paths * sizeof(int));
 274                for (i=0; i<nr_paths; i++)
 275                        pathlens[i] = strlen(paths[i]);
 276        }
 277
 278        return diff_tree_sha1(old, new, "");
 279}