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