diff.con commit Rename some test scripts and describe the naming convention (f50c9f7)
   1/*
   2 * Copyright (C) 2005 Junio C Hamano
   3 */
   4#include <sys/types.h>
   5#include <sys/wait.h>
   6#include <signal.h>
   7#include <limits.h>
   8#include "cache.h"
   9#include "diff.h"
  10
  11static const char *diff_opts = "-pu";
  12
  13static const char *external_diff(void)
  14{
  15        static const char *external_diff_cmd = NULL;
  16        static int done_preparing = 0;
  17
  18        if (done_preparing)
  19                return external_diff_cmd;
  20
  21        /*
  22         * Default values above are meant to match the
  23         * Linux kernel development style.  Examples of
  24         * alternative styles you can specify via environment
  25         * variables are:
  26         *
  27         * GIT_DIFF_OPTS="-c";
  28         */
  29        if (gitenv("GIT_EXTERNAL_DIFF"))
  30                external_diff_cmd = gitenv("GIT_EXTERNAL_DIFF");
  31
  32        /* In case external diff fails... */
  33        diff_opts = gitenv("GIT_DIFF_OPTS") ? : diff_opts;
  34
  35        done_preparing = 1;
  36        return external_diff_cmd;
  37}
  38
  39/* Help to copy the thing properly quoted for the shell safety.
  40 * any single quote is replaced with '\'', and the caller is
  41 * expected to enclose the result within a single quote pair.
  42 *
  43 * E.g.
  44 *  original     sq_expand     result
  45 *  name     ==> name      ==> 'name'
  46 *  a b      ==> a b       ==> 'a b'
  47 *  a'b      ==> a'\''b    ==> 'a'\''b'
  48 */
  49static char *sq_expand(const char *src)
  50{
  51        static char *buf = NULL;
  52        int cnt, c;
  53        const char *cp;
  54        char *bp;
  55
  56        /* count bytes needed to store the quoted string. */ 
  57        for (cnt = 1, cp = src; *cp; cnt++, cp++)
  58                if (*cp == '\'')
  59                        cnt += 3;
  60
  61        buf = xmalloc(cnt);
  62        bp = buf;
  63        while ((c = *src++)) {
  64                if (c != '\'')
  65                        *bp++ = c;
  66                else {
  67                        bp = strcpy(bp, "'\\''");
  68                        bp += 4;
  69                }
  70        }
  71        *bp = 0;
  72        return buf;
  73}
  74
  75static struct diff_tempfile {
  76        const char *name;
  77        char hex[41];
  78        char mode[10];
  79        char tmp_path[50];
  80} diff_temp[2];
  81
  82static void builtin_diff(const char *name,
  83                         struct diff_tempfile *temp)
  84{
  85        int i, next_at;
  86        const char *diff_cmd = "diff -L'%s%s' -L'%s%s'";
  87        const char *diff_arg  = "'%s' '%s'||:"; /* "||:" is to return 0 */
  88        const char *input_name_sq[2];
  89        const char *path0[2];
  90        const char *path1[2];
  91        const char *name_sq = sq_expand(name);
  92        char *cmd;
  93        
  94        /* diff_cmd and diff_arg have 6 %s in total which makes
  95         * the sum of these strings 12 bytes larger than required.
  96         * we use 2 spaces around diff-opts, and we need to count
  97         * terminating NUL, so we subtract 9 here.
  98         */
  99        int cmd_size = (strlen(diff_cmd) + strlen(diff_opts) +
 100                        strlen(diff_arg) - 9);
 101        for (i = 0; i < 2; i++) {
 102                input_name_sq[i] = sq_expand(temp[i].name);
 103                if (!strcmp(temp[i].name, "/dev/null")) {
 104                        path0[i] = "/dev/null";
 105                        path1[i] = "";
 106                } else {
 107                        path0[i] = i ? "b/" : "a/";
 108                        path1[i] = name_sq;
 109                }
 110                cmd_size += (strlen(path0[i]) + strlen(path1[i]) +
 111                             strlen(input_name_sq[i]));
 112        }
 113
 114        cmd = xmalloc(cmd_size);
 115
 116        next_at = 0;
 117        next_at += snprintf(cmd+next_at, cmd_size-next_at,
 118                            diff_cmd,
 119                            path0[0], path1[0], path0[1], path1[1]);
 120        next_at += snprintf(cmd+next_at, cmd_size-next_at,
 121                            " %s ", diff_opts);
 122        next_at += snprintf(cmd+next_at, cmd_size-next_at,
 123                            diff_arg, input_name_sq[0], input_name_sq[1]);
 124
 125        if (!path1[0][0])
 126                printf("Created: %s (mode:%s)\n", name, temp[1].mode);
 127        else if (!path1[1][0])
 128                printf("Deleted: %s\n", name);
 129        else if (strcmp(temp[0].mode, temp[1].mode)) {
 130                printf("Mode changed: %s (%s->%s)\n", name,
 131                       temp[0].mode, temp[1].mode);
 132                /* Be careful.  We do not want to diff between
 133                 * symlink and a file.
 134                 */
 135                if (strncmp(temp[0].mode, "120", 3) !=
 136                    strncmp(temp[1].mode, "120", 3))
 137                        exit(0);
 138        }
 139        fflush(NULL);
 140        execlp("/bin/sh","sh", "-c", cmd, NULL);
 141}
 142
 143/*
 144 * Given a name and sha1 pair, if the dircache tells us the file in
 145 * the work tree has that object contents, return true, so that
 146 * prepare_temp_file() does not have to inflate and extract.
 147 */
 148static int work_tree_matches(const char *name, const unsigned char *sha1)
 149{
 150        struct cache_entry *ce;
 151        struct stat st;
 152        int pos, len;
 153        
 154        /* We do not read the cache ourselves here, because the
 155         * benchmark with my previous version that always reads cache
 156         * shows that it makes things worse for diff-tree comparing
 157         * two linux-2.6 kernel trees in an already checked out work
 158         * tree.  This is because most diff-tree comparison deals with
 159         * only a small number of files, while reading the cache is
 160         * expensive for a large project, and its cost outweighs the
 161         * savings we get by not inflating the object to a temporary
 162         * file.  Practically, this code only helps when we are used
 163         * by diff-cache --cached, which does read the cache before
 164         * calling us.
 165         */ 
 166        if (!active_cache)
 167                return 0;
 168
 169        len = strlen(name);
 170        pos = cache_name_pos(name, len);
 171        if (pos < 0)
 172                return 0;
 173        ce = active_cache[pos];
 174        if ((lstat(name, &st) < 0) ||
 175            !S_ISREG(st.st_mode) ||
 176            cache_match_stat(ce, &st) ||
 177            memcmp(sha1, ce->sha1, 20))
 178                return 0;
 179        return 1;
 180}
 181
 182static void prep_temp_blob(struct diff_tempfile *temp,
 183                           void *blob,
 184                           unsigned long size,
 185                           unsigned char *sha1,
 186                           int mode)
 187{
 188        int fd;
 189
 190        strcpy(temp->tmp_path, ".diff_XXXXXX");
 191        fd = mkstemp(temp->tmp_path);
 192        if (fd < 0)
 193                die("unable to create temp-file");
 194        if (write(fd, blob, size) != size)
 195                die("unable to write temp-file");
 196        close(fd);
 197        temp->name = temp->tmp_path;
 198        strcpy(temp->hex, sha1_to_hex(sha1));
 199        temp->hex[40] = 0;
 200        sprintf(temp->mode, "%06o", mode);
 201}
 202
 203static void prepare_temp_file(const char *name,
 204                              struct diff_tempfile *temp,
 205                              struct diff_spec *one)
 206{
 207        static unsigned char null_sha1[20] = { 0, };
 208        int use_work_tree = 0;
 209
 210        if (!one->file_valid) {
 211        not_a_valid_file:
 212                /* A '-' entry produces this for file-2, and
 213                 * a '+' entry produces this for file-1.
 214                 */
 215                temp->name = "/dev/null";
 216                strcpy(temp->hex, ".");
 217                strcpy(temp->mode, ".");
 218                return;
 219        }
 220
 221        if (one->sha1_valid &&
 222            (!memcmp(one->blob_sha1, null_sha1, sizeof(null_sha1)) ||
 223             work_tree_matches(name, one->blob_sha1)))
 224                use_work_tree = 1;
 225
 226        if (!one->sha1_valid || use_work_tree) {
 227                struct stat st;
 228                temp->name = name;
 229                if (lstat(temp->name, &st) < 0) {
 230                        if (errno == ENOENT)
 231                                goto not_a_valid_file;
 232                        die("stat(%s): %s", temp->name, strerror(errno));
 233                }
 234                if (S_ISLNK(st.st_mode)) {
 235                        int ret;
 236                        char *buf, buf_[1024];
 237                        buf = ((sizeof(buf_) < st.st_size) ?
 238                               xmalloc(st.st_size) : buf_);
 239                        ret = readlink(name, buf, st.st_size);
 240                        if (ret < 0)
 241                                die("readlink(%s)", name);
 242                        prep_temp_blob(temp, buf, st.st_size,
 243                                       (one->sha1_valid ?
 244                                        one->blob_sha1 : null_sha1),
 245                                       (one->sha1_valid ?
 246                                        one->mode : S_IFLNK));
 247                }
 248                else {
 249                        if (!one->sha1_valid)
 250                                strcpy(temp->hex, sha1_to_hex(null_sha1));
 251                        else
 252                                strcpy(temp->hex, sha1_to_hex(one->blob_sha1));
 253                        sprintf(temp->mode, "%06o",
 254                                S_IFREG |ce_permissions(st.st_mode));
 255                }
 256                return;
 257        }
 258        else {
 259                void *blob;
 260                char type[20];
 261                unsigned long size;
 262
 263                blob = read_sha1_file(one->blob_sha1, type, &size);
 264                if (!blob || strcmp(type, "blob"))
 265                        die("unable to read blob object for %s (%s)",
 266                            name, sha1_to_hex(one->blob_sha1));
 267                prep_temp_blob(temp, blob, size, one->blob_sha1, one->mode);
 268                free(blob);
 269        }
 270}
 271
 272static void remove_tempfile(void)
 273{
 274        int i;
 275
 276        for (i = 0; i < 2; i++)
 277                if (diff_temp[i].name == diff_temp[i].tmp_path) {
 278                        unlink(diff_temp[i].name);
 279                        diff_temp[i].name = NULL;
 280                }
 281}
 282
 283static void remove_tempfile_on_signal(int signo)
 284{
 285        remove_tempfile();
 286}
 287
 288/* An external diff command takes:
 289 *
 290 * diff-cmd name infile1 infile1-sha1 infile1-mode \
 291 *               infile2 infile2-sha1 infile2-mode.
 292 *
 293 */
 294void run_external_diff(const char *name,
 295                       struct diff_spec *one,
 296                       struct diff_spec *two)
 297{
 298        struct diff_tempfile *temp = diff_temp;
 299        pid_t pid;
 300        int status;
 301        static int atexit_asked = 0;
 302
 303        if (one && two) {
 304                prepare_temp_file(name, &temp[0], one);
 305                prepare_temp_file(name, &temp[1], two);
 306                if (! atexit_asked &&
 307                    (temp[0].name == temp[0].tmp_path ||
 308                     temp[1].name == temp[1].tmp_path)) {
 309                        atexit_asked = 1;
 310                        atexit(remove_tempfile);
 311                }
 312                signal(SIGINT, remove_tempfile_on_signal);
 313        }
 314
 315        fflush(NULL);
 316        pid = fork();
 317        if (pid < 0)
 318                die("unable to fork");
 319        if (!pid) {
 320                const char *pgm = external_diff();
 321                if (pgm) {
 322                        if (one && two)
 323                                execlp(pgm, pgm,
 324                                       name,
 325                                       temp[0].name, temp[0].hex, temp[0].mode,
 326                                       temp[1].name, temp[1].hex, temp[1].mode,
 327                                       NULL);
 328                        else
 329                                execlp(pgm, pgm, name, NULL);
 330                }
 331                /*
 332                 * otherwise we use the built-in one.
 333                 */
 334                if (one && two)
 335                        builtin_diff(name, temp);
 336                else
 337                        printf("* Unmerged path %s\n", name);
 338                exit(0);
 339        }
 340        if (waitpid(pid, &status, 0) < 0 ||
 341            !WIFEXITED(status) || WEXITSTATUS(status)) {
 342                /* Earlier we did not check the exit status because
 343                 * diff exits non-zero if files are different, and
 344                 * we are not interested in knowing that.  It was a
 345                 * mistake which made it harder to quit a diff-*
 346                 * session that uses the git-apply-patch-script as
 347                 * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
 348                 * should also exit non-zero only when it wants to
 349                 * abort the entire diff-* session.
 350                 */
 351                remove_tempfile();
 352                fprintf(stderr, "external diff died, stopping at %s.\n", name);
 353                exit(1);
 354        }
 355        remove_tempfile();
 356}
 357
 358void diff_addremove(int addremove, unsigned mode,
 359                    const unsigned char *sha1,
 360                    const char *base, const char *path)
 361{
 362        char concatpath[PATH_MAX];
 363        struct diff_spec spec[2], *one, *two;
 364
 365        memcpy(spec[0].blob_sha1, sha1, 20);
 366        spec[0].mode = mode;
 367        spec[0].sha1_valid = spec[0].file_valid = 1;
 368        spec[1].file_valid = 0;
 369
 370        if (addremove == '+') {
 371                one = spec + 1; two = spec;
 372        } else {
 373                one = spec; two = one + 1;
 374        }
 375        
 376        if (path) {
 377                strcpy(concatpath, base);
 378                strcat(concatpath, path);
 379        }
 380        run_external_diff(path ? concatpath : base, one, two);
 381}
 382
 383void diff_change(unsigned old_mode, unsigned new_mode,
 384                 const unsigned char *old_sha1,
 385                 const unsigned char *new_sha1,
 386                 const char *base, const char *path) {
 387        char concatpath[PATH_MAX];
 388        struct diff_spec spec[2];
 389
 390        memcpy(spec[0].blob_sha1, old_sha1, 20);
 391        spec[0].mode = old_mode;
 392        memcpy(spec[1].blob_sha1, new_sha1, 20);
 393        spec[1].mode = new_mode;
 394        spec[0].sha1_valid = spec[0].file_valid = 1;
 395        spec[1].sha1_valid = spec[1].file_valid = 1;
 396
 397        if (path) {
 398                strcpy(concatpath, base);
 399                strcat(concatpath, path);
 400        }
 401        run_external_diff(path ? concatpath : base, &spec[0], &spec[1]);
 402}
 403
 404void diff_unmerge(const char *path)
 405{
 406        run_external_diff(path, NULL, NULL);
 407}