b9267d6159c33b9b6325d14c1936f7254cfa53bb
   1#include <ctype.h>
   2#include "cache.h"
   3#include "diff.h"
   4
   5static int silent = 0;
   6static int ignore_merges = 1;
   7static int recursive = 0;
   8static int read_stdin = 0;
   9static int line_termination = '\n';
  10static int generate_patch = 0;
  11static const char *header = NULL;
  12
  13// What paths are we interested in?
  14static int nr_paths = 0;
  15static char **paths = NULL;
  16static int *pathlens = NULL;
  17
  18static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base);
  19
  20static void update_tree_entry(void **bufp, unsigned long *sizep)
  21{
  22        void *buf = *bufp;
  23        unsigned long size = *sizep;
  24        int len = strlen(buf) + 1 + 20;
  25
  26        if (size < len)
  27                die("corrupt tree file");
  28        *bufp = buf + len;
  29        *sizep = size - len;
  30}
  31
  32static const unsigned char *extract(void *tree, unsigned long size, const char **pathp, unsigned int *modep)
  33{
  34        int len = strlen(tree)+1;
  35        const unsigned char *sha1 = tree + len;
  36        const char *path = strchr(tree, ' ');
  37
  38        if (!path || size < len + 20 || sscanf(tree, "%o", modep) != 1)
  39                die("corrupt tree file");
  40        *pathp = path+1;
  41        return sha1;
  42}
  43
  44static char *malloc_base(const char *base, const char *path, int pathlen)
  45{
  46        int baselen = strlen(base);
  47        char *newbase = xmalloc(baselen + pathlen + 2);
  48        memcpy(newbase, base, baselen);
  49        memcpy(newbase + baselen, path, pathlen);
  50        memcpy(newbase + baselen + pathlen, "/", 2);
  51        return newbase;
  52}
  53
  54static void show_file(const char *prefix, void *tree, unsigned long size, const char *base);
  55
  56/* A whole sub-tree went away or appeared */
  57static void show_tree(const char *prefix, void *tree, unsigned long size, const char *base)
  58{
  59        while (size) {
  60                show_file(prefix, tree, size, base);
  61                update_tree_entry(&tree, &size);
  62        }
  63}
  64
  65/* A file entry went away or appeared */
  66static void show_file(const char *prefix, void *tree, unsigned long size, const char *base)
  67{
  68        unsigned mode;
  69        const char *path;
  70        const unsigned char *sha1 = extract(tree, size, &path, &mode);
  71
  72        if (header) {
  73                printf("%s", header);
  74                header = NULL;
  75        }
  76
  77        if (silent)
  78                return;
  79
  80        if (recursive && S_ISDIR(mode)) {
  81                char type[20];
  82                unsigned long size;
  83                char *newbase = malloc_base(base, path, strlen(path));
  84                void *tree;
  85
  86                tree = read_sha1_file(sha1, type, &size);
  87                if (!tree || strcmp(type, "tree"))
  88                        die("corrupt tree sha %s", sha1_to_hex(sha1));
  89
  90                show_tree(prefix, tree, size, newbase);
  91                
  92                free(tree);
  93                free(newbase);
  94                return;
  95        }
  96
  97        if (generate_patch) {
  98                if (!S_ISDIR(mode))
  99                        diff_addremove(prefix[0], mode, sha1, base, path);
 100        }
 101        else
 102                printf("%s%06o\t%s\t%s\t%s%s%c", prefix, mode,
 103                       S_ISDIR(mode) ? "tree" : "blob",
 104                       sha1_to_hex(sha1), base, path,
 105                       line_termination);
 106}
 107
 108static int compare_tree_entry(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
 109{
 110        unsigned mode1, mode2;
 111        const char *path1, *path2;
 112        const unsigned char *sha1, *sha2;
 113        int cmp, pathlen1, pathlen2;
 114        char old_sha1_hex[50];
 115
 116        sha1 = extract(tree1, size1, &path1, &mode1);
 117        sha2 = extract(tree2, size2, &path2, &mode2);
 118
 119        pathlen1 = strlen(path1);
 120        pathlen2 = strlen(path2);
 121        cmp = cache_name_compare(path1, pathlen1, path2, pathlen2);
 122        if (cmp < 0) {
 123                show_file("-", tree1, size1, base);
 124                return -1;
 125        }
 126        if (cmp > 0) {
 127                show_file("+", tree2, size2, base);
 128                return 1;
 129        }
 130        if (!memcmp(sha1, sha2, 20) && mode1 == mode2)
 131                return 0;
 132
 133        /*
 134         * If the filemode has changed to/from a directory from/to a regular
 135         * file, we need to consider it a remove and an add.
 136         */
 137        if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
 138                show_file("-", tree1, size1, base);
 139                show_file("+", tree2, size2, base);
 140                return 0;
 141        }
 142
 143        if (recursive && S_ISDIR(mode1)) {
 144                int retval;
 145                char *newbase = malloc_base(base, path1, pathlen1);
 146                retval = diff_tree_sha1(sha1, sha2, newbase);
 147                free(newbase);
 148                return retval;
 149        }
 150
 151        if (header) {
 152                printf("%s", header);
 153                header = NULL;
 154        }
 155        if (silent)
 156                return 0;
 157
 158        if (generate_patch) {
 159                if (!S_ISDIR(mode1))
 160                        diff_change(mode1, mode2, sha1, sha2, base, path1);
 161        }
 162        else {
 163                strcpy(old_sha1_hex, sha1_to_hex(sha1));
 164                printf("*%06o->%06o\t%s\t%s->%s\t%s%s%c", mode1, mode2,
 165                       S_ISDIR(mode1) ? "tree" : "blob",
 166                       old_sha1_hex, sha1_to_hex(sha2), base, path1,
 167                       line_termination);
 168        }
 169        return 0;
 170}
 171
 172static int interesting(void *tree, unsigned long size, const char *base)
 173{
 174        const char *path;
 175        unsigned mode;
 176        int i;
 177        int baselen, pathlen;
 178
 179        if (!nr_paths)
 180                return 1;
 181
 182        (void)extract(tree, size, &path, &mode);
 183
 184        pathlen = strlen(path);
 185        baselen = strlen(base);
 186
 187        for (i=0; i < nr_paths; i++) {
 188                const char *match = paths[i];
 189                int matchlen = pathlens[i];
 190
 191                if (baselen >= matchlen) {
 192                        /* If it doesn't match, move along... */
 193                        if (strncmp(base, match, matchlen))
 194                                continue;
 195
 196                        /* The base is a subdirectory of a path which was specified. */
 197                        return 1;
 198                }
 199
 200                /* Does the base match? */
 201                if (strncmp(base, match, baselen))
 202                        continue;
 203
 204                match += baselen;
 205                matchlen -= baselen;
 206
 207                if (pathlen > matchlen)
 208                        continue;
 209
 210                if (strncmp(path, match, pathlen))
 211                        continue;
 212
 213                return 1;
 214        }
 215        return 0; /* No matches */
 216}
 217
 218static int diff_tree(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
 219{
 220        while (size1 | size2) {
 221                if (nr_paths && size1 && !interesting(tree1, size1, base)) {
 222                        update_tree_entry(&tree1, &size1);
 223                        continue;
 224                }
 225                if (nr_paths && size2 && !interesting(tree2, size2, base)) {
 226                        update_tree_entry(&tree2, &size2);
 227                        continue;
 228                }
 229                if (!size1) {
 230                        show_file("+", tree2, size2, base);
 231                        update_tree_entry(&tree2, &size2);
 232                        continue;
 233                }
 234                if (!size2) {
 235                        show_file("-", tree1, size1, base);
 236                        update_tree_entry(&tree1, &size1);
 237                        continue;
 238                }
 239                switch (compare_tree_entry(tree1, size1, tree2, size2, base)) {
 240                case -1:
 241                        update_tree_entry(&tree1, &size1);
 242                        continue;
 243                case 0:
 244                        update_tree_entry(&tree1, &size1);
 245                        /* Fallthrough */
 246                case 1:
 247                        update_tree_entry(&tree2, &size2);
 248                        continue;
 249                }
 250                die("diff-tree: internal error");
 251        }
 252        return 0;
 253}
 254
 255static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base)
 256{
 257        void *tree1, *tree2;
 258        unsigned long size1, size2;
 259        int retval;
 260
 261        tree1 = read_object_with_reference(old, "tree", &size1, 0);
 262        if (!tree1)
 263                die("unable to read source tree (%s)", sha1_to_hex(old));
 264        tree2 = read_object_with_reference(new, "tree", &size2, 0);
 265        if (!tree2)
 266                die("unable to read destination tree (%s)", sha1_to_hex(new));
 267        retval = diff_tree(tree1, size1, tree2, size2, base);
 268        free(tree1);
 269        free(tree2);
 270        return retval;
 271}
 272
 273static int diff_tree_stdin(char *line)
 274{
 275        int len = strlen(line);
 276        unsigned char commit[20], parent[20];
 277        unsigned long size, offset;
 278        static char this_header[100];
 279        char *buf;
 280
 281        if (!len || line[len-1] != '\n')
 282                return -1;
 283        line[len-1] = 0;
 284        if (get_sha1_hex(line, commit))
 285                return -1;
 286        if (isspace(line[40]) && !get_sha1_hex(line+41, parent)) {
 287                line[40] = 0;
 288                line[81] = 0;
 289                sprintf(this_header, "%s (from %s)\n", line, line+41);
 290                header = this_header;
 291                return diff_tree_sha1(parent, commit, "");
 292        }
 293        buf = read_object_with_reference(commit, "commit", &size, NULL);
 294        if (!buf)
 295                return -1;
 296
 297        /* More than one parent? */
 298        if (ignore_merges) {
 299                if (!memcmp(buf + 46 + 48, "parent ", 7))
 300                        return 0;
 301        }
 302
 303        line[40] = 0;
 304        offset = 46;
 305        while (offset + 48 < size && !memcmp(buf + offset, "parent ", 7)) {
 306                if (get_sha1_hex(buf + offset + 7, parent))
 307                        return -1;
 308                sprintf(this_header, "%s (from %s)\n", line, sha1_to_hex(parent));
 309                header = this_header;
 310                diff_tree_sha1(parent, commit, "");
 311                offset += 48;
 312        }
 313        return -1;
 314}
 315
 316static char *diff_tree_usage = "diff-tree [-p] [-r] [-z] <tree sha1> <tree sha1>";
 317
 318int main(int argc, char **argv)
 319{
 320        char line[1000];
 321        unsigned char old[20], new[20];
 322
 323        for (;;) {
 324                char *arg;
 325
 326                argv++;
 327                argc--;
 328                arg = *argv;
 329                if (!arg || *arg != '-')
 330                        break;
 331
 332                if (!strcmp(arg, "-")) {
 333                        argv++;
 334                        argc--;
 335                        break;
 336                }
 337                if (!strcmp(arg, "-r")) {
 338                        recursive = 1;
 339                        continue;
 340                }
 341                if (!strcmp(arg, "-p")) {
 342                        recursive = generate_patch = 1;
 343                        continue;
 344                }
 345                if (!strcmp(arg, "-z")) {
 346                        line_termination = '\0';
 347                        continue;
 348                }
 349                if (!strcmp(arg, "-m")) {
 350                        ignore_merges = 0;
 351                        continue;
 352                }
 353                if (!strcmp(arg, "-s")) {
 354                        silent = 1;
 355                        continue;
 356                }
 357                if (!strcmp(arg, "--stdin")) {
 358                        read_stdin = 1;
 359                        continue;
 360                }
 361                usage(diff_tree_usage);
 362        }
 363
 364        if (!read_stdin) {
 365                if (argc < 2 || get_sha1(argv[0], old) || get_sha1(argv[1], new))
 366                        usage(diff_tree_usage);
 367                argv += 2;
 368                argc -= 2;
 369        }
 370
 371        if (argc > 0) {
 372                int i;
 373
 374                paths = argv;
 375                nr_paths = argc;
 376                pathlens = xmalloc(nr_paths * sizeof(int));
 377                for (i=0; i<nr_paths; i++)
 378                        pathlens[i] = strlen(paths[i]);
 379        }
 380
 381        if (!read_stdin)
 382                return diff_tree_sha1(old, new, "");
 383
 384        while (fgets(line, sizeof(line), stdin))
 385                diff_tree_stdin(line);
 386
 387        return 0;
 388}