diff-lib.con commit user manual: answer some comments from Junio (01997b4)
   1/*
   2 * Copyright (C) 2005 Junio C Hamano
   3 */
   4#include "cache.h"
   5#include "quote.h"
   6#include "commit.h"
   7#include "diff.h"
   8#include "diffcore.h"
   9#include "revision.h"
  10
  11/*
  12 * diff-files
  13 */
  14
  15int run_diff_files(struct rev_info *revs, int silent_on_removed)
  16{
  17        int entries, i;
  18        int diff_unmerged_stage = revs->max_count;
  19
  20        if (diff_unmerged_stage < 0)
  21                diff_unmerged_stage = 2;
  22        entries = read_cache();
  23        if (entries < 0) {
  24                perror("read_cache");
  25                return -1;
  26        }
  27        for (i = 0; i < entries; i++) {
  28                struct stat st;
  29                unsigned int oldmode, newmode;
  30                struct cache_entry *ce = active_cache[i];
  31                int changed;
  32
  33                if (!ce_path_match(ce, revs->prune_data))
  34                        continue;
  35
  36                if (ce_stage(ce)) {
  37                        struct combine_diff_path *dpath;
  38                        int num_compare_stages = 0;
  39                        size_t path_len;
  40
  41                        path_len = ce_namelen(ce);
  42
  43                        dpath = xmalloc (combine_diff_path_size (5, path_len));
  44                        dpath->path = (char *) &(dpath->parent[5]);
  45
  46                        dpath->next = NULL;
  47                        dpath->len = path_len;
  48                        memcpy(dpath->path, ce->name, path_len);
  49                        dpath->path[path_len] = '\0';
  50                        dpath->mode = 0;
  51                        hashclr(dpath->sha1);
  52                        memset(&(dpath->parent[0]), 0,
  53                                        sizeof(struct combine_diff_parent)*5);
  54
  55                        while (i < entries) {
  56                                struct cache_entry *nce = active_cache[i];
  57                                int stage;
  58
  59                                if (strcmp(ce->name, nce->name))
  60                                        break;
  61
  62                                /* Stage #2 (ours) is the first parent,
  63                                 * stage #3 (theirs) is the second.
  64                                 */
  65                                stage = ce_stage(nce);
  66                                if (2 <= stage) {
  67                                        int mode = ntohl(nce->ce_mode);
  68                                        num_compare_stages++;
  69                                        hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
  70                                        dpath->parent[stage-2].mode =
  71                                                canon_mode(mode);
  72                                        dpath->parent[stage-2].status =
  73                                                DIFF_STATUS_MODIFIED;
  74                                }
  75
  76                                /* diff against the proper unmerged stage */
  77                                if (stage == diff_unmerged_stage)
  78                                        ce = nce;
  79                                i++;
  80                        }
  81                        /*
  82                         * Compensate for loop update
  83                         */
  84                        i--;
  85
  86                        if (revs->combine_merges && num_compare_stages == 2) {
  87                                show_combined_diff(dpath, 2,
  88                                                   revs->dense_combined_merges,
  89                                                   revs);
  90                                free(dpath);
  91                                continue;
  92                        }
  93                        free(dpath);
  94                        dpath = NULL;
  95
  96                        /*
  97                         * Show the diff for the 'ce' if we found the one
  98                         * from the desired stage.
  99                         */
 100                        diff_unmerge(&revs->diffopt, ce->name);
 101                        if (ce_stage(ce) != diff_unmerged_stage)
 102                                continue;
 103                }
 104
 105                if (lstat(ce->name, &st) < 0) {
 106                        if (errno != ENOENT && errno != ENOTDIR) {
 107                                perror(ce->name);
 108                                continue;
 109                        }
 110                        if (silent_on_removed)
 111                                continue;
 112                        diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
 113                                       ce->sha1, ce->name, NULL);
 114                        continue;
 115                }
 116                changed = ce_match_stat(ce, &st, 0);
 117                if (!changed && !revs->diffopt.find_copies_harder)
 118                        continue;
 119                oldmode = ntohl(ce->ce_mode);
 120
 121                newmode = canon_mode(st.st_mode);
 122                if (!trust_executable_bit &&
 123                    S_ISREG(newmode) && S_ISREG(oldmode) &&
 124                    ((newmode ^ oldmode) == 0111))
 125                        newmode = oldmode;
 126                diff_change(&revs->diffopt, oldmode, newmode,
 127                            ce->sha1, (changed ? null_sha1 : ce->sha1),
 128                            ce->name, NULL);
 129
 130        }
 131        diffcore_std(&revs->diffopt);
 132        diff_flush(&revs->diffopt);
 133        return 0;
 134}
 135
 136/*
 137 * diff-index
 138 */
 139
 140/* A file entry went away or appeared */
 141static void diff_index_show_file(struct rev_info *revs,
 142                                 const char *prefix,
 143                                 struct cache_entry *ce,
 144                                 unsigned char *sha1, unsigned int mode)
 145{
 146        diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
 147                       sha1, ce->name, NULL);
 148}
 149
 150static int get_stat_data(struct cache_entry *ce,
 151                         unsigned char **sha1p,
 152                         unsigned int *modep,
 153                         int cached, int match_missing)
 154{
 155        unsigned char *sha1 = ce->sha1;
 156        unsigned int mode = ce->ce_mode;
 157
 158        if (!cached) {
 159                static unsigned char no_sha1[20];
 160                int changed;
 161                struct stat st;
 162                if (lstat(ce->name, &st) < 0) {
 163                        if (errno == ENOENT && match_missing) {
 164                                *sha1p = sha1;
 165                                *modep = mode;
 166                                return 0;
 167                        }
 168                        return -1;
 169                }
 170                changed = ce_match_stat(ce, &st, 0);
 171                if (changed) {
 172                        mode = create_ce_mode(st.st_mode);
 173                        if (!trust_executable_bit && S_ISREG(st.st_mode))
 174                                mode = ce->ce_mode;
 175                        sha1 = no_sha1;
 176                }
 177        }
 178
 179        *sha1p = sha1;
 180        *modep = mode;
 181        return 0;
 182}
 183
 184static void show_new_file(struct rev_info *revs,
 185                          struct cache_entry *new,
 186                          int cached, int match_missing)
 187{
 188        unsigned char *sha1;
 189        unsigned int mode;
 190
 191        /* New file in the index: it might actually be different in
 192         * the working copy.
 193         */
 194        if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
 195                return;
 196
 197        diff_index_show_file(revs, "+", new, sha1, mode);
 198}
 199
 200static int show_modified(struct rev_info *revs,
 201                         struct cache_entry *old,
 202                         struct cache_entry *new,
 203                         int report_missing,
 204                         int cached, int match_missing)
 205{
 206        unsigned int mode, oldmode;
 207        unsigned char *sha1;
 208
 209        if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
 210                if (report_missing)
 211                        diff_index_show_file(revs, "-", old,
 212                                             old->sha1, old->ce_mode);
 213                return -1;
 214        }
 215
 216        if (revs->combine_merges && !cached &&
 217            (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) {
 218                struct combine_diff_path *p;
 219                int pathlen = ce_namelen(new);
 220
 221                p = xmalloc(combine_diff_path_size(2, pathlen));
 222                p->path = (char *) &p->parent[2];
 223                p->next = NULL;
 224                p->len = pathlen;
 225                memcpy(p->path, new->name, pathlen);
 226                p->path[pathlen] = 0;
 227                p->mode = ntohl(mode);
 228                hashclr(p->sha1);
 229                memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
 230                p->parent[0].status = DIFF_STATUS_MODIFIED;
 231                p->parent[0].mode = ntohl(new->ce_mode);
 232                hashcpy(p->parent[0].sha1, new->sha1);
 233                p->parent[1].status = DIFF_STATUS_MODIFIED;
 234                p->parent[1].mode = ntohl(old->ce_mode);
 235                hashcpy(p->parent[1].sha1, old->sha1);
 236                show_combined_diff(p, 2, revs->dense_combined_merges, revs);
 237                free(p);
 238                return 0;
 239        }
 240
 241        oldmode = old->ce_mode;
 242        if (mode == oldmode && !hashcmp(sha1, old->sha1) &&
 243            !revs->diffopt.find_copies_harder)
 244                return 0;
 245
 246        mode = ntohl(mode);
 247        oldmode = ntohl(oldmode);
 248
 249        diff_change(&revs->diffopt, oldmode, mode,
 250                    old->sha1, sha1, old->name, NULL);
 251        return 0;
 252}
 253
 254static int diff_cache(struct rev_info *revs,
 255                      struct cache_entry **ac, int entries,
 256                      const char **pathspec,
 257                      int cached, int match_missing)
 258{
 259        while (entries) {
 260                struct cache_entry *ce = *ac;
 261                int same = (entries > 1) && ce_same_name(ce, ac[1]);
 262
 263                if (!ce_path_match(ce, pathspec))
 264                        goto skip_entry;
 265
 266                switch (ce_stage(ce)) {
 267                case 0:
 268                        /* No stage 1 entry? That means it's a new file */
 269                        if (!same) {
 270                                show_new_file(revs, ce, cached, match_missing);
 271                                break;
 272                        }
 273                        /* Show difference between old and new */
 274                        show_modified(revs,ac[1], ce, 1,
 275                                      cached, match_missing);
 276                        break;
 277                case 1:
 278                        /* No stage 3 (merge) entry?
 279                         * That means it's been deleted.
 280                         */
 281                        if (!same) {
 282                                diff_index_show_file(revs, "-", ce,
 283                                                     ce->sha1, ce->ce_mode);
 284                                break;
 285                        }
 286                        /* We come here with ce pointing at stage 1
 287                         * (original tree) and ac[1] pointing at stage
 288                         * 3 (unmerged).  show-modified with
 289                         * report-missing set to false does not say the
 290                         * file is deleted but reports true if work
 291                         * tree does not have it, in which case we
 292                         * fall through to report the unmerged state.
 293                         * Otherwise, we show the differences between
 294                         * the original tree and the work tree.
 295                         */
 296                        if (!cached &&
 297                            !show_modified(revs, ce, ac[1], 0,
 298                                           cached, match_missing))
 299                                break;
 300                        /* fallthru */
 301                case 3:
 302                        diff_unmerge(&revs->diffopt, ce->name);
 303                        break;
 304
 305                default:
 306                        die("impossible cache entry stage");
 307                }
 308
 309skip_entry:
 310                /*
 311                 * Ignore all the different stages for this file,
 312                 * we've handled the relevant cases now.
 313                 */
 314                do {
 315                        ac++;
 316                        entries--;
 317                } while (entries && ce_same_name(ce, ac[0]));
 318        }
 319        return 0;
 320}
 321
 322/*
 323 * This turns all merge entries into "stage 3". That guarantees that
 324 * when we read in the new tree (into "stage 1"), we won't lose sight
 325 * of the fact that we had unmerged entries.
 326 */
 327static void mark_merge_entries(void)
 328{
 329        int i;
 330        for (i = 0; i < active_nr; i++) {
 331                struct cache_entry *ce = active_cache[i];
 332                if (!ce_stage(ce))
 333                        continue;
 334                ce->ce_flags |= htons(CE_STAGEMASK);
 335        }
 336}
 337
 338int run_diff_index(struct rev_info *revs, int cached)
 339{
 340        int ret;
 341        struct object *ent;
 342        struct tree *tree;
 343        const char *tree_name;
 344        int match_missing = 0;
 345
 346        /* 
 347         * Backward compatibility wart - "diff-index -m" does
 348         * not mean "do not ignore merges", but totally different.
 349         */
 350        if (!revs->ignore_merges)
 351                match_missing = 1;
 352
 353        if (read_cache() < 0) {
 354                perror("read_cache");
 355                return -1;
 356        }
 357        mark_merge_entries();
 358
 359        ent = revs->pending.objects[0].item;
 360        tree_name = revs->pending.objects[0].name;
 361        tree = parse_tree_indirect(ent->sha1);
 362        if (!tree)
 363                return error("bad tree object %s", tree_name);
 364        if (read_tree(tree, 1, revs->prune_data))
 365                return error("unable to read tree object %s", tree_name);
 366        ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
 367                         cached, match_missing);
 368        diffcore_std(&revs->diffopt);
 369        diff_flush(&revs->diffopt);
 370        return ret;
 371}