Merge branch 'ab/grep-preparatory-cleanup'
authorJunio C Hamano <gitster@pobox.com>
Fri, 2 Jun 2017 06:06:05 +0000 (15:06 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 2 Jun 2017 06:06:06 +0000 (15:06 +0900)
The internal implementation of "git grep" has seen some clean-up.

* ab/grep-preparatory-cleanup: (31 commits)
grep: assert that threading is enabled when calling grep_{lock,unlock}
grep: given --threads with NO_PTHREADS=YesPlease, warn
pack-objects: fix buggy warning about threads
pack-objects & index-pack: add test for --threads warning
test-lib: add a PTHREADS prerequisite
grep: move is_fixed() earlier to avoid forward declaration
grep: change internal *pcre* variable & function names to be *pcre1*
grep: change the internal PCRE macro names to be PCRE1
grep: factor test for \0 in grep patterns into a function
grep: remove redundant regflags assignments
grep: catch a missing enum in switch statement
perf: add a comparison test of log --grep regex engines with -F
perf: add a comparison test of log --grep regex engines
perf: add a comparison test of grep regex engines with -F
perf: add a comparison test of grep regex engines
perf: emit progress output when unpacking & building
perf: add a GIT_PERF_MAKE_COMMAND for when *_MAKE_OPTS won't do
grep: add tests to fix blind spots with \0 patterns
grep: prepare for testing binary regexes containing rx metacharacters
grep: add a test helper function for less verbose -f \0 tests
...

1  2 
Makefile
builtin/grep.c
builtin/pack-objects.c
revision.c
t/perf/README
t/t4202-log.sh
t/test-lib.sh
diff --combined Makefile
index 2ed6db728a8479adf7df76d1525a50733fa013ae,a79274e5e69c9a189f6d5a4ee857fedd93a5150d..7bea912e5a2ffb6d2293b4b768c4fc39cc8c21b4
+++ b/Makefile
@@@ -24,8 -24,10 +24,10 @@@ all:
  # Define NO_OPENSSL environment variable if you do not have OpenSSL.
  # This also implies BLK_SHA1.
  #
- # Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
- # able to use Perl-compatible regular expressions.
+ # Define USE_LIBPCRE if you have and want to use libpcre. Various
+ # commands such as log and grep offer runtime options to use
+ # Perl-compatible regular expressions instead of standard or extended
+ # POSIX regular expressions.
  #
  # Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
  # /foo/bar/include and /foo/bar/lib directories.
@@@ -842,7 -844,6 +844,7 @@@ LIB_OBJS += streaming.
  LIB_OBJS += string-list.o
  LIB_OBJS += submodule.o
  LIB_OBJS += submodule-config.o
 +LIB_OBJS += sub-process.o
  LIB_OBJS += symlinks.o
  LIB_OBJS += tag.o
  LIB_OBJS += tempfile.o
@@@ -1087,7 -1088,7 +1089,7 @@@ ifdef NO_LIBGEN_
  endif
  
  ifdef USE_LIBPCRE
-       BASIC_CFLAGS += -DUSE_LIBPCRE
+       BASIC_CFLAGS += -DUSE_LIBPCRE1
        ifdef LIBPCREDIR
                BASIC_CFLAGS += -I$(LIBPCREDIR)/include
                EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib)
@@@ -2239,8 -2240,9 +2241,9 @@@ GIT-BUILD-OPTIONS: FORC
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@+
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@+
        @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@+
-       @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@+
+       @echo USE_LIBPCRE1=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@+
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+
+       @echo NO_PTHREADS=\''$(subst ','\'',$(subst ','\'',$(NO_PTHREADS)))'\' >>$@+
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+
        @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
        @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
@@@ -2271,6 -2273,9 +2274,9 @@@ endi
  ifdef GIT_PERF_MAKE_OPTS
        @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@+
  endif
+ ifdef GIT_PERF_MAKE_COMMAND
+       @echo GIT_PERF_MAKE_COMMAND=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_COMMAND)))'\' >>$@+
+ endif
  ifdef GIT_INTEROP_MAKE_OPTS
        @echo GIT_INTEROP_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_INTEROP_MAKE_OPTS)))'\' >>$@+
  endif
diff --combined builtin/grep.c
index 72a19699a829646eb5261d934ceebff16b5d4f4d,b1095362fbf9a4726367b96e0f34d38faf27d9bd..7df9c253eebb3053693769d14efe0551393dfd4d
@@@ -73,14 -73,14 +73,14 @@@ static pthread_mutex_t grep_mutex
  
  static inline void grep_lock(void)
  {
-       if (num_threads)
-               pthread_mutex_lock(&grep_mutex);
+       assert(num_threads);
+       pthread_mutex_lock(&grep_mutex);
  }
  
  static inline void grep_unlock(void)
  {
-       if (num_threads)
-               pthread_mutex_unlock(&grep_mutex);
+       assert(num_threads);
+       pthread_mutex_unlock(&grep_mutex);
  }
  
  /* Signalled when a new work_item is added to todo. */
@@@ -289,6 -289,17 +289,17 @@@ static int grep_cmd_config(const char *
                if (num_threads < 0)
                        die(_("invalid number of threads specified (%d) for %s"),
                            num_threads, var);
+ #ifdef NO_PTHREADS
+               else if (num_threads && num_threads != 1) {
+                       /*
+                        * TRANSLATORS: %s is the configuration
+                        * variable for tweaking threads, currently
+                        * grep.threads
+                        */
+                       warning(_("no threads support, ignoring %s"), var);
+                       num_threads = 0;
+               }
+ #endif
        }
  
        return st;
@@@ -495,6 -506,8 +506,8 @@@ static void compile_submodule_options(c
                break;
        case GREP_PATTERN_TYPE_UNSPECIFIED:
                break;
+       default:
+               die("BUG: Added a new grep pattern type without updating switch statement");
        }
  
        for (pattern = opt->pattern_list; pattern != NULL;
@@@ -866,7 -879,7 +879,7 @@@ static int grep_directory(struct grep_o
        if (exc_std)
                setup_standard_excludes(&dir);
  
 -      fill_directory(&dir, pathspec);
 +      fill_directory(&dir, &the_index, pathspec);
        for (i = 0; i < dir.nr; i++) {
                if (!dir_path_match(dir.entries[i], pathspec, 0, NULL))
                        continue;
@@@ -1190,18 -1203,16 +1203,18 @@@ int cmd_grep(int argc, const char **arg
                        break;
                }
  
 -              if (get_sha1_with_context(arg, 0, oid.hash, &oc)) {
 +              if (get_sha1_with_context(arg, GET_SHA1_RECORD_PATH,
 +                                        oid.hash, &oc)) {
                        if (seen_dashdash)
                                die(_("unable to resolve revision: %s"), arg);
                        break;
                }
  
 -              object = parse_object_or_die(oid.hash, arg);
 +              object = parse_object_or_die(&oid, arg);
                if (!seen_dashdash)
                        verify_non_filename(prefix, arg);
                add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
 +              free(oc.path);
        }
  
        /*
        else if (num_threads < 0)
                die(_("invalid number of threads specified (%d)"), num_threads);
  #else
+       if (num_threads)
+               warning(_("no threads support, ignoring --threads"));
        num_threads = 0;
  #endif
  
diff --combined builtin/pack-objects.c
index 80439047aa21b9291417cb6ef30164d41878b728,f1baf05dfe31dc9bbb91c1b6ff44c123fa1b3d38..f672225def033595602bc4ff91ee26edd9804944
@@@ -44,7 -44,7 +44,7 @@@ static uint32_t nr_result, nr_written
  static int non_empty;
  static int reuse_delta = 1, reuse_object = 1;
  static int keep_unreachable, unpack_unreachable, include_tag;
 -static unsigned long unpack_unreachable_expiration;
 +static timestamp_t unpack_unreachable_expiration;
  static int pack_loose_unreachable;
  static int local;
  static int have_non_local_packs;
@@@ -106,14 -106,12 +106,14 @@@ static void *get_delta(struct object_en
        void *buf, *base_buf, *delta_buf;
        enum object_type type;
  
 -      buf = read_sha1_file(entry->idx.sha1, &type, &size);
 +      buf = read_sha1_file(entry->idx.oid.hash, &type, &size);
        if (!buf)
 -              die("unable to read %s", sha1_to_hex(entry->idx.sha1));
 -      base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
 +              die("unable to read %s", oid_to_hex(&entry->idx.oid));
 +      base_buf = read_sha1_file(entry->delta->idx.oid.hash, &type,
 +                                &base_size);
        if (!base_buf)
 -              die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
 +              die("unable to read %s",
 +                  oid_to_hex(&entry->delta->idx.oid));
        delta_buf = diff_delta(base_buf, base_size,
                               buf, size, &delta_size, 0);
        if (!delta_buf || delta_size != entry->delta_size)
@@@ -251,14 -249,12 +251,14 @@@ static unsigned long write_no_reuse_obj
        if (!usable_delta) {
                if (entry->type == OBJ_BLOB &&
                    entry->size > big_file_threshold &&
 -                  (st = open_istream(entry->idx.sha1, &type, &size, NULL)) != NULL)
 +                  (st = open_istream(entry->idx.oid.hash, &type, &size, NULL)) != NULL)
                        buf = NULL;
                else {
 -                      buf = read_sha1_file(entry->idx.sha1, &type, &size);
 +                      buf = read_sha1_file(entry->idx.oid.hash, &type,
 +                                           &size);
                        if (!buf)
 -                              die(_("unable to read %s"), sha1_to_hex(entry->idx.sha1));
 +                              die(_("unable to read %s"),
 +                                  oid_to_hex(&entry->idx.oid));
                }
                /*
                 * make sure no cached delta data remains from a
                        return 0;
                }
                sha1write(f, header, hdrlen);
 -              sha1write(f, entry->delta->idx.sha1, 20);
 +              sha1write(f, entry->delta->idx.oid.hash, 20);
                hdrlen += 20;
        } else {
                if (limit && hdrlen + datalen + 20 >= limit) {
                sha1write(f, header, hdrlen);
        }
        if (st) {
 -              datalen = write_large_blob_data(st, f, entry->idx.sha1);
 +              datalen = write_large_blob_data(st, f, entry->idx.oid.hash);
                close_istream(st);
        } else {
                sha1write(f, buf, datalen);
@@@ -373,8 -369,7 +373,8 @@@ static off_t write_reuse_object(struct 
        datalen = revidx[1].offset - offset;
        if (!pack_to_stdout && p->index_version > 1 &&
            check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
 -              error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
 +              error("bad packed object CRC for %s",
 +                    oid_to_hex(&entry->idx.oid));
                unuse_pack(&w_curs);
                return write_no_reuse_object(f, entry, limit, usable_delta);
        }
  
        if (!pack_to_stdout && p->index_version == 1 &&
            check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
 -              error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
 +              error("corrupt packed object for %s",
 +                    oid_to_hex(&entry->idx.oid));
                unuse_pack(&w_curs);
                return write_no_reuse_object(f, entry, limit, usable_delta);
        }
                        return 0;
                }
                sha1write(f, header, hdrlen);
 -              sha1write(f, entry->delta->idx.sha1, 20);
 +              sha1write(f, entry->delta->idx.oid.hash, 20);
                hdrlen += 20;
                reused_delta++;
        } else {
@@@ -515,7 -509,7 +515,7 @@@ static enum write_one_status write_one(
        recursing = (e->idx.offset == 1);
        if (recursing) {
                warning("recursive delta detected for object %s",
 -                      sha1_to_hex(e->idx.sha1));
 +                      oid_to_hex(&e->idx.oid));
                return WRITE_ONE_RECURSIVE;
        } else if (e->idx.offset || e->preferred_base) {
                /* offset is non zero if object is written already. */
@@@ -1438,7 -1432,7 +1438,7 @@@ static void check_object(struct object_
                                ofs += 1;
                                if (!ofs || MSB(ofs, 7)) {
                                        error("delta base offset overflow in pack for %s",
 -                                            sha1_to_hex(entry->idx.sha1));
 +                                            oid_to_hex(&entry->idx.oid));
                                        goto give_up;
                                }
                                c = buf[used_0++];
                        ofs = entry->in_pack_offset - ofs;
                        if (ofs <= 0 || ofs >= entry->in_pack_offset) {
                                error("delta base offset out of bound for %s",
 -                                    sha1_to_hex(entry->idx.sha1));
 +                                    oid_to_hex(&entry->idx.oid));
                                goto give_up;
                        }
                        if (reuse_delta && !entry->preferred_base) {
                unuse_pack(&w_curs);
        }
  
 -      entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
 +      entry->type = sha1_object_info(entry->idx.oid.hash, &entry->size);
        /*
         * The error condition is checked in prepare_pack().  This is
         * to permit a missing preferred base object to be ignored
@@@ -1520,7 -1514,7 +1520,7 @@@ static int pack_offset_sort(const void 
  
        /* avoid filesystem trashing with loose objects */
        if (!a->in_pack && !b->in_pack)
 -              return hashcmp(a->idx.sha1, b->idx.sha1);
 +              return oidcmp(&a->idx.oid, &b->idx.oid);
  
        if (a->in_pack < b->in_pack)
                return -1;
@@@ -1566,8 -1560,7 +1566,8 @@@ static void drop_reused_delta(struct ob
                 * And if that fails, the error will be recorded in entry->type
                 * and dealt with in prepare_pack().
                 */
 -              entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
 +              entry->type = sha1_object_info(entry->idx.oid.hash,
 +                                             &entry->size);
        }
  }
  
@@@ -1859,29 -1852,26 +1859,29 @@@ static int try_delta(struct unpacked *t
        /* Load data if not already done */
        if (!trg->data) {
                read_lock();
 -              trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
 +              trg->data = read_sha1_file(trg_entry->idx.oid.hash, &type,
 +                                         &sz);
                read_unlock();
                if (!trg->data)
                        die("object %s cannot be read",
 -                          sha1_to_hex(trg_entry->idx.sha1));
 +                          oid_to_hex(&trg_entry->idx.oid));
                if (sz != trg_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
 -                          sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
 +                          oid_to_hex(&trg_entry->idx.oid), sz,
 +                          trg_size);
                *mem_usage += sz;
        }
        if (!src->data) {
                read_lock();
 -              src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
 +              src->data = read_sha1_file(src_entry->idx.oid.hash, &type,
 +                                         &sz);
                read_unlock();
                if (!src->data) {
                        if (src_entry->preferred_base) {
                                static int warned = 0;
                                if (!warned++)
                                        warning("object %s cannot be read",
 -                                              sha1_to_hex(src_entry->idx.sha1));
 +                                              oid_to_hex(&src_entry->idx.oid));
                                /*
                                 * Those objects are not included in the
                                 * resulting pack.  Be resilient and ignore
                                return 0;
                        }
                        die("object %s cannot be read",
 -                          sha1_to_hex(src_entry->idx.sha1));
 +                          oid_to_hex(&src_entry->idx.oid));
                }
                if (sz != src_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
 -                          sha1_to_hex(src_entry->idx.sha1), sz, src_size);
 +                          oid_to_hex(&src_entry->idx.oid), sz,
 +                          src_size);
                *mem_usage += sz;
        }
        if (!src->index) {
@@@ -2348,7 -2337,7 +2348,7 @@@ static void add_tag_chain(const struct 
        if (packlist_find(&to_pack, oid->hash, NULL))
                return;
  
 -      tag = lookup_tag(oid->hash);
 +      tag = lookup_tag(oid);
        while (1) {
                if (!tag || parse_tag(tag) || !tag->tagged)
                        die("unable to pack objects reachable from tag %s",
@@@ -2417,7 -2406,7 +2417,7 @@@ static void prepare_pack(int window, in
                        nr_deltas++;
                        if (entry->type < 0)
                                die("unable to get type of object %s",
 -                                  sha1_to_hex(entry->idx.sha1));
 +                                  oid_to_hex(&entry->idx.oid));
                } else {
                        if (entry->type < 0) {
                                /*
@@@ -2483,8 -2472,10 +2483,10 @@@ static int git_pack_config(const char *
                        die("invalid number of threads specified (%d)",
                            delta_search_threads);
  #ifdef NO_PTHREADS
-               if (delta_search_threads != 1)
+               if (delta_search_threads != 1) {
                        warning("no threads support, ignoring %s", k);
+                       delta_search_threads = 0;
+               }
  #endif
                return 0;
        }
@@@ -2686,7 -2677,7 +2688,7 @@@ static int has_sha1_pack_kept_or_nonloc
  static struct oid_array recent_objects;
  
  static int loosened_object_can_be_discarded(const struct object_id *oid,
 -                                          unsigned long mtime)
 +                                          timestamp_t mtime)
  {
        if (!unpack_unreachable_expiration)
                return 0;
@@@ -2728,11 -2719,7 +2730,11 @@@ static void loosen_unused_packed_object
   */
  static int pack_options_allow_reuse(void)
  {
 -      return pack_to_stdout && allow_ofs_delta;
 +      return pack_to_stdout &&
 +             allow_ofs_delta &&
 +             !ignore_packed_keep &&
 +             (!local || !have_non_local_packs) &&
 +             !incremental;
  }
  
  static int get_object_list_from_bitmap(struct rev_info *revs)
@@@ -2792,10 -2779,10 +2794,10 @@@ static void get_object_list(int ac, con
                                continue;
                        }
                        if (starts_with(line, "--shallow ")) {
 -                              unsigned char sha1[20];
 -                              if (get_sha1_hex(line + 10, sha1))
 +                              struct object_id oid;
 +                              if (get_oid_hex(line + 10, &oid))
                                        die("not an SHA-1 '%s'", line + 10);
 -                              register_shallow(sha1);
 +                              register_shallow(&oid);
                                use_bitmap_index = 0;
                                continue;
                        }
diff --combined revision.c
index 3680a43a6e35d9850414b42f663acdc184d4d366,c96265d89dea5f1df6094621e68a164239e4f9f6..f88c14bab3188c65631593640289b26d00a6a3fc
@@@ -59,10 -59,10 +59,10 @@@ static void mark_tree_contents_unintere
        while (tree_entry(&desc, &entry)) {
                switch (object_type(entry.mode)) {
                case OBJ_TREE:
 -                      mark_tree_uninteresting(lookup_tree(entry.oid->hash));
 +                      mark_tree_uninteresting(lookup_tree(entry.oid));
                        break;
                case OBJ_BLOB:
 -                      mark_blob_uninteresting(lookup_blob(entry.oid->hash));
 +                      mark_blob_uninteresting(lookup_blob(entry.oid));
                        break;
                default:
                        /* Subproject commit - not in this repository */
@@@ -177,23 -177,23 +177,23 @@@ void add_pending_object(struct rev_inf
  
  void add_head_to_pending(struct rev_info *revs)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct object *obj;
 -      if (get_sha1("HEAD", sha1))
 +      if (get_oid("HEAD", &oid))
                return;
 -      obj = parse_object(sha1);
 +      obj = parse_object(&oid);
        if (!obj)
                return;
        add_pending_object(revs, obj, "HEAD");
  }
  
  static struct object *get_reference(struct rev_info *revs, const char *name,
 -                                  const unsigned char *sha1,
 +                                  const struct object_id *oid,
                                    unsigned int flags)
  {
        struct object *object;
  
 -      object = parse_object(sha1);
 +      object = parse_object(oid);
        if (!object) {
                if (revs->ignore_missing)
                        return object;
        return object;
  }
  
 -void add_pending_sha1(struct rev_info *revs, const char *name,
 -                    const unsigned char *sha1, unsigned int flags)
 +void add_pending_oid(struct rev_info *revs, const char *name,
 +                    const struct object_id *oid, unsigned int flags)
  {
 -      struct object *object = get_reference(revs, name, sha1, flags);
 +      struct object *object = get_reference(revs, name, oid, flags);
        add_pending_object(revs, object, name);
  }
  
@@@ -228,9 -228,9 +228,9 @@@ static struct commit *handle_commit(str
                        add_pending_object(revs, object, tag->tag);
                if (!tag->tagged)
                        die("bad tag");
 -              object = parse_object(tag->tagged->oid.hash);
 +              object = parse_object(&tag->tagged->oid);
                if (!object) {
 -                      if (flags & UNINTERESTING)
 +                      if (revs->ignore_missing_links || (flags & UNINTERESTING))
                                return NULL;
                        die("bad object %s", oid_to_hex(&tag->tagged->oid));
                }
@@@ -884,7 -884,7 +884,7 @@@ static void cherry_pick_list(struct com
  /* How many extra uninteresting commits we want to see.. */
  #define SLOP 5
  
 -static int still_interesting(struct commit_list *src, unsigned long date, int slop,
 +static int still_interesting(struct commit_list *src, timestamp_t date, int slop,
                             struct commit **interesting_cache)
  {
        /*
@@@ -1018,7 -1018,7 +1018,7 @@@ static void limit_left_right(struct com
  static int limit_list(struct rev_info *revs)
  {
        int slop = SLOP;
 -      unsigned long date = ~0ul;
 +      timestamp_t date = TIME_MAX;
        struct commit_list *list = revs->commits;
        struct commit_list *newlist = NULL;
        struct commit_list **p = &newlist;
@@@ -1157,9 -1157,9 +1157,9 @@@ static int handle_one_ref(const char *p
        if (ref_excluded(cb->all_revs->ref_excludes, path))
            return 0;
  
 -      object = get_reference(cb->all_revs, path, oid->hash, cb->all_flags);
 +      object = get_reference(cb->all_revs, path, oid, cb->all_flags);
        add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
 -      add_pending_sha1(cb->all_revs, path, oid->hash, cb->all_flags);
 +      add_pending_oid(cb->all_revs, path, oid, cb->all_flags);
        return 0;
  }
  
@@@ -1200,7 -1200,7 +1200,7 @@@ static void handle_one_reflog_commit(st
  {
        struct all_refs_cb *cb = cb_data;
        if (!is_null_oid(oid)) {
 -              struct object *o = parse_object(oid->hash);
 +              struct object *o = parse_object(oid);
                if (o) {
                        o->flags |= cb->all_flags;
                        /* ??? CMDLINEFLAGS ??? */
  }
  
  static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
 -              const char *email, unsigned long timestamp, int tz,
 +              const char *email, timestamp_t timestamp, int tz,
                const char *message, void *cb_data)
  {
        handle_one_reflog_commit(ooid, cb_data);
@@@ -1249,7 -1249,7 +1249,7 @@@ static void add_cache_tree(struct cache
        int i;
  
        if (it->entry_count >= 0) {
 -              struct tree *tree = lookup_tree(it->sha1);
 +              struct tree *tree = lookup_tree(&it->oid);
                add_pending_object_with_path(revs, &tree->object, "",
                                             040000, path->buf);
        }
@@@ -1275,7 -1275,7 +1275,7 @@@ void add_index_objects_to_pending(struc
                if (S_ISGITLINK(ce->ce_mode))
                        continue;
  
 -              blob = lookup_blob(ce->oid.hash);
 +              blob = lookup_blob(&ce->oid);
                if (!blob)
                        die("unable to add index blob to traversal");
                add_pending_object_with_path(revs, &blob->object, "",
  static int add_parents_only(struct rev_info *revs, const char *arg_, int flags,
                            int exclude_parent)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct object *it;
        struct commit *commit;
        struct commit_list *parents;
                flags ^= UNINTERESTING | BOTTOM;
                arg++;
        }
 -      if (get_sha1_committish(arg, sha1))
 +      if (get_sha1_committish(arg, oid.hash))
                return 0;
        while (1) {
 -              it = get_reference(revs, arg, sha1, 0);
 +              it = get_reference(revs, arg, &oid, 0);
                if (!it && revs->ignore_missing)
                        return 0;
                if (it->type != OBJ_TAG)
                        break;
                if (!((struct tag*)it)->tagged)
                        return 0;
 -              hashcpy(sha1, ((struct tag*)it)->tagged->oid.hash);
 +              oidcpy(&oid, &((struct tag*)it)->tagged->oid);
        }
        if (it->type != OBJ_COMMIT)
                return 0;
@@@ -1389,16 -1389,16 +1389,16 @@@ static void prepare_show_merge(struct r
  {
        struct commit_list *bases;
        struct commit *head, *other;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        const char **prune = NULL;
        int i, prune_num = 1; /* counting terminating NULL */
  
 -      if (get_sha1("HEAD", sha1))
 +      if (get_oid("HEAD", &oid))
                die("--merge without HEAD?");
 -      head = lookup_commit_or_die(sha1, "HEAD");
 -      if (get_sha1("MERGE_HEAD", sha1))
 +      head = lookup_commit_or_die(&oid, "HEAD");
 +      if (get_oid("MERGE_HEAD", &oid))
                die("--merge without MERGE_HEAD?");
 -      other = lookup_commit_or_die(sha1, "MERGE_HEAD");
 +      other = lookup_commit_or_die(&oid, "MERGE_HEAD");
        add_pending_object(revs, &head->object, "HEAD");
        add_pending_object(revs, &other->object, "MERGE_HEAD");
        bases = get_merge_bases(head, other);
        revs->limited = 1;
  }
  
 +static int dotdot_missing(const char *arg, char *dotdot,
 +                        struct rev_info *revs, int symmetric)
 +{
 +      if (revs->ignore_missing)
 +              return 0;
 +      /* de-munge so we report the full argument */
 +      *dotdot = '.';
 +      die(symmetric
 +          ? "Invalid symmetric difference expression %s"
 +          : "Invalid revision range %s", arg);
 +}
 +
 +static int handle_dotdot_1(const char *arg, char *dotdot,
 +                         struct rev_info *revs, int flags,
 +                         int cant_be_filename,
 +                         struct object_context *a_oc,
 +                         struct object_context *b_oc)
 +{
 +      const char *a_name, *b_name;
 +      struct object_id a_oid, b_oid;
 +      struct object *a_obj, *b_obj;
 +      unsigned int a_flags, b_flags;
 +      int symmetric = 0;
 +      unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
 +      unsigned int oc_flags = GET_SHA1_COMMITTISH | GET_SHA1_RECORD_PATH;
 +
 +      a_name = arg;
 +      if (!*a_name)
 +              a_name = "HEAD";
 +
 +      b_name = dotdot + 2;
 +      if (*b_name == '.') {
 +              symmetric = 1;
 +              b_name++;
 +      }
 +      if (!*b_name)
 +              b_name = "HEAD";
 +
 +      if (get_sha1_with_context(a_name, oc_flags, a_oid.hash, a_oc) ||
 +          get_sha1_with_context(b_name, oc_flags, b_oid.hash, b_oc))
 +              return -1;
 +
 +      if (!cant_be_filename) {
 +              *dotdot = '.';
 +              verify_non_filename(revs->prefix, arg);
 +              *dotdot = '\0';
 +      }
 +
 +      a_obj = parse_object(&a_oid);
 +      b_obj = parse_object(&b_oid);
 +      if (!a_obj || !b_obj)
 +              return dotdot_missing(arg, dotdot, revs, symmetric);
 +
 +      if (!symmetric) {
 +              /* just A..B */
 +              b_flags = flags;
 +              a_flags = flags_exclude;
 +      } else {
 +              /* A...B -- find merge bases between the two */
 +              struct commit *a, *b;
 +              struct commit_list *exclude;
 +
 +              a = lookup_commit_reference(&a_obj->oid);
 +              b = lookup_commit_reference(&b_obj->oid);
 +              if (!a || !b)
 +                      return dotdot_missing(arg, dotdot, revs, symmetric);
 +
 +              exclude = get_merge_bases(a, b);
 +              add_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,
 +                                   flags_exclude);
 +              add_pending_commit_list(revs, exclude, flags_exclude);
 +              free_commit_list(exclude);
 +
 +              b_flags = flags;
 +              a_flags = flags | SYMMETRIC_LEFT;
 +      }
 +
 +      a_obj->flags |= a_flags;
 +      b_obj->flags |= b_flags;
 +      add_rev_cmdline(revs, a_obj, a_name, REV_CMD_LEFT, a_flags);
 +      add_rev_cmdline(revs, b_obj, b_name, REV_CMD_RIGHT, b_flags);
 +      add_pending_object_with_path(revs, a_obj, a_name, a_oc->mode, a_oc->path);
 +      add_pending_object_with_path(revs, b_obj, b_name, b_oc->mode, b_oc->path);
 +      return 0;
 +}
 +
 +static int handle_dotdot(const char *arg,
 +                       struct rev_info *revs, int flags,
 +                       int cant_be_filename)
 +{
 +      struct object_context a_oc, b_oc;
 +      char *dotdot = strstr(arg, "..");
 +      int ret;
 +
 +      if (!dotdot)
 +              return -1;
 +
 +      memset(&a_oc, 0, sizeof(a_oc));
 +      memset(&b_oc, 0, sizeof(b_oc));
 +
 +      *dotdot = '\0';
 +      ret = handle_dotdot_1(arg, dotdot, revs, flags, cant_be_filename,
 +                            &a_oc, &b_oc);
 +      *dotdot = '.';
 +
 +      free(a_oc.path);
 +      free(b_oc.path);
 +
 +      return ret;
 +}
 +
  int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
  {
        struct object_context oc;
 -      char *dotdot;
 +      char *mark;
        struct object *object;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        int local_flags;
        const char *arg = arg_;
        int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
 -      unsigned get_sha1_flags = 0;
 +      unsigned get_sha1_flags = GET_SHA1_RECORD_PATH;
  
        flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
  
 -      dotdot = strstr(arg, "..");
 -      if (dotdot) {
 -              unsigned char from_sha1[20];
 -              const char *next = dotdot + 2;
 -              const char *this = arg;
 -              int symmetric = *next == '.';
 -              unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
 -              static const char head_by_default[] = "HEAD";
 -              unsigned int a_flags;
 -
 -              *dotdot = 0;
 -              next += symmetric;
 -
 -              if (!*next)
 -                      next = head_by_default;
 -              if (dotdot == arg)
 -                      this = head_by_default;
 -              if (this == head_by_default && next == head_by_default &&
 -                  !symmetric) {
 -                      /*
 -                       * Just ".."?  That is not a range but the
 -                       * pathspec for the parent directory.
 -                       */
 -                      if (!cant_be_filename) {
 -                              *dotdot = '.';
 -                              return -1;
 -                      }
 -              }
 -              if (!get_sha1_committish(this, from_sha1) &&
 -                  !get_sha1_committish(next, sha1)) {
 -                      struct object *a_obj, *b_obj;
 -
 -                      if (!cant_be_filename) {
 -                              *dotdot = '.';
 -                              verify_non_filename(revs->prefix, arg);
 -                      }
 -
 -                      a_obj = parse_object(from_sha1);
 -                      b_obj = parse_object(sha1);
 -                      if (!a_obj || !b_obj) {
 -                      missing:
 -                              if (revs->ignore_missing)
 -                                      return 0;
 -                              die(symmetric
 -                                  ? "Invalid symmetric difference expression %s"
 -                                  : "Invalid revision range %s", arg);
 -                      }
 -
 -                      if (!symmetric) {
 -                              /* just A..B */
 -                              a_flags = flags_exclude;
 -                      } else {
 -                              /* A...B -- find merge bases between the two */
 -                              struct commit *a, *b;
 -                              struct commit_list *exclude;
 -
 -                              a = (a_obj->type == OBJ_COMMIT
 -                                   ? (struct commit *)a_obj
 -                                   : lookup_commit_reference(a_obj->oid.hash));
 -                              b = (b_obj->type == OBJ_COMMIT
 -                                   ? (struct commit *)b_obj
 -                                   : lookup_commit_reference(b_obj->oid.hash));
 -                              if (!a || !b)
 -                                      goto missing;
 -                              exclude = get_merge_bases(a, b);
 -                              add_rev_cmdline_list(revs, exclude,
 -                                                   REV_CMD_MERGE_BASE,
 -                                                   flags_exclude);
 -                              add_pending_commit_list(revs, exclude,
 -                                                      flags_exclude);
 -                              free_commit_list(exclude);
 -
 -                              a_flags = flags | SYMMETRIC_LEFT;
 -                      }
 -
 -                      a_obj->flags |= a_flags;
 -                      b_obj->flags |= flags;
 -                      add_rev_cmdline(revs, a_obj, this,
 -                                      REV_CMD_LEFT, a_flags);
 -                      add_rev_cmdline(revs, b_obj, next,
 -                                      REV_CMD_RIGHT, flags);
 -                      add_pending_object(revs, a_obj, this);
 -                      add_pending_object(revs, b_obj, next);
 -                      return 0;
 -              }
 -              *dotdot = '.';
 +      if (!cant_be_filename && !strcmp(arg, "..")) {
 +              /*
 +               * Just ".."?  That is not a range but the
 +               * pathspec for the parent directory.
 +               */
 +              return -1;
        }
  
 -      dotdot = strstr(arg, "^@");
 -      if (dotdot && !dotdot[2]) {
 -              *dotdot = 0;
 +      if (!handle_dotdot(arg, revs, flags, revarg_opt))
 +              return 0;
 +
 +      mark = strstr(arg, "^@");
 +      if (mark && !mark[2]) {
 +              *mark = 0;
                if (add_parents_only(revs, arg, flags, 0))
                        return 0;
 -              *dotdot = '^';
 +              *mark = '^';
        }
 -      dotdot = strstr(arg, "^!");
 -      if (dotdot && !dotdot[2]) {
 -              *dotdot = 0;
 +      mark = strstr(arg, "^!");
 +      if (mark && !mark[2]) {
 +              *mark = 0;
                if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0))
 -                      *dotdot = '^';
 +                      *mark = '^';
        }
 -      dotdot = strstr(arg, "^-");
 -      if (dotdot) {
 +      mark = strstr(arg, "^-");
 +      if (mark) {
                int exclude_parent = 1;
  
 -              if (dotdot[2]) {
 +              if (mark[2]) {
                        char *end;
 -                      exclude_parent = strtoul(dotdot + 2, &end, 10);
 +                      exclude_parent = strtoul(mark + 2, &end, 10);
                        if (*end != '\0' || !exclude_parent)
                                return -1;
                }
  
 -              *dotdot = 0;
 +              *mark = 0;
                if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
 -                      *dotdot = '^';
 +                      *mark = '^';
        }
  
        local_flags = 0;
        }
  
        if (revarg_opt & REVARG_COMMITTISH)
 -              get_sha1_flags = GET_SHA1_COMMITTISH;
 +              get_sha1_flags |= GET_SHA1_COMMITTISH;
  
 -      if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
 +      if (get_sha1_with_context(arg, get_sha1_flags, oid.hash, &oc))
                return revs->ignore_missing ? 0 : -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
 -      object = get_reference(revs, arg, sha1, flags ^ local_flags);
 +      object = get_reference(revs, arg, &oid, flags ^ local_flags);
        add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
 -      add_pending_object_with_mode(revs, object, arg, oc.mode);
 +      add_pending_object_with_path(revs, object, arg, oc.mode, oc.path);
 +      free(oc.path);
        return 0;
  }
  
@@@ -2026,6 -1991,7 +2026,7 @@@ static int handle_revision_opt(struct r
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
                revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_ERE;
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
+               revs->grep_filter.ignore_case = 1;
                revs->grep_filter.regflags |= REG_ICASE;
                DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
        } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
@@@ -2322,12 -2288,12 +2323,12 @@@ int setup_revisions(int argc, const cha
        if (revs->show_merge)
                prepare_show_merge(revs);
        if (revs->def && !revs->pending.nr && !got_rev_arg) {
 -              unsigned char sha1[20];
 +              struct object_id oid;
                struct object *object;
                struct object_context oc;
 -              if (get_sha1_with_context(revs->def, 0, sha1, &oc))
 +              if (get_sha1_with_context(revs->def, 0, oid.hash, &oc))
                        diagnose_missing_default(revs->def);
 -              object = get_reference(revs, revs->def, sha1, 0);
 +              object = get_reference(revs, revs->def, &oid, 0);
                add_pending_object_with_mode(revs, object, revs->def, oc.mode);
        }
  
diff --combined t/perf/README
index de2fe1569603eac1316d7c5a7224e2617f0cb996,0b6a8d29061066c92f438aad0df958ba4d194717..21321a0f361203aacc78516ae25f21f35107da02
@@@ -60,7 -60,22 +60,22 @@@ You can set the following variables (al
  
      GIT_PERF_MAKE_OPTS
        Options to use when automatically building a git tree for
-       performance testing.  E.g., -j6 would be useful.
+       performance testing. E.g., -j6 would be useful. Passed
+       directly to make as "make $GIT_PERF_MAKE_OPTS".
+     GIT_PERF_MAKE_COMMAND
+       An arbitrary command that'll be run in place of the make
+       command, if set the GIT_PERF_MAKE_OPTS variable is
+       ignored. Useful in cases where source tree changes might
+       require issuing a different make command to different
+       revisions.
+       This can be (ab)used to monkeypatch or otherwise change the
+       tree about to be built. Note that the build directory can be
+       re-used for subsequent runs so the make command might get
+       executed multiple times on the same tree, but don't count on
+       any of that, that's an implementation detail that might change
+       in the future.
  
      GIT_PERF_REPO
      GIT_PERF_LARGE_REPO
@@@ -106,7 -121,6 +121,7 @@@ sources perf-lib.sh
  
  After that you will want to use some of the following:
  
 +      test_perf_fresh_repo    # sets up an empty repository
        test_perf_default_repo  # sets up a "normal" repository
        test_perf_large_repo    # sets up a "large" repository
  
diff --combined t/t4202-log.sh
index e4441957e2b406c16121578f6d1344488509cafe,dbed3efeeeeee6ecb4c6cbe7848c18bd684bb2e1..66606e7508559e343d1b6959397dec77ddf5c6ac
@@@ -231,14 -231,47 +231,47 @@@ secon
  initial
  EOF
  test_expect_success 'log --invert-grep --grep' '
-       git log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual &&
-       test_cmp expect actual
+       # Fixed
+       git -c grep.patternType=fixed log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual &&
+       test_cmp expect actual &&
+       # POSIX basic
+       git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual &&
+       test_cmp expect actual &&
+       # POSIX extended
+       git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual &&
+       test_cmp expect actual &&
+       # PCRE
+       if test_have_prereq PCRE
+       then
+               git -c grep.patternType=perl log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual &&
+               test_cmp expect actual
+       fi
  '
  
  test_expect_success 'log --invert-grep --grep -i' '
        echo initial >expect &&
-       git log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual &&
-       test_cmp expect actual
+       # Fixed
+       git -c grep.patternType=fixed log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual &&
+       test_cmp expect actual &&
+       # POSIX basic
+       git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual &&
+       test_cmp expect actual &&
+       # POSIX extended
+       git -c grep.patternType=extended log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual &&
+       test_cmp expect actual &&
+       # PCRE
+       if test_have_prereq PCRE
+       then
+               git -c grep.patternType=perl log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual &&
+               test_cmp expect actual
+       fi
  '
  
  test_expect_success 'log --grep option parsing' '
@@@ -256,13 -289,53 +289,53 @@@ test_expect_success 'log -i --grep' 
  
  test_expect_success 'log --grep -i' '
        echo Second >expect &&
+       # Fixed
        git log -1 --pretty="tformat:%s" --grep=sec -i >actual &&
-       test_cmp expect actual
+       test_cmp expect actual &&
+       # POSIX basic
+       git -c grep.patternType=basic log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual &&
+       test_cmp expect actual &&
+       # POSIX extended
+       git -c grep.patternType=extended log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual &&
+       test_cmp expect actual &&
+       # PCRE
+       if test_have_prereq PCRE
+       then
+               git -c grep.patternType=perl log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual &&
+               test_cmp expect actual
+       fi
  '
  
  test_expect_success 'log -F -E --grep=<ere> uses ere' '
        echo second >expect &&
-       git log -1 --pretty="tformat:%s" -F -E --grep=s.c.nd >actual &&
+       # basic would need \(s\) to do the same
+       git log -1 --pretty="tformat:%s" -F -E --grep="(s).c.nd" >actual &&
+       test_cmp expect actual
+ '
+ test_expect_success PCRE 'log -F -E --perl-regexp --grep=<pcre> uses PCRE' '
+       test_when_finished "rm -rf num_commits" &&
+       git init num_commits &&
+       (
+               cd num_commits &&
+               test_commit 1d &&
+               test_commit 2e
+       ) &&
+       # In PCRE \d in [\d] is like saying "0-9", and matches the 2
+       # in 2e...
+       echo 2e >expect &&
+       git -C num_commits log -1 --pretty="tformat:%s" -F -E --perl-regexp --grep="[\d]" >actual &&
+       test_cmp expect actual &&
+       # ...in POSIX basic and extended it is the same as [d],
+       # i.e. "d", which matches 1d, but does not match 2e.
+       echo 1d >expect &&
+       git -C num_commits log -1 --pretty="tformat:%s" -F -E --grep="[\d]" >actual &&
        test_cmp expect actual
  '
  
@@@ -280,6 -353,81 +353,81 @@@ test_expect_success 'log with grep.patt
        test_cmp expect actual
  '
  
+ test_expect_success 'log with various grep.patternType configurations & command-lines' '
+       git init pattern-type &&
+       (
+               cd pattern-type &&
+               test_commit 1 file A &&
+               # The tagname is overridden here because creating a
+               # tag called "(1|2)" as test_commit would otherwise
+               # implicitly do would fail on e.g. MINGW.
+               test_commit "(1|2)" file B 2 &&
+               echo "(1|2)" >expect.fixed &&
+               cp expect.fixed expect.basic &&
+               cp expect.fixed expect.extended &&
+               cp expect.fixed expect.perl &&
+               # A strcmp-like match with fixed.
+               git -c grep.patternType=fixed log --pretty=tformat:%s \
+                       --grep="(1|2)" >actual.fixed &&
+               # POSIX basic matches (, | and ) literally.
+               git -c grep.patternType=basic log --pretty=tformat:%s \
+                       --grep="(.|.)" >actual.basic &&
+               # POSIX extended needs to have | escaped to match it
+               # literally, whereas under basic this is the same as
+               # (|2), i.e. it would also match "1". This test checks
+               # for extended by asserting that it is not matching
+               # what basic would match.
+               git -c grep.patternType=extended log --pretty=tformat:%s \
+                       --grep="\|2" >actual.extended &&
+               if test_have_prereq PCRE
+               then
+                       # Only PCRE would match [\d]\| with only
+                       # "(1|2)" due to [\d]. POSIX basic would match
+                       # both it and "1" since similarly to the
+                       # extended match above it is the same as
+                       # \([\d]\|\). POSIX extended would
+                       # match neither.
+                       git -c grep.patternType=perl log --pretty=tformat:%s \
+                               --grep="[\d]\|" >actual.perl &&
+                       test_cmp expect.perl actual.perl
+               fi &&
+               test_cmp expect.fixed actual.fixed &&
+               test_cmp expect.basic actual.basic &&
+               test_cmp expect.extended actual.extended &&
+               git log --pretty=tformat:%s -F \
+                       --grep="(1|2)" >actual.fixed.short-arg &&
+               git log --pretty=tformat:%s -E \
+                       --grep="\|2" >actual.extended.short-arg &&
+               test_cmp expect.fixed actual.fixed.short-arg &&
+               test_cmp expect.extended actual.extended.short-arg &&
+               git log --pretty=tformat:%s --fixed-strings \
+                       --grep="(1|2)" >actual.fixed.long-arg &&
+               git log --pretty=tformat:%s --basic-regexp \
+                       --grep="(.|.)" >actual.basic.long-arg &&
+               git log --pretty=tformat:%s --extended-regexp \
+                       --grep="\|2" >actual.extended.long-arg &&
+               if test_have_prereq PCRE
+               then
+                       git log --pretty=tformat:%s --perl-regexp \
+                               --grep="[\d]\|" >actual.perl.long-arg &&
+                       test_cmp expect.perl actual.perl.long-arg
+               else
+                       test_must_fail git log --perl-regexp \
+                               --grep="[\d]\|"
+               fi &&
+               test_cmp expect.fixed actual.fixed.long-arg &&
+               test_cmp expect.basic actual.basic.long-arg &&
+               test_cmp expect.extended actual.extended.long-arg
+       )
+ '
  cat > expect <<EOF
  * Second
  * sixth
@@@ -399,7 -547,7 +547,7 @@@ cat > expect <<\EO
  | |
  | |     Merge branch 'side'
  | |
 -| * commit side
 +| * commit tags/side-2
  | | Author: A U Thor <author@example.com>
  | |
  | |     side-2
@@@ -577,18 -725,6 +725,18 @@@ test_expect_success 'log.decorate confi
  
  '
  
 +test_expect_success 'log.decorate config parsing' '
 +      git log --oneline --decorate=full >expect.full &&
 +      git log --oneline --decorate=short >expect.short &&
 +
 +      test_config log.decorate full &&
 +      test_config log.mailmap true &&
 +      git log --oneline >actual &&
 +      test_cmp expect.full actual &&
 +      git log --oneline --decorate=short >actual &&
 +      test_cmp expect.short actual
 +'
 +
  test_expect_success TTY 'log output on a TTY' '
        git log --oneline --decorate >expect.short &&
  
@@@ -1392,13 -1528,4 +1540,13 @@@ test_expect_success 'log --source paint
        test_cmp expect actual
  '
  
 +test_expect_success 'log --source paints symmetric ranges' '
 +      cat >expect <<-\EOF &&
 +      09e12a9 source-b three
 +      8e393e1 source-a two
 +      EOF
 +      git log --oneline --source source-a...source-b >actual &&
 +      test_cmp expect actual
 +'
 +
  test_done
diff --combined t/test-lib.sh
index ec2571f0185b7c6aad15e3b01e67f6b79b643928,ab92c0ebaa990bb7b7c561d1a9a66c505bf67d11..4936725c675f9dd3290e40722d8e40d63f3c874c
@@@ -745,36 -745,26 +745,36 @@@ test_done () 
        fi
        case "$test_failure" in
        0)
 -              # Maybe print SKIP message
 -              if test -n "$skip_all" && test $test_count -gt 0
 -              then
 -                      error "Can't use skip_all after running some tests"
 -              fi
 -              test -z "$skip_all" || skip_all=" # SKIP $skip_all"
 -
                if test $test_external_has_tap -eq 0
                then
                        if test $test_remaining -gt 0
                        then
                                say_color pass "# passed all $msg"
                        fi
 -                      say "1..$test_count$skip_all"
 +
 +                      # Maybe print SKIP message
 +                      test -z "$skip_all" || skip_all="# SKIP $skip_all"
 +                      case "$test_count" in
 +                      0)
 +                              say "1..$test_count${skip_all:+ $skip_all}"
 +                              ;;
 +                      *)
 +                              test -z "$skip_all" ||
 +                              say_color warn "$skip_all"
 +                              say "1..$test_count"
 +                              ;;
 +                      esac
                fi
  
 -              test -d "$remove_trash" &&
 -              cd "$(dirname "$remove_trash")" &&
 -              rm -rf "$(basename "$remove_trash")"
 +              if test -z "$debug"
 +              then
 +                      test -d "$TRASH_DIRECTORY" ||
 +                      error "Tests passed but trash directory already removed before test cleanup; aborting"
  
 +                      cd "$TRASH_DIRECTORY/.." &&
 +                      rm -fr "$TRASH_DIRECTORY" ||
 +                      error "Tests passed but test cleanup failed; aborting"
 +              fi
                test_at_end_hook_
  
                exit 0 ;;
@@@ -929,6 -919,7 +929,6 @@@ case "$TRASH_DIRECTORY" i
  /*) ;; # absolute path is good
   *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;;
  esac
 -test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY
  rm -fr "$TRASH_DIRECTORY" || {
        GIT_EXIT_OK=t
        echo >&5 "FATAL: Cannot prepare test area"
@@@ -1018,8 -1009,9 +1018,9 @@@ esa
  
  ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
  test -z "$NO_PERL" && test_set_prereq PERL
+ test -z "$NO_PTHREADS" && test_set_prereq PTHREADS
  test -z "$NO_PYTHON" && test_set_prereq PYTHON
- test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
+ test -n "$USE_LIBPCRE1" && test_set_prereq PCRE
  test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
  
  # Can we rely on git's output in the C locale?
@@@ -1173,6 -1165,3 +1174,6 @@@ build_option () 
  test_lazy_prereq LONG_IS_64BIT '
        test 8 -le "$(build_option sizeof-long)"
  '
 +
 +test_lazy_prereq TIME_IS_64BIT 'test-date is64bit'
 +test_lazy_prereq TIME_T_IS_64BIT 'test-date time_t-is64bit'