293e00b702a14f6d2ace7dd88e176b6291f12510
   1#include "cache.h"
   2#include "strbuf.h"
   3#include "diff.h"
   4
   5static int matches_pathspec(const char *name, char **spec, int cnt)
   6{
   7        int i;
   8        int namelen = strlen(name);
   9        for (i = 0; i < cnt; i++) {
  10                int speclen = strlen(spec[i]);
  11                if (! strncmp(spec[i], name, speclen) &&
  12                    speclen <= namelen &&
  13                    (name[speclen] == 0 ||
  14                     name[speclen] == '/'))
  15                        return 1;
  16        }
  17        return 0;
  18}
  19
  20static int parse_oneside_change(const char *cp, unsigned char *sha1,
  21                                char *path) {
  22        int ch;
  23        while ((ch = *cp) && '0' <= ch && ch <= '7')
  24                cp++; /* skip mode bits */
  25        if (strncmp(cp, "\tblob\t", 6))
  26                return -1;
  27        cp += 6;
  28        if (get_sha1_hex(cp, sha1))
  29                return -1;
  30        cp += 40;
  31        if (*cp++ != '\t')
  32                return -1;
  33        strcpy(path, cp);
  34        return 0;
  35}
  36
  37#define STATUS_CACHED    0 /* cached and sha1 valid */
  38#define STATUS_ABSENT    1 /* diff-tree says old removed or new added */
  39#define STATUS_UNCACHED  2 /* diff-cache output: read from working tree */
  40
  41static int parse_diff_tree_output(const char *buf,
  42                                  unsigned char *old_sha1,
  43                                  int *old_status,
  44                                  unsigned char *new_sha1,
  45                                  int *new_status,
  46                                  char *path) {
  47        const char *cp = buf;
  48        int ch;
  49        static unsigned char null_sha[20] = { 0, };
  50
  51        switch (*cp++) {
  52        case '+':
  53                *old_status = STATUS_ABSENT;
  54                *new_status = (memcmp(new_sha1, null_sha, sizeof(null_sha)) ?
  55                               STATUS_CACHED : STATUS_UNCACHED);
  56                return parse_oneside_change(cp, new_sha1, path);
  57        case '-':
  58                *new_status = STATUS_ABSENT;
  59                *old_status = (memcmp(old_sha1, null_sha, sizeof(null_sha)) ?
  60                               STATUS_CACHED : STATUS_UNCACHED);
  61                return parse_oneside_change(cp, old_sha1, path);
  62        case '*':
  63                break;
  64        default:
  65                return -1;
  66        }
  67        
  68        /* This is for '*' entries */
  69        while ((ch = *cp) && ('0' <= ch && ch <= '7'))
  70                cp++; /* skip mode bits */
  71        if (strncmp(cp, "->", 2))
  72                return -1;
  73        cp += 2;
  74        while ((ch = *cp) && ('0' <= ch && ch <= '7'))
  75                cp++; /* skip mode bits */
  76        if (strncmp(cp, "\tblob\t", 6))
  77                return -1;
  78        cp += 6;
  79        if (get_sha1_hex(cp, old_sha1))
  80                return -1;
  81        cp += 40;
  82        if (strncmp(cp, "->", 2))
  83                return -1;
  84        cp += 2;
  85        if (get_sha1_hex(cp, new_sha1))
  86                return -1;
  87        cp += 40;
  88        if (*cp++ != '\t')
  89                return -1;
  90        strcpy(path, cp);
  91        *old_status = (memcmp(old_sha1, null_sha, sizeof(null_sha)) ?
  92                       STATUS_CACHED : STATUS_UNCACHED);
  93        *new_status = (memcmp(new_sha1, null_sha, sizeof(null_sha)) ?
  94                       STATUS_CACHED : STATUS_UNCACHED);
  95        return 0;
  96}
  97
  98static int sha1err(const char *path, const unsigned char *sha1)
  99{
 100        return error("diff-tree-helper: unable to read sha1 file of %s (%s)",
 101                     path, sha1_to_hex(sha1));
 102}
 103
 104static int fserr(const char *path)
 105{
 106        return error("diff-tree-helper: unable to read file %s", path);
 107}
 108
 109static char *map_whole_file(const char *path, unsigned long *size) {
 110        int fd;
 111        struct stat st;
 112        void *buf;
 113
 114        if ((fd = open(path, O_RDONLY)) < 0) {
 115                error("diff-tree-helper: unable to read file %s", path);
 116                return 0;
 117        }
 118        if (fstat(fd, &st) < 0) {
 119                close(fd);
 120                error("diff-tree-helper: unable to stat file %s", path);
 121                return 0;
 122        }
 123        *size = st.st_size;
 124        buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
 125        close(fd);
 126        return buf;
 127}
 128
 129static int show_diff(const unsigned char *old_sha1, int old_status,
 130                     const unsigned char *new_sha1, int new_status,
 131                     const char *path, int reverse_diff)
 132{
 133        char other[PATH_MAX];
 134        unsigned long size;
 135        char type[20];
 136        int fd;
 137        int reverse;
 138        void *blob = 0;
 139        const char *fs = 0;
 140        int need_unmap = 0;
 141        int need_unlink = 0;
 142
 143
 144        switch (old_status) {
 145        case STATUS_CACHED:
 146                blob = read_sha1_file(old_sha1, type, &size);
 147                if (! blob)
 148                        return sha1err(path, old_sha1);
 149                        
 150                switch (new_status) {
 151                case STATUS_CACHED:
 152                        strcpy(other, ".diff_tree_helper_XXXXXX");
 153                        fd = mkstemp(other);
 154                        if (fd < 0)
 155                                die("unable to create temp-file");
 156                        if (write(fd, blob, size) != size)
 157                                die("unable to write temp-file");
 158                        close(fd);
 159                        free(blob);
 160
 161                        blob = read_sha1_file(new_sha1, type, &size);
 162                        if (! blob)
 163                                return sha1err(path, new_sha1);
 164
 165                        need_unlink = 1;
 166                        /* new = blob, old = fs */
 167                        reverse = !reverse_diff;
 168                        fs = other;
 169                        break;
 170
 171                case STATUS_ABSENT:
 172                case STATUS_UNCACHED:
 173                        fs = ((new_status == STATUS_ABSENT) ?
 174                              "/dev/null" : path);
 175                        reverse = reverse_diff;
 176                        break;
 177
 178                default:
 179                        reverse = reverse_diff;
 180                }
 181                break;
 182
 183        case STATUS_ABSENT:
 184                switch (new_status) {
 185                case STATUS_CACHED:
 186                        blob = read_sha1_file(new_sha1, type, &size);
 187                        if (! blob)
 188                                return sha1err(path, new_sha1);
 189                        /* old = fs, new = blob */
 190                        fs = "/dev/null";
 191                        reverse = !reverse_diff;
 192                        break;
 193
 194                case STATUS_ABSENT:
 195                        return error("diff-tree-helper: absent from both old and new?");
 196                case STATUS_UNCACHED:
 197                        fs = path;
 198                        blob = strdup("");
 199                        size = 0;
 200                        /* old = blob, new = fs */
 201                        reverse = reverse_diff;
 202                        break;
 203                default:
 204                        reverse = reverse_diff;
 205                }
 206                break;
 207
 208        case STATUS_UNCACHED:
 209                fs = path; /* old = fs, new = blob */
 210                reverse = !reverse_diff;
 211
 212                switch (new_status) {
 213                case STATUS_CACHED:
 214                        blob = read_sha1_file(new_sha1, type, &size);
 215                        if (! blob)
 216                                return sha1err(path, new_sha1);
 217                        break;
 218
 219                case STATUS_ABSENT:
 220                        blob = strdup("");
 221                        size = 0;
 222                        break;
 223
 224                case STATUS_UNCACHED:
 225                        /* old = fs */
 226                        blob = map_whole_file(path, &size);
 227                        if (! blob)
 228                                return fserr(path);
 229                        need_unmap = 1;
 230                        break;
 231                default:
 232                        reverse = reverse_diff;
 233                }
 234                break;
 235
 236        default:
 237                reverse = reverse_diff;
 238        }
 239        
 240        if (fs)
 241                show_differences(fs,
 242                                 path, /* label */
 243                                 blob,
 244                                 size,
 245                                 reverse /* 0: diff blob fs
 246                                            1: diff fs blob */);
 247
 248        if (need_unlink)
 249                unlink(other);
 250        if (need_unmap && blob)
 251                munmap(blob, size);
 252        else
 253                free(blob);
 254        return 0;
 255}
 256
 257static const char *diff_tree_helper_usage =
 258"diff-tree-helper [-R] [-z] paths...";
 259
 260int main(int ac, char **av) {
 261        struct strbuf sb;
 262        int reverse_diff = 0;
 263        int line_termination = '\n';
 264
 265        strbuf_init(&sb);
 266
 267        while (1 < ac && av[1][0] == '-') {
 268                if (av[1][1] == 'R')
 269                        reverse_diff = 1;
 270                else if (av[1][1] == 'z')
 271                        line_termination = 0;
 272                else
 273                        usage(diff_tree_helper_usage);
 274                ac--; av++;
 275        }
 276        /* the remaining parameters are paths patterns */
 277
 278        prepare_diff_cmd();
 279
 280        while (1) {
 281                int old_status, new_status;
 282                unsigned char old_sha1[20], new_sha1[20];
 283                char path[PATH_MAX];
 284                read_line(&sb, stdin, line_termination);
 285                if (sb.eof)
 286                        break;
 287                if (parse_diff_tree_output(sb.buf,
 288                                           old_sha1, &old_status,
 289                                           new_sha1, &new_status,
 290                                           path)) {
 291                        fprintf(stderr, "cannot parse %s\n", sb.buf);
 292                        continue;
 293                }
 294                if (1 < ac && ! matches_pathspec(path, av+1, ac-1))
 295                        continue;
 296
 297                show_diff(old_sha1, old_status,
 298                          new_sha1, new_status,
 299                          path, reverse_diff);
 300        }
 301        return 0;
 302}