26ddf00aa3d8977d9366d1a7ec2b794551c1a5c1
   1/*
   2 * Copyright (C) 2005 Junio C Hamano
   3 * Copyright (C) 2010 Google Inc.
   4 */
   5#include "cache.h"
   6#include "diff.h"
   7#include "diffcore.h"
   8#include "xdiff-interface.h"
   9#include "kwset.h"
  10
  11typedef int (*pickaxe_fn)(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws);
  12
  13static void pickaxe(struct diff_queue_struct *q, struct diff_options *o,
  14                    regex_t *regexp, kwset_t kws, pickaxe_fn fn)
  15{
  16        int i;
  17        struct diff_queue_struct outq;
  18
  19        DIFF_QUEUE_CLEAR(&outq);
  20
  21        if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
  22                /* Showing the whole changeset if needle exists */
  23                for (i = 0; i < q->nr; i++) {
  24                        struct diff_filepair *p = q->queue[i];
  25                        if (fn(p, o, regexp, kws))
  26                                return; /* do not munge the queue */
  27                }
  28
  29                /*
  30                 * Otherwise we will clear the whole queue by copying
  31                 * the empty outq at the end of this function, but
  32                 * first clear the current entries in the queue.
  33                 */
  34                for (i = 0; i < q->nr; i++)
  35                        diff_free_filepair(q->queue[i]);
  36        } else {
  37                /* Showing only the filepairs that has the needle */
  38                for (i = 0; i < q->nr; i++) {
  39                        struct diff_filepair *p = q->queue[i];
  40                        if (fn(p, o, regexp, kws))
  41                                diff_q(&outq, p);
  42                        else
  43                                diff_free_filepair(p);
  44                }
  45        }
  46
  47        free(q->queue);
  48        *q = outq;
  49}
  50
  51struct diffgrep_cb {
  52        regex_t *regexp;
  53        int hit;
  54};
  55
  56static void diffgrep_consume(void *priv, char *line, unsigned long len)
  57{
  58        struct diffgrep_cb *data = priv;
  59        regmatch_t regmatch;
  60        int hold;
  61
  62        if (line[0] != '+' && line[0] != '-')
  63                return;
  64        if (data->hit)
  65                /*
  66                 * NEEDSWORK: we should have a way to terminate the
  67                 * caller early.
  68                 */
  69                return;
  70        /* Yuck -- line ought to be "const char *"! */
  71        hold = line[len];
  72        line[len] = '\0';
  73        data->hit = !regexec(data->regexp, line + 1, 1, &regmatch, 0);
  74        line[len] = hold;
  75}
  76
  77static int diff_grep(struct diff_filepair *p, struct diff_options *o,
  78                     regex_t *regexp, kwset_t kws)
  79{
  80        regmatch_t regmatch;
  81        struct userdiff_driver *textconv_one = NULL;
  82        struct userdiff_driver *textconv_two = NULL;
  83        mmfile_t mf1, mf2;
  84        int hit;
  85
  86        if (diff_unmodified_pair(p))
  87                return 0;
  88
  89        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
  90                textconv_one = get_textconv(p->one);
  91                textconv_two = get_textconv(p->two);
  92        }
  93
  94        mf1.size = fill_textconv(textconv_one, p->one, &mf1.ptr);
  95        mf2.size = fill_textconv(textconv_two, p->two, &mf2.ptr);
  96
  97        if (!DIFF_FILE_VALID(p->one)) {
  98                if (!DIFF_FILE_VALID(p->two))
  99                        return 0; /* ignore unmerged */
 100                /* created "two" -- does it have what we are looking for? */
 101                hit = !regexec(regexp, mf2.ptr, 1, &regmatch, 0);
 102        } else if (!DIFF_FILE_VALID(p->two)) {
 103                /* removed "one" -- did it have what we are looking for? */
 104                hit = !regexec(regexp, mf1.ptr, 1, &regmatch, 0);
 105        } else {
 106                /*
 107                 * We have both sides; need to run textual diff and see if
 108                 * the pattern appears on added/deleted lines.
 109                 */
 110                struct diffgrep_cb ecbdata;
 111                xpparam_t xpp;
 112                xdemitconf_t xecfg;
 113
 114                memset(&xpp, 0, sizeof(xpp));
 115                memset(&xecfg, 0, sizeof(xecfg));
 116                ecbdata.regexp = regexp;
 117                ecbdata.hit = 0;
 118                xecfg.ctxlen = o->context;
 119                xecfg.interhunkctxlen = o->interhunkcontext;
 120                xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata,
 121                              &xpp, &xecfg);
 122                hit = ecbdata.hit;
 123        }
 124        if (textconv_one)
 125                free(mf1.ptr);
 126        if (textconv_two)
 127                free(mf2.ptr);
 128        return hit;
 129}
 130
 131static void diffcore_pickaxe_grep(struct diff_options *o)
 132{
 133        int err;
 134        regex_t regex;
 135        int cflags = REG_EXTENDED | REG_NEWLINE;
 136
 137        if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE))
 138                cflags |= REG_ICASE;
 139
 140        err = regcomp(&regex, o->pickaxe, cflags);
 141        if (err) {
 142                char errbuf[1024];
 143                regerror(err, &regex, errbuf, 1024);
 144                regfree(&regex);
 145                die("invalid log-grep regex: %s", errbuf);
 146        }
 147
 148        pickaxe(&diff_queued_diff, o, &regex, NULL, diff_grep);
 149
 150        regfree(&regex);
 151        return;
 152}
 153
 154static unsigned int contains(mmfile_t *mf, struct diff_options *o,
 155                             regex_t *regexp, kwset_t kws)
 156{
 157        unsigned int cnt;
 158        unsigned long sz;
 159        const char *data;
 160
 161        sz = mf->size;
 162        data = mf->ptr;
 163        cnt = 0;
 164
 165        if (regexp) {
 166                regmatch_t regmatch;
 167                int flags = 0;
 168
 169                assert(data[sz] == '\0');
 170                while (*data && !regexec(regexp, data, 1, &regmatch, flags)) {
 171                        flags |= REG_NOTBOL;
 172                        data += regmatch.rm_eo;
 173                        if (*data && regmatch.rm_so == regmatch.rm_eo)
 174                                data++;
 175                        cnt++;
 176                }
 177
 178        } else { /* Classic exact string match */
 179                while (sz) {
 180                        struct kwsmatch kwsm;
 181                        size_t offset = kwsexec(kws, data, sz, &kwsm);
 182                        const char *found;
 183                        if (offset == -1)
 184                                break;
 185                        else
 186                                found = data + offset;
 187                        sz -= found - data + kwsm.size[0];
 188                        data = found + kwsm.size[0];
 189                        cnt++;
 190                }
 191        }
 192        return cnt;
 193}
 194
 195static int has_changes(struct diff_filepair *p, struct diff_options *o,
 196                       regex_t *regexp, kwset_t kws)
 197{
 198        struct userdiff_driver *textconv_one = NULL;
 199        struct userdiff_driver *textconv_two = NULL;
 200        mmfile_t mf1, mf2;
 201        int ret;
 202
 203        if (!o->pickaxe[0])
 204                return 0;
 205
 206        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
 207                textconv_one = get_textconv(p->one);
 208                textconv_two = get_textconv(p->two);
 209        }
 210
 211        /*
 212         * If we have an unmodified pair, we know that the count will be the
 213         * same and don't even have to load the blobs. Unless textconv is in
 214         * play, _and_ we are using two different textconv filters (e.g.,
 215         * because a pair is an exact rename with different textconv attributes
 216         * for each side, which might generate different content).
 217         */
 218        if (textconv_one == textconv_two && diff_unmodified_pair(p))
 219                return 0;
 220
 221        mf1.size = fill_textconv(textconv_one, p->one, &mf1.ptr);
 222        mf2.size = fill_textconv(textconv_two, p->two, &mf2.ptr);
 223
 224        if (!DIFF_FILE_VALID(p->one)) {
 225                if (!DIFF_FILE_VALID(p->two))
 226                        ret = 0; /* ignore unmerged */
 227                /* created */
 228                ret = contains(&mf2, o, regexp, kws) != 0;
 229        }
 230        else if (!DIFF_FILE_VALID(p->two)) /* removed */
 231                ret = contains(&mf1, o, regexp, kws) != 0;
 232        else
 233                ret = contains(&mf1, o, regexp, kws) !=
 234                      contains(&mf2, o, regexp, kws);
 235
 236        if (textconv_one)
 237                free(mf1.ptr);
 238        if (textconv_two)
 239                free(mf2.ptr);
 240        diff_free_filespec_data(p->one);
 241        diff_free_filespec_data(p->two);
 242
 243        return ret;
 244}
 245
 246static void diffcore_pickaxe_count(struct diff_options *o)
 247{
 248        const char *needle = o->pickaxe;
 249        int opts = o->pickaxe_opts;
 250        unsigned long len = strlen(needle);
 251        regex_t regex, *regexp = NULL;
 252        kwset_t kws = NULL;
 253
 254        if (opts & DIFF_PICKAXE_REGEX) {
 255                int err;
 256                err = regcomp(&regex, needle, REG_EXTENDED | REG_NEWLINE);
 257                if (err) {
 258                        /* The POSIX.2 people are surely sick */
 259                        char errbuf[1024];
 260                        regerror(err, &regex, errbuf, 1024);
 261                        regfree(&regex);
 262                        die("invalid pickaxe regex: %s", errbuf);
 263                }
 264                regexp = &regex;
 265        } else {
 266                kws = kwsalloc(DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE)
 267                               ? tolower_trans_tbl : NULL);
 268                kwsincr(kws, needle, len);
 269                kwsprep(kws);
 270        }
 271
 272        pickaxe(&diff_queued_diff, o, regexp, kws, has_changes);
 273
 274        if (opts & DIFF_PICKAXE_REGEX)
 275                regfree(&regex);
 276        else
 277                kwsfree(kws);
 278        return;
 279}
 280
 281void diffcore_pickaxe(struct diff_options *o)
 282{
 283        /* Might want to warn when both S and G are on; I don't care... */
 284        if (o->pickaxe_opts & DIFF_PICKAXE_KIND_G)
 285                diffcore_pickaxe_grep(o);
 286        else
 287                diffcore_pickaxe_count(o);
 288}