summaryrefslogtreecommitdiff
path: root/t/helper
diff options
context:
space:
mode:
Diffstat (limited to 't/helper')
-rw-r--r--t/helper/.gitignore34
-rw-r--r--t/helper/test-chmtime.c119
-rw-r--r--t/helper/test-config.c206
-rw-r--r--t/helper/test-ctype.c42
-rw-r--r--t/helper/test-date.c99
-rw-r--r--t/helper/test-delta.c78
-rw-r--r--t/helper/test-dump-cache-tree.c68
-rw-r--r--t/helper/test-dump-split-index.c36
-rw-r--r--t/helper/test-dump-untracked-cache.c64
-rw-r--r--t/helper/test-fake-ssh.c30
-rw-r--r--t/helper/test-genrandom.c33
-rw-r--r--t/helper/test-hashmap.c264
-rw-r--r--t/helper/test-index-version.c14
-rw-r--r--t/helper/test-lazy-init-name-hash.c264
-rw-r--r--t/helper/test-line-buffer.c91
-rw-r--r--t/helper/test-match-trees.c26
-rw-r--r--t/helper/test-mergesort.c52
-rw-r--r--t/helper/test-mktemp.c14
-rw-r--r--t/helper/test-parse-options.c179
-rw-r--r--t/helper/test-path-utils.c262
-rw-r--r--t/helper/test-prio-queue.c39
-rw-r--r--t/helper/test-read-cache.c13
-rw-r--r--t/helper/test-regex.c75
-rw-r--r--t/helper/test-revision-walking.c68
-rw-r--r--t/helper/test-run-command.c87
-rw-r--r--t/helper/test-scrap-cache-tree.c18
-rw-r--r--t/helper/test-sha1-array.c35
-rw-r--r--t/helper/test-sha1.c56
-rwxr-xr-xt/helper/test-sha1.sh83
-rw-r--r--t/helper/test-sigchain.c22
-rw-r--r--t/helper/test-string-list.c128
-rw-r--r--t/helper/test-submodule-config.c76
-rw-r--r--t/helper/test-subprocess.c19
-rw-r--r--t/helper/test-svn-fe.c52
-rw-r--r--t/helper/test-urlmatch-normalization.c50
-rw-r--r--t/helper/test-wildmatch.c21
36 files changed, 2817 insertions, 0 deletions
diff --git a/t/helper/.gitignore b/t/helper/.gitignore
new file mode 100644
index 0000000000..758ed2e8fa
--- /dev/null
+++ b/t/helper/.gitignore
@@ -0,0 +1,34 @@
+/test-chmtime
+/test-ctype
+/test-config
+/test-date
+/test-delta
+/test-dump-cache-tree
+/test-dump-split-index
+/test-dump-untracked-cache
+/test-fake-ssh
+/test-scrap-cache-tree
+/test-genrandom
+/test-hashmap
+/test-index-version
+/test-lazy-init-name-hash
+/test-line-buffer
+/test-match-trees
+/test-mergesort
+/test-mktemp
+/test-parse-options
+/test-path-utils
+/test-prio-queue
+/test-read-cache
+/test-regex
+/test-revision-walking
+/test-run-command
+/test-sha1
+/test-sha1-array
+/test-sigchain
+/test-string-list
+/test-submodule-config
+/test-subprocess
+/test-svn-fe
+/test-urlmatch-normalization
+/test-wildmatch
diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c
new file mode 100644
index 0000000000..e760256406
--- /dev/null
+++ b/t/helper/test-chmtime.c
@@ -0,0 +1,119 @@
+/*
+ * This program can either change modification time of the given
+ * file(s) or just print it. The program does not change atime or
+ * ctime (their values are explicitly preserved).
+ *
+ * The mtime can be changed to an absolute value:
+ *
+ * test-chmtime =<seconds> file...
+ *
+ * Relative to the current time as returned by time(3):
+ *
+ * test-chmtime =+<seconds> (or =-<seconds>) file...
+ *
+ * Or relative to the current mtime of the file:
+ *
+ * test-chmtime <seconds> file...
+ * test-chmtime +<seconds> (or -<seconds>) file...
+ *
+ * Examples:
+ *
+ * To just print the mtime use --verbose and set the file mtime offset to 0:
+ *
+ * test-chmtime -v +0 file
+ *
+ * To set the mtime to current time:
+ *
+ * test-chmtime =+0 file
+ *
+ */
+#include "git-compat-util.h"
+#include <utime.h>
+
+static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-)<seconds> <file>...";
+
+static int timespec_arg(const char *arg, long int *set_time, int *set_eq)
+{
+ char *test;
+ const char *timespec = arg;
+ *set_eq = (*timespec == '=') ? 1 : 0;
+ if (*set_eq) {
+ timespec++;
+ if (*timespec == '+') {
+ *set_eq = 2; /* relative "in the future" */
+ timespec++;
+ }
+ }
+ *set_time = strtol(timespec, &test, 10);
+ if (*test) {
+ fprintf(stderr, "Not a base-10 integer: %s\n", arg + 1);
+ return 0;
+ }
+ if ((*set_eq && *set_time < 0) || *set_eq == 2) {
+ time_t now = time(NULL);
+ *set_time += now;
+ }
+ return 1;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ static int verbose;
+
+ int i = 1;
+ /* no mtime change by default */
+ int set_eq = 0;
+ long int set_time = 0;
+
+ if (argc < 3)
+ goto usage;
+
+ if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) {
+ verbose = 1;
+ ++i;
+ }
+ if (timespec_arg(argv[i], &set_time, &set_eq))
+ ++i;
+ else
+ goto usage;
+
+ for (; i < argc; i++) {
+ struct stat sb;
+ struct utimbuf utb;
+
+ if (stat(argv[i], &sb) < 0) {
+ fprintf(stderr, "Failed to stat %s: %s\n",
+ argv[i], strerror(errno));
+ return 1;
+ }
+
+#ifdef GIT_WINDOWS_NATIVE
+ if (!(sb.st_mode & S_IWUSR) &&
+ chmod(argv[i], sb.st_mode | S_IWUSR)) {
+ fprintf(stderr, "Could not make user-writable %s: %s",
+ argv[i], strerror(errno));
+ return 1;
+ }
+#endif
+
+ utb.actime = sb.st_atime;
+ utb.modtime = set_eq ? set_time : sb.st_mtime + set_time;
+
+ if (verbose) {
+ uintmax_t mtime = utb.modtime < 0 ? 0: utb.modtime;
+ printf("%"PRIuMAX"\t%s\n", mtime, argv[i]);
+ }
+
+ if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) {
+ fprintf(stderr, "Failed to modify time on %s: %s\n",
+ argv[i], strerror(errno));
+ return 1;
+ }
+ }
+
+ return 0;
+
+usage:
+ fprintf(stderr, "usage: %s %s\n", argv[0], usage_str);
+ return 1;
+}
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
new file mode 100644
index 0000000000..8e3ed6a76c
--- /dev/null
+++ b/t/helper/test-config.c
@@ -0,0 +1,206 @@
+#include "cache.h"
+#include "string-list.h"
+
+/*
+ * This program exposes the C API of the configuration mechanism
+ * as a set of simple commands in order to facilitate testing.
+ *
+ * Reads stdin and prints result of command to stdout:
+ *
+ * get_value -> prints the value with highest priority for the entered key
+ *
+ * get_value_multi -> prints all values for the entered key in increasing order
+ * of priority
+ *
+ * get_int -> print integer value for the entered key or die
+ *
+ * get_bool -> print bool value for the entered key or die
+ *
+ * get_string -> print string value for the entered key or die
+ *
+ * configset_get_value -> returns value with the highest priority for the entered key
+ * from a config_set constructed from files entered as arguments.
+ *
+ * configset_get_value_multi -> returns value_list for the entered key sorted in
+ * ascending order of priority from a config_set
+ * constructed from files entered as arguments.
+ *
+ * iterate -> iterate over all values using git_config(), and print some
+ * data for each
+ *
+ * Examples:
+ *
+ * To print the value with highest priority for key "foo.bAr Baz.rock":
+ * test-config get_value "foo.bAr Baz.rock"
+ *
+ */
+
+static const char *scope_name(enum config_scope scope)
+{
+ switch (scope) {
+ case CONFIG_SCOPE_SYSTEM:
+ return "system";
+ case CONFIG_SCOPE_GLOBAL:
+ return "global";
+ case CONFIG_SCOPE_REPO:
+ return "repo";
+ case CONFIG_SCOPE_CMDLINE:
+ return "cmdline";
+ default:
+ return "unknown";
+ }
+}
+static int iterate_cb(const char *var, const char *value, void *data)
+{
+ static int nr;
+
+ if (nr++)
+ putchar('\n');
+
+ printf("key=%s\n", var);
+ printf("value=%s\n", value ? value : "(null)");
+ printf("origin=%s\n", current_config_origin_type());
+ printf("name=%s\n", current_config_name());
+ printf("scope=%s\n", scope_name(current_config_scope()));
+
+ return 0;
+}
+
+static int early_config_cb(const char *var, const char *value, void *vdata)
+{
+ const char *key = vdata;
+
+ if (!strcmp(key, var))
+ printf("%s\n", value);
+
+ return 0;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ int i, val;
+ const char *v;
+ const struct string_list *strptr;
+ struct config_set cs;
+
+ if (argc == 3 && !strcmp(argv[1], "read_early_config")) {
+ read_early_config(early_config_cb, (void *)argv[2]);
+ return 0;
+ }
+
+ setup_git_directory();
+
+ git_configset_init(&cs);
+
+ if (argc < 2) {
+ fprintf(stderr, "Please, provide a command name on the command-line\n");
+ goto exit1;
+ } else if (argc == 3 && !strcmp(argv[1], "get_value")) {
+ if (!git_config_get_value(argv[2], &v)) {
+ if (!v)
+ printf("(NULL)\n");
+ else
+ printf("%s\n", v);
+ goto exit0;
+ } else {
+ printf("Value not found for \"%s\"\n", argv[2]);
+ goto exit1;
+ }
+ } else if (argc == 3 && !strcmp(argv[1], "get_value_multi")) {
+ strptr = git_config_get_value_multi(argv[2]);
+ if (strptr) {
+ for (i = 0; i < strptr->nr; i++) {
+ v = strptr->items[i].string;
+ if (!v)
+ printf("(NULL)\n");
+ else
+ printf("%s\n", v);
+ }
+ goto exit0;
+ } else {
+ printf("Value not found for \"%s\"\n", argv[2]);
+ goto exit1;
+ }
+ } else if (argc == 3 && !strcmp(argv[1], "get_int")) {
+ if (!git_config_get_int(argv[2], &val)) {
+ printf("%d\n", val);
+ goto exit0;
+ } else {
+ printf("Value not found for \"%s\"\n", argv[2]);
+ goto exit1;
+ }
+ } else if (argc == 3 && !strcmp(argv[1], "get_bool")) {
+ if (!git_config_get_bool(argv[2], &val)) {
+ printf("%d\n", val);
+ goto exit0;
+ } else {
+ printf("Value not found for \"%s\"\n", argv[2]);
+ goto exit1;
+ }
+ } else if (argc == 3 && !strcmp(argv[1], "get_string")) {
+ if (!git_config_get_string_const(argv[2], &v)) {
+ printf("%s\n", v);
+ goto exit0;
+ } else {
+ printf("Value not found for \"%s\"\n", argv[2]);
+ goto exit1;
+ }
+ } else if (!strcmp(argv[1], "configset_get_value")) {
+ for (i = 3; i < argc; i++) {
+ int err;
+ if ((err = git_configset_add_file(&cs, argv[i]))) {
+ fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]);
+ goto exit2;
+ }
+ }
+ if (!git_configset_get_value(&cs, argv[2], &v)) {
+ if (!v)
+ printf("(NULL)\n");
+ else
+ printf("%s\n", v);
+ goto exit0;
+ } else {
+ printf("Value not found for \"%s\"\n", argv[2]);
+ goto exit1;
+ }
+ } else if (!strcmp(argv[1], "configset_get_value_multi")) {
+ for (i = 3; i < argc; i++) {
+ int err;
+ if ((err = git_configset_add_file(&cs, argv[i]))) {
+ fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]);
+ goto exit2;
+ }
+ }
+ strptr = git_configset_get_value_multi(&cs, argv[2]);
+ if (strptr) {
+ for (i = 0; i < strptr->nr; i++) {
+ v = strptr->items[i].string;
+ if (!v)
+ printf("(NULL)\n");
+ else
+ printf("%s\n", v);
+ }
+ goto exit0;
+ } else {
+ printf("Value not found for \"%s\"\n", argv[2]);
+ goto exit1;
+ }
+ } else if (!strcmp(argv[1], "iterate")) {
+ git_config(iterate_cb, NULL);
+ goto exit0;
+ }
+
+ die("%s: Please check the syntax and the function name", argv[0]);
+
+exit0:
+ git_configset_clear(&cs);
+ return 0;
+
+exit1:
+ git_configset_clear(&cs);
+ return 1;
+
+exit2:
+ git_configset_clear(&cs);
+ return 2;
+}
diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c
new file mode 100644
index 0000000000..bb72c47df5
--- /dev/null
+++ b/t/helper/test-ctype.c
@@ -0,0 +1,42 @@
+#include "cache.h"
+
+static int rc;
+
+static void report_error(const char *class, int ch)
+{
+ printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch);
+ rc = 1;
+}
+
+static int is_in(const char *s, int ch)
+{
+ /* We can't find NUL using strchr. It's classless anyway. */
+ if (ch == '\0')
+ return 0;
+ return !!strchr(s, ch);
+}
+
+#define TEST_CLASS(t,s) { \
+ int i; \
+ for (i = 0; i < 256; i++) { \
+ if (is_in(s, i) != t(i)) \
+ report_error(#t, i); \
+ } \
+}
+
+#define DIGIT "0123456789"
+#define LOWER "abcdefghijklmnopqrstuvwxyz"
+#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+int cmd_main(int argc, const char **argv)
+{
+ TEST_CLASS(isdigit, DIGIT);
+ TEST_CLASS(isspace, " \n\r\t");
+ TEST_CLASS(isalpha, LOWER UPPER);
+ TEST_CLASS(isalnum, LOWER UPPER DIGIT);
+ TEST_CLASS(is_glob_special, "*?[\\");
+ TEST_CLASS(is_regex_special, "$()*+.?[\\^{|");
+ TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+
+ return rc;
+}
diff --git a/t/helper/test-date.c b/t/helper/test-date.c
new file mode 100644
index 0000000000..506054bcd5
--- /dev/null
+++ b/t/helper/test-date.c
@@ -0,0 +1,99 @@
+#include "cache.h"
+
+static const char *usage_msg = "\n"
+" test-date relative [time_t]...\n"
+" test-date show:<format> [time_t]...\n"
+" test-date parse [date]...\n"
+" test-date approxidate [date]...\n";
+
+static void show_relative_dates(const char **argv, struct timeval *now)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ for (; *argv; argv++) {
+ time_t t = atoi(*argv);
+ show_date_relative(t, 0, now, &buf);
+ printf("%s -> %s\n", *argv, buf.buf);
+ }
+ strbuf_release(&buf);
+}
+
+static void show_dates(const char **argv, const char *format)
+{
+ struct date_mode mode;
+
+ parse_date_format(format, &mode);
+ for (; *argv; argv++) {
+ char *arg;
+ time_t t;
+ int tz;
+
+ /*
+ * Do not use our normal timestamp parsing here, as the point
+ * is to test the formatting code in isolation.
+ */
+ t = strtol(*argv, &arg, 10);
+ while (*arg == ' ')
+ arg++;
+ tz = atoi(arg);
+
+ printf("%s -> %s\n", *argv, show_date(t, tz, &mode));
+ }
+}
+
+static void parse_dates(const char **argv, struct timeval *now)
+{
+ struct strbuf result = STRBUF_INIT;
+
+ for (; *argv; argv++) {
+ unsigned long t;
+ int tz;
+
+ strbuf_reset(&result);
+ parse_date(*argv, &result);
+ if (sscanf(result.buf, "%lu %d", &t, &tz) == 2)
+ printf("%s -> %s\n",
+ *argv, show_date(t, tz, DATE_MODE(ISO8601)));
+ else
+ printf("%s -> bad\n", *argv);
+ }
+ strbuf_release(&result);
+}
+
+static void parse_approxidate(const char **argv, struct timeval *now)
+{
+ for (; *argv; argv++) {
+ time_t t;
+ t = approxidate_relative(*argv, now);
+ printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601)));
+ }
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ struct timeval now;
+ const char *x;
+
+ x = getenv("TEST_DATE_NOW");
+ if (x) {
+ now.tv_sec = atoi(x);
+ now.tv_usec = 0;
+ }
+ else
+ gettimeofday(&now, NULL);
+
+ argv++;
+ if (!*argv)
+ usage(usage_msg);
+ if (!strcmp(*argv, "relative"))
+ show_relative_dates(argv+1, &now);
+ else if (skip_prefix(*argv, "show:", &x))
+ show_dates(argv+1, x);
+ else if (!strcmp(*argv, "parse"))
+ parse_dates(argv+1, &now);
+ else if (!strcmp(*argv, "approxidate"))
+ parse_approxidate(argv+1, &now);
+ else
+ usage(usage_msg);
+ return 0;
+}
diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c
new file mode 100644
index 0000000000..59937dc1be
--- /dev/null
+++ b/t/helper/test-delta.c
@@ -0,0 +1,78 @@
+/*
+ * test-delta.c: test code to exercise diff-delta.c and patch-delta.c
+ *
+ * (C) 2005 Nicolas Pitre <nico@fluxnic.net>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "git-compat-util.h"
+#include "delta.h"
+#include "cache.h"
+
+static const char usage_str[] =
+ "test-delta (-d|-p) <from_file> <data_file> <out_file>";
+
+int cmd_main(int argc, const char **argv)
+{
+ int fd;
+ struct stat st;
+ void *from_buf, *data_buf, *out_buf;
+ unsigned long from_size, data_size, out_size;
+
+ if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) {
+ fprintf(stderr, "usage: %s\n", usage_str);
+ return 1;
+ }
+
+ fd = open(argv[2], O_RDONLY);
+ if (fd < 0 || fstat(fd, &st)) {
+ perror(argv[2]);
+ return 1;
+ }
+ from_size = st.st_size;
+ from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (from_buf == MAP_FAILED) {
+ perror(argv[2]);
+ close(fd);
+ return 1;
+ }
+ close(fd);
+
+ fd = open(argv[3], O_RDONLY);
+ if (fd < 0 || fstat(fd, &st)) {
+ perror(argv[3]);
+ return 1;
+ }
+ data_size = st.st_size;
+ data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (data_buf == MAP_FAILED) {
+ perror(argv[3]);
+ close(fd);
+ return 1;
+ }
+ close(fd);
+
+ if (argv[1][1] == 'd')
+ out_buf = diff_delta(from_buf, from_size,
+ data_buf, data_size,
+ &out_size, 0);
+ else
+ out_buf = patch_delta(from_buf, from_size,
+ data_buf, data_size,
+ &out_size);
+ if (!out_buf) {
+ fprintf(stderr, "delta operation failed (returned NULL)\n");
+ return 1;
+ }
+
+ fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (fd < 0 || write_in_full(fd, out_buf, out_size) != out_size) {
+ perror(argv[4]);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c
new file mode 100644
index 0000000000..7af116d49e
--- /dev/null
+++ b/t/helper/test-dump-cache-tree.c
@@ -0,0 +1,68 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+
+static void dump_one(struct cache_tree *it, const char *pfx, const char *x)
+{
+ if (it->entry_count < 0)
+ printf("%-40s %s%s (%d subtrees)\n",
+ "invalid", x, pfx, it->subtree_nr);
+ else
+ printf("%s %s%s (%d entries, %d subtrees)\n",
+ sha1_to_hex(it->sha1), x, pfx,
+ it->entry_count, it->subtree_nr);
+}
+
+static int dump_cache_tree(struct cache_tree *it,
+ struct cache_tree *ref,
+ const char *pfx)
+{
+ int i;
+ int errs = 0;
+
+ if (!it || !ref)
+ /* missing in either */
+ return 0;
+
+ if (it->entry_count < 0) {
+ /* invalid */
+ dump_one(it, pfx, "");
+ dump_one(ref, pfx, "#(ref) ");
+ }
+ else {
+ dump_one(it, pfx, "");
+ if (hashcmp(it->sha1, ref->sha1) ||
+ ref->entry_count != it->entry_count ||
+ ref->subtree_nr != it->subtree_nr) {
+ /* claims to be valid but is lying */
+ dump_one(ref, pfx, "#(ref) ");
+ errs = 1;
+ }
+ }
+
+ for (i = 0; i < it->subtree_nr; i++) {
+ char path[PATH_MAX];
+ struct cache_tree_sub *down = it->down[i];
+ struct cache_tree_sub *rdwn;
+
+ rdwn = cache_tree_sub(ref, down->name);
+ xsnprintf(path, sizeof(path), "%s%.*s/", pfx, down->namelen, down->name);
+ if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
+ errs = 1;
+ }
+ return errs;
+}
+
+int cmd_main(int ac, const char **av)
+{
+ struct index_state istate;
+ struct cache_tree *another = cache_tree();
+ setup_git_directory();
+ if (read_cache() < 0)
+ die("unable to read index file");
+ istate = the_index;
+ istate.cache_tree = another;
+ cache_tree_update(&istate, WRITE_TREE_DRY_RUN);
+ return dump_cache_tree(active_cache_tree, another, "");
+}
diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c
new file mode 100644
index 0000000000..e44430b699
--- /dev/null
+++ b/t/helper/test-dump-split-index.c
@@ -0,0 +1,36 @@
+#include "cache.h"
+#include "split-index.h"
+#include "ewah/ewok.h"
+
+static void show_bit(size_t pos, void *data)
+{
+ printf(" %d", (int)pos);
+}
+
+int cmd_main(int ac, const char **av)
+{
+ struct split_index *si;
+ int i;
+
+ do_read_index(&the_index, av[1], 1);
+ printf("own %s\n", sha1_to_hex(the_index.sha1));
+ si = the_index.split_index;
+ if (!si) {
+ printf("not a split index\n");
+ return 0;
+ }
+ printf("base %s\n", sha1_to_hex(si->base_sha1));
+ for (i = 0; i < the_index.cache_nr; i++) {
+ struct cache_entry *ce = the_index.cache[i];
+ printf("%06o %s %d\t%s\n", ce->ce_mode,
+ oid_to_hex(&ce->oid), ce_stage(ce), ce->name);
+ }
+ printf("replacements:");
+ if (si->replace_bitmap)
+ ewah_each_bit(si->replace_bitmap, show_bit, NULL);
+ printf("\ndeletions:");
+ if (si->delete_bitmap)
+ ewah_each_bit(si->delete_bitmap, show_bit, NULL);
+ printf("\n");
+ return 0;
+}
diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c
new file mode 100644
index 0000000000..f752532ffb
--- /dev/null
+++ b/t/helper/test-dump-untracked-cache.c
@@ -0,0 +1,64 @@
+#include "cache.h"
+#include "dir.h"
+
+static int compare_untracked(const void *a_, const void *b_)
+{
+ const char *const *a = a_;
+ const char *const *b = b_;
+ return strcmp(*a, *b);
+}
+
+static int compare_dir(const void *a_, const void *b_)
+{
+ const struct untracked_cache_dir *const *a = a_;
+ const struct untracked_cache_dir *const *b = b_;
+ return strcmp((*a)->name, (*b)->name);
+}
+
+static void dump(struct untracked_cache_dir *ucd, struct strbuf *base)
+{
+ int i, len;
+ QSORT(ucd->untracked, ucd->untracked_nr, compare_untracked);
+ QSORT(ucd->dirs, ucd->dirs_nr, compare_dir);
+ len = base->len;
+ strbuf_addf(base, "%s/", ucd->name);
+ printf("%s %s", base->buf,
+ sha1_to_hex(ucd->exclude_sha1));
+ if (ucd->recurse)
+ fputs(" recurse", stdout);
+ if (ucd->check_only)
+ fputs(" check_only", stdout);
+ if (ucd->valid)
+ fputs(" valid", stdout);
+ printf("\n");
+ for (i = 0; i < ucd->untracked_nr; i++)
+ printf("%s\n", ucd->untracked[i]);
+ for (i = 0; i < ucd->dirs_nr; i++)
+ dump(ucd->dirs[i], base);
+ strbuf_setlen(base, len);
+}
+
+int cmd_main(int ac, const char **av)
+{
+ struct untracked_cache *uc;
+ struct strbuf base = STRBUF_INIT;
+
+ /* Hack to avoid modifying the untracked cache when we read it */
+ ignore_untracked_cache_config = 1;
+
+ setup_git_directory();
+ if (read_cache() < 0)
+ die("unable to read index file");
+ uc = the_index.untracked;
+ if (!uc) {
+ printf("no untracked cache\n");
+ return 0;
+ }
+ printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1));
+ printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1));
+ printf("exclude_per_dir %s\n", uc->exclude_per_dir);
+ printf("flags %08x\n", uc->dir_flags);
+ if (uc->root)
+ dump(uc->root, &base);
+ return 0;
+}
diff --git a/t/helper/test-fake-ssh.c b/t/helper/test-fake-ssh.c
new file mode 100644
index 0000000000..12beee99ad
--- /dev/null
+++ b/t/helper/test-fake-ssh.c
@@ -0,0 +1,30 @@
+#include "git-compat-util.h"
+#include "run-command.h"
+#include "strbuf.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char *trash_directory = getenv("TRASH_DIRECTORY");
+ struct strbuf buf = STRBUF_INIT;
+ FILE *f;
+ int i;
+ const char *child_argv[] = { NULL, NULL };
+
+ /* First, print all parameters into $TRASH_DIRECTORY/ssh-output */
+ if (!trash_directory)
+ die("Need a TRASH_DIRECTORY!");
+ strbuf_addf(&buf, "%s/ssh-output", trash_directory);
+ f = fopen(buf.buf, "w");
+ if (!f)
+ die("Could not write to %s", buf.buf);
+ for (i = 0; i < argc; i++)
+ fprintf(f, "%s%s", i > 0 ? " " : "", i > 0 ? argv[i] : "ssh:");
+ fprintf(f, "\n");
+ fclose(f);
+
+ /* Now, evaluate the *last* parameter */
+ if (argc < 2)
+ return 0;
+ child_argv[0] = argv[argc - 1];
+ return run_command_v_opt(child_argv, RUN_USING_SHELL);
+}
diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c
new file mode 100644
index 0000000000..8d11d22d98
--- /dev/null
+++ b/t/helper/test-genrandom.c
@@ -0,0 +1,33 @@
+/*
+ * Simple random data generator used to create reproducible test files.
+ * This is inspired from POSIX.1-2001 implementation example for rand().
+ * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2.
+ */
+
+#include "git-compat-util.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ unsigned long count, next = 0;
+ unsigned char *c;
+
+ if (argc < 2 || argc > 3) {
+ fprintf(stderr, "usage: %s <seed_string> [<size>]\n", argv[0]);
+ return 1;
+ }
+
+ c = (unsigned char *) argv[1];
+ do {
+ next = next * 11 + *c;
+ } while (*c++);
+
+ count = (argc == 3) ? strtoul(argv[2], NULL, 0) : -1L;
+
+ while (count--) {
+ next = next * 1103515245 + 12345;
+ if (putchar((next >> 16) & 0xff) == EOF)
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
new file mode 100644
index 0000000000..7aa9440e27
--- /dev/null
+++ b/t/helper/test-hashmap.c
@@ -0,0 +1,264 @@
+#include "git-compat-util.h"
+#include "hashmap.h"
+
+struct test_entry
+{
+ struct hashmap_entry ent;
+ /* key and value as two \0-terminated strings */
+ char key[FLEX_ARRAY];
+};
+
+static const char *get_value(const struct test_entry *e)
+{
+ return e->key + strlen(e->key) + 1;
+}
+
+static int test_entry_cmp(const struct test_entry *e1,
+ const struct test_entry *e2, const char* key)
+{
+ return strcmp(e1->key, key ? key : e2->key);
+}
+
+static int test_entry_cmp_icase(const struct test_entry *e1,
+ const struct test_entry *e2, const char* key)
+{
+ return strcasecmp(e1->key, key ? key : e2->key);
+}
+
+static struct test_entry *alloc_test_entry(int hash, char *key, int klen,
+ char *value, int vlen)
+{
+ struct test_entry *entry = malloc(sizeof(struct test_entry) + klen
+ + vlen + 2);
+ hashmap_entry_init(entry, hash);
+ memcpy(entry->key, key, klen + 1);
+ memcpy(entry->key + klen + 1, value, vlen + 1);
+ return entry;
+}
+
+#define HASH_METHOD_FNV 0
+#define HASH_METHOD_I 1
+#define HASH_METHOD_IDIV10 2
+#define HASH_METHOD_0 3
+#define HASH_METHOD_X2 4
+#define TEST_SPARSE 8
+#define TEST_ADD 16
+#define TEST_SIZE 100000
+
+static unsigned int hash(unsigned int method, unsigned int i, const char *key)
+{
+ unsigned int hash = 0;
+ switch (method & 3)
+ {
+ case HASH_METHOD_FNV:
+ hash = strhash(key);
+ break;
+ case HASH_METHOD_I:
+ hash = i;
+ break;
+ case HASH_METHOD_IDIV10:
+ hash = i / 10;
+ break;
+ case HASH_METHOD_0:
+ hash = 0;
+ break;
+ }
+
+ if (method & HASH_METHOD_X2)
+ hash = 2 * hash;
+ return hash;
+}
+
+/*
+ * Test performance of hashmap.[ch]
+ * Usage: time echo "perfhashmap method rounds" | test-hashmap
+ */
+static void perf_hashmap(unsigned int method, unsigned int rounds)
+{
+ struct hashmap map;
+ char buf[16];
+ struct test_entry **entries;
+ unsigned int *hashes;
+ unsigned int i, j;
+
+ entries = malloc(TEST_SIZE * sizeof(struct test_entry *));
+ hashes = malloc(TEST_SIZE * sizeof(int));
+ for (i = 0; i < TEST_SIZE; i++) {
+ snprintf(buf, sizeof(buf), "%i", i);
+ entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0);
+ hashes[i] = hash(method, i, entries[i]->key);
+ }
+
+ if (method & TEST_ADD) {
+ /* test adding to the map */
+ for (j = 0; j < rounds; j++) {
+ hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0);
+
+ /* add entries */
+ for (i = 0; i < TEST_SIZE; i++) {
+ hashmap_entry_init(entries[i], hashes[i]);
+ hashmap_add(&map, entries[i]);
+ }
+
+ hashmap_free(&map, 0);
+ }
+ } else {
+ /* test map lookups */
+ hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0);
+
+ /* fill the map (sparsely if specified) */
+ j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE;
+ for (i = 0; i < j; i++) {
+ hashmap_entry_init(entries[i], hashes[i]);
+ hashmap_add(&map, entries[i]);
+ }
+
+ for (j = 0; j < rounds; j++) {
+ for (i = 0; i < TEST_SIZE; i++) {
+ hashmap_get_from_hash(&map, hashes[i],
+ entries[i]->key);
+ }
+ }
+
+ hashmap_free(&map, 0);
+ }
+}
+
+#define DELIM " \t\r\n"
+
+/*
+ * Read stdin line by line and print result of commands to stdout:
+ *
+ * hash key -> strhash(key) memhash(key) strihash(key) memihash(key)
+ * put key value -> NULL / old value
+ * get key -> NULL / value
+ * remove key -> NULL / old value
+ * iterate -> key1 value1\nkey2 value2\n...
+ * size -> tablesize numentries
+ *
+ * perfhashmap method rounds -> test hashmap.[ch] performance
+ */
+int cmd_main(int argc, const char **argv)
+{
+ char line[1024];
+ struct hashmap map;
+ int icase;
+
+ /* init hash map */
+ icase = argc > 1 && !strcmp("ignorecase", argv[1]);
+ hashmap_init(&map, (hashmap_cmp_fn) (icase ? test_entry_cmp_icase
+ : test_entry_cmp), 0);
+
+ /* process commands from stdin */
+ while (fgets(line, sizeof(line), stdin)) {
+ char *cmd, *p1 = NULL, *p2 = NULL;
+ int l1 = 0, l2 = 0, hash = 0;
+ struct test_entry *entry;
+
+ /* break line into command and up to two parameters */
+ cmd = strtok(line, DELIM);
+ /* ignore empty lines */
+ if (!cmd || *cmd == '#')
+ continue;
+
+ p1 = strtok(NULL, DELIM);
+ if (p1) {
+ l1 = strlen(p1);
+ hash = icase ? strihash(p1) : strhash(p1);
+ p2 = strtok(NULL, DELIM);
+ if (p2)
+ l2 = strlen(p2);
+ }
+
+ if (!strcmp("hash", cmd) && l1) {
+
+ /* print results of different hash functions */
+ printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1),
+ strihash(p1), memihash(p1, l1));
+
+ } else if (!strcmp("add", cmd) && l1 && l2) {
+
+ /* create entry with key = p1, value = p2 */
+ entry = alloc_test_entry(hash, p1, l1, p2, l2);
+
+ /* add to hashmap */
+ hashmap_add(&map, entry);
+
+ } else if (!strcmp("put", cmd) && l1 && l2) {
+
+ /* create entry with key = p1, value = p2 */
+ entry = alloc_test_entry(hash, p1, l1, p2, l2);
+
+ /* add / replace entry */
+ entry = hashmap_put(&map, entry);
+
+ /* print and free replaced entry, if any */
+ puts(entry ? get_value(entry) : "NULL");
+ free(entry);
+
+ } else if (!strcmp("get", cmd) && l1) {
+
+ /* lookup entry in hashmap */
+ entry = hashmap_get_from_hash(&map, hash, p1);
+
+ /* print result */
+ if (!entry)
+ puts("NULL");
+ while (entry) {
+ puts(get_value(entry));
+ entry = hashmap_get_next(&map, entry);
+ }
+
+ } else if (!strcmp("remove", cmd) && l1) {
+
+ /* setup static key */
+ struct hashmap_entry key;
+ hashmap_entry_init(&key, hash);
+
+ /* remove entry from hashmap */
+ entry = hashmap_remove(&map, &key, p1);
+
+ /* print result and free entry*/
+ puts(entry ? get_value(entry) : "NULL");
+ free(entry);
+
+ } else if (!strcmp("iterate", cmd)) {
+
+ struct hashmap_iter iter;
+ hashmap_iter_init(&map, &iter);
+ while ((entry = hashmap_iter_next(&iter)))
+ printf("%s %s\n", entry->key, get_value(entry));
+
+ } else if (!strcmp("size", cmd)) {
+
+ /* print table sizes */
+ printf("%u %u\n", map.tablesize, map.size);
+
+ } else if (!strcmp("intern", cmd) && l1) {
+
+ /* test that strintern works */
+ const char *i1 = strintern(p1);
+ const char *i2 = strintern(p1);
+ if (strcmp(i1, p1))
+ printf("strintern(%s) returns %s\n", p1, i1);
+ else if (i1 == p1)
+ printf("strintern(%s) returns input pointer\n", p1);
+ else if (i1 != i2)
+ printf("strintern(%s) != strintern(%s)", i1, i2);
+ else
+ printf("%s\n", i1);
+
+ } else if (!strcmp("perfhashmap", cmd) && l1 && l2) {
+
+ perf_hashmap(atoi(p1), atoi(p2));
+
+ } else {
+
+ printf("Unknown command %s\n", cmd);
+
+ }
+ }
+
+ hashmap_free(&map, 1);
+ return 0;
+}
diff --git a/t/helper/test-index-version.c b/t/helper/test-index-version.c
new file mode 100644
index 0000000000..f569f6b7ef
--- /dev/null
+++ b/t/helper/test-index-version.c
@@ -0,0 +1,14 @@
+#include "cache.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ struct cache_header hdr;
+ int version;
+
+ memset(&hdr,0,sizeof(hdr));
+ if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))
+ return 0;
+ version = ntohl(hdr.hdr_version);
+ printf("%d\n", version);
+ return 0;
+}
diff --git a/t/helper/test-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c
new file mode 100644
index 0000000000..6368a89345
--- /dev/null
+++ b/t/helper/test-lazy-init-name-hash.c
@@ -0,0 +1,264 @@
+#include "cache.h"
+#include "parse-options.h"
+
+static int single;
+static int multi;
+static int count = 1;
+static int dump;
+static int perf;
+static int analyze;
+static int analyze_step;
+
+/*
+ * Dump the contents of the "dir" and "name" hash tables to stdout.
+ * If you sort the result, you can compare it with the other type
+ * mode and verify that both single and multi produce the same set.
+ */
+static void dump_run(void)
+{
+ struct hashmap_iter iter_dir;
+ struct hashmap_iter iter_cache;
+
+ /* Stolen from name-hash.c */
+ struct dir_entry {
+ struct hashmap_entry ent;
+ struct dir_entry *parent;
+ int nr;
+ unsigned int namelen;
+ char name[FLEX_ARRAY];
+ };
+
+ struct dir_entry *dir;
+ struct cache_entry *ce;
+
+ read_cache();
+ if (single) {
+ test_lazy_init_name_hash(&the_index, 0);
+ } else {
+ int nr_threads_used = test_lazy_init_name_hash(&the_index, 1);
+ if (!nr_threads_used)
+ die("non-threaded code path used");
+ }
+
+ dir = hashmap_iter_first(&the_index.dir_hash, &iter_dir);
+ while (dir) {
+ printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name);
+ dir = hashmap_iter_next(&iter_dir);
+ }
+
+ ce = hashmap_iter_first(&the_index.name_hash, &iter_cache);
+ while (ce) {
+ printf("name %08x %s\n", ce->ent.hash, ce->name);
+ ce = hashmap_iter_next(&iter_cache);
+ }
+
+ discard_cache();
+}
+
+/*
+ * Run the single or multi threaded version "count" times and
+ * report on the time taken.
+ */
+static uint64_t time_runs(int try_threaded)
+{
+ uint64_t t0, t1, t2;
+ uint64_t sum = 0;
+ uint64_t avg;
+ int nr_threads_used;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ t0 = getnanotime();
+ read_cache();
+ t1 = getnanotime();
+ nr_threads_used = test_lazy_init_name_hash(&the_index, try_threaded);
+ t2 = getnanotime();
+
+ sum += (t2 - t1);
+
+ if (try_threaded && !nr_threads_used)
+ die("non-threaded code path used");
+
+ if (nr_threads_used)
+ printf("%f %f %d multi %d\n",
+ ((double)(t1 - t0))/1000000000,
+ ((double)(t2 - t1))/1000000000,
+ the_index.cache_nr,
+ nr_threads_used);
+ else
+ printf("%f %f %d single\n",
+ ((double)(t1 - t0))/1000000000,
+ ((double)(t2 - t1))/1000000000,
+ the_index.cache_nr);
+ fflush(stdout);
+
+ discard_cache();
+ }
+
+ avg = sum / count;
+ if (count > 1)
+ printf("avg %f %s\n",
+ (double)avg/1000000000,
+ (try_threaded) ? "multi" : "single");
+
+ return avg;
+}
+
+/*
+ * Try a series of runs varying the "istate->cache_nr" and
+ * try to find a good value for the multi-threaded criteria.
+ */
+static void analyze_run(void)
+{
+ uint64_t t1s, t1m, t2s, t2m;
+ int cache_nr_limit;
+ int nr_threads_used;
+ int i;
+ int nr;
+
+ read_cache();
+ cache_nr_limit = the_index.cache_nr;
+ discard_cache();
+
+ nr = analyze;
+ while (1) {
+ uint64_t sum_single = 0;
+ uint64_t sum_multi = 0;
+ uint64_t avg_single;
+ uint64_t avg_multi;
+
+ if (nr > cache_nr_limit)
+ nr = cache_nr_limit;
+
+ for (i = 0; i < count; i++) {
+ read_cache();
+ the_index.cache_nr = nr; /* cheap truncate of index */
+ t1s = getnanotime();
+ test_lazy_init_name_hash(&the_index, 0);
+ t2s = getnanotime();
+ sum_single += (t2s - t1s);
+ the_index.cache_nr = cache_nr_limit;
+ discard_cache();
+
+ read_cache();
+ the_index.cache_nr = nr; /* cheap truncate of index */
+ t1m = getnanotime();
+ nr_threads_used = test_lazy_init_name_hash(&the_index, 1);
+ t2m = getnanotime();
+ sum_multi += (t2m - t1m);
+ the_index.cache_nr = cache_nr_limit;
+ discard_cache();
+
+ if (!nr_threads_used)
+ printf(" [size %8d] [single %f] non-threaded code path used\n",
+ nr, ((double)(t2s - t1s))/1000000000);
+ else
+ printf(" [size %8d] [single %f] %c [multi %f %d]\n",
+ nr,
+ ((double)(t2s - t1s))/1000000000,
+ (((t2s - t1s) < (t2m - t1m)) ? '<' : '>'),
+ ((double)(t2m - t1m))/1000000000,
+ nr_threads_used);
+ fflush(stdout);
+ }
+ if (count > 1) {
+ avg_single = sum_single / count;
+ avg_multi = sum_multi / count;
+ if (!nr_threads_used)
+ printf("avg [size %8d] [single %f]\n",
+ nr,
+ (double)avg_single/1000000000);
+ else
+ printf("avg [size %8d] [single %f] %c [multi %f %d]\n",
+ nr,
+ (double)avg_single/1000000000,
+ (avg_single < avg_multi ? '<' : '>'),
+ (double)avg_multi/1000000000,
+ nr_threads_used);
+ fflush(stdout);
+ }
+
+ if (nr >= cache_nr_limit)
+ return;
+ nr += analyze_step;
+ }
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ const char *usage[] = {
+ "test-lazy-init-name-hash -d (-s | -m)",
+ "test-lazy-init-name-hash -p [-c c]",
+ "test-lazy-init-name-hash -a a [--step s] [-c c]",
+ "test-lazy-init-name-hash (-s | -m) [-c c]",
+ "test-lazy-init-name-hash -s -m [-c c]",
+ NULL
+ };
+ struct option options[] = {
+ OPT_BOOL('s', "single", &single, "run single-threaded code"),
+ OPT_BOOL('m', "multi", &multi, "run multi-threaded code"),
+ OPT_INTEGER('c', "count", &count, "number of passes"),
+ OPT_BOOL('d', "dump", &dump, "dump hash tables"),
+ OPT_BOOL('p', "perf", &perf, "compare single vs multi"),
+ OPT_INTEGER('a', "analyze", &analyze, "analyze different multi sizes"),
+ OPT_INTEGER(0, "step", &analyze_step, "analyze step factor"),
+ OPT_END(),
+ };
+ const char *prefix;
+ uint64_t avg_single, avg_multi;
+
+ prefix = setup_git_directory();
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ /*
+ * istate->dir_hash is only created when ignore_case is set.
+ */
+ ignore_case = 1;
+
+ if (dump) {
+ if (perf || analyze > 0)
+ die("cannot combine dump, perf, or analyze");
+ if (count > 1)
+ die("count not valid with dump");
+ if (single && multi)
+ die("cannot use both single and multi with dump");
+ if (!single && !multi)
+ die("dump requires either single or multi");
+ dump_run();
+ return 0;
+ }
+
+ if (perf) {
+ if (analyze > 0)
+ die("cannot combine dump, perf, or analyze");
+ if (single || multi)
+ die("cannot use single or multi with perf");
+ avg_single = time_runs(0);
+ avg_multi = time_runs(1);
+ if (avg_multi > avg_single)
+ die("multi is slower");
+ return 0;
+ }
+
+ if (analyze) {
+ if (analyze < 500)
+ die("analyze must be at least 500");
+ if (!analyze_step)
+ analyze_step = analyze;
+ if (single || multi)
+ die("cannot use single or multi with analyze");
+ analyze_run();
+ return 0;
+ }
+
+ if (!single && !multi)
+ die("require either -s or -m or both");
+
+ if (single)
+ time_runs(0);
+ if (multi)
+ time_runs(1);
+
+ return 0;
+}
diff --git a/t/helper/test-line-buffer.c b/t/helper/test-line-buffer.c
new file mode 100644
index 0000000000..81575fe2ab
--- /dev/null
+++ b/t/helper/test-line-buffer.c
@@ -0,0 +1,91 @@
+/*
+ * test-line-buffer.c: code to exercise the svn importer's input helper
+ */
+
+#include "git-compat-util.h"
+#include "strbuf.h"
+#include "vcs-svn/line_buffer.h"
+
+static uint32_t strtouint32(const char *s)
+{
+ char *end;
+ uintmax_t n = strtoumax(s, &end, 10);
+ if (*s == '\0' || *end != '\0')
+ die("invalid count: %s", s);
+ return (uint32_t) n;
+}
+
+static void handle_command(const char *command, const char *arg, struct line_buffer *buf)
+{
+ switch (*command) {
+ case 'b':
+ if (starts_with(command, "binary ")) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addch(&sb, '>');
+ buffer_read_binary(buf, &sb, strtouint32(arg));
+ fwrite(sb.buf, 1, sb.len, stdout);
+ strbuf_release(&sb);
+ return;
+ }
+ case 'c':
+ if (starts_with(command, "copy ")) {
+ buffer_copy_bytes(buf, strtouint32(arg));
+ return;
+ }
+ case 's':
+ if (starts_with(command, "skip ")) {
+ buffer_skip_bytes(buf, strtouint32(arg));
+ return;
+ }
+ default:
+ die("unrecognized command: %s", command);
+ }
+}
+
+static void handle_line(const char *line, struct line_buffer *stdin_buf)
+{
+ const char *arg = strchr(line, ' ');
+ if (!arg)
+ die("no argument in line: %s", line);
+ handle_command(line, arg + 1, stdin_buf);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ struct line_buffer stdin_buf = LINE_BUFFER_INIT;
+ struct line_buffer file_buf = LINE_BUFFER_INIT;
+ struct line_buffer *input = &stdin_buf;
+ const char *filename;
+ char *s;
+
+ if (argc == 1)
+ filename = NULL;
+ else if (argc == 2)
+ filename = argv[1];
+ else
+ usage("test-line-buffer [file | &fd] < script");
+
+ if (buffer_init(&stdin_buf, NULL))
+ die_errno("open error");
+ if (filename) {
+ if (*filename == '&') {
+ if (buffer_fdinit(&file_buf, strtouint32(filename + 1)))
+ die_errno("error opening fd %s", filename + 1);
+ } else {
+ if (buffer_init(&file_buf, filename))
+ die_errno("error opening %s", filename);
+ }
+ input = &file_buf;
+ }
+
+ while ((s = buffer_read_line(&stdin_buf)))
+ handle_line(s, input);
+
+ if (filename && buffer_deinit(&file_buf))
+ die("error reading from %s", filename);
+ if (buffer_deinit(&stdin_buf))
+ die("input error");
+ if (ferror(stdout))
+ die("output error");
+ return 0;
+}
diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c
new file mode 100644
index 0000000000..e939502863
--- /dev/null
+++ b/t/helper/test-match-trees.c
@@ -0,0 +1,26 @@
+#include "cache.h"
+#include "tree.h"
+
+int cmd_main(int ac, const char **av)
+{
+ struct object_id hash1, hash2, shifted;
+ struct tree *one, *two;
+
+ setup_git_directory();
+
+ if (get_oid(av[1], &hash1))
+ die("cannot parse %s as an object name", av[1]);
+ if (get_oid(av[2], &hash2))
+ die("cannot parse %s as an object name", av[2]);
+ one = parse_tree_indirect(hash1.hash);
+ if (!one)
+ die("not a tree-ish %s", av[1]);
+ two = parse_tree_indirect(hash2.hash);
+ if (!two)
+ die("not a tree-ish %s", av[2]);
+
+ shift_tree(&one->object.oid, &two->object.oid, &shifted, -1);
+ printf("shifted: %s\n", oid_to_hex(&shifted));
+
+ exit(0);
+}
diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c
new file mode 100644
index 0000000000..335cf6b626
--- /dev/null
+++ b/t/helper/test-mergesort.c
@@ -0,0 +1,52 @@
+#include "cache.h"
+#include "mergesort.h"
+
+struct line {
+ char *text;
+ struct line *next;
+};
+
+static void *get_next(const void *a)
+{
+ return ((const struct line *)a)->next;
+}
+
+static void set_next(void *a, void *b)
+{
+ ((struct line *)a)->next = b;
+}
+
+static int compare_strings(const void *a, const void *b)
+{
+ const struct line *x = a, *y = b;
+ return strcmp(x->text, y->text);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ struct line *line, *p = NULL, *lines = NULL;
+ struct strbuf sb = STRBUF_INIT;
+
+ for (;;) {
+ if (strbuf_getwholeline(&sb, stdin, '\n'))
+ break;
+ line = xmalloc(sizeof(struct line));
+ line->text = strbuf_detach(&sb, NULL);
+ if (p) {
+ line->next = p->next;
+ p->next = line;
+ } else {
+ line->next = NULL;
+ lines = line;
+ }
+ p = line;
+ }
+
+ lines = llist_mergesort(lines, get_next, set_next, compare_strings);
+
+ while (lines) {
+ printf("%s", lines->text);
+ lines = lines->next;
+ }
+ return 0;
+}
diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c
new file mode 100644
index 0000000000..89d9b2f7be
--- /dev/null
+++ b/t/helper/test-mktemp.c
@@ -0,0 +1,14 @@
+/*
+ * test-mktemp.c: code to exercise the creation of temporary files
+ */
+#include "git-compat-util.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ if (argc != 2)
+ usage("Expected 1 parameter defining the temporary file template");
+
+ xmkstemp(xstrdup(argv[1]));
+
+ return 0;
+}
diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c
new file mode 100644
index 0000000000..a01430c24b
--- /dev/null
+++ b/t/helper/test-parse-options.c
@@ -0,0 +1,179 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "string-list.h"
+
+static int boolean = 0;
+static int integer = 0;
+static unsigned long magnitude = 0;
+static unsigned long timestamp;
+static int abbrev = 7;
+static int verbose = -1; /* unspecified */
+static int dry_run = 0, quiet = 0;
+static char *string = NULL;
+static char *file = NULL;
+static int ambiguous;
+static struct string_list list = STRING_LIST_INIT_NODUP;
+
+static struct {
+ int called;
+ const char *arg;
+ int unset;
+} length_cb;
+
+static int length_callback(const struct option *opt, const char *arg, int unset)
+{
+ length_cb.called = 1;
+ length_cb.arg = arg;
+ length_cb.unset = unset;
+
+ if (unset)
+ return 1; /* do not support unset */
+
+ *(int *)opt->value = strlen(arg);
+ return 0;
+}
+
+static int number_callback(const struct option *opt, const char *arg, int unset)
+{
+ *(int *)opt->value = strtol(arg, NULL, 10);
+ return 0;
+}
+
+static int collect_expect(const struct option *opt, const char *arg, int unset)
+{
+ struct string_list *expect;
+ struct string_list_item *item;
+ struct strbuf label = STRBUF_INIT;
+ const char *colon;
+
+ if (!arg || unset)
+ die("malformed --expect option");
+
+ expect = (struct string_list *)opt->value;
+ colon = strchr(arg, ':');
+ if (!colon)
+ die("malformed --expect option, lacking a colon");
+ strbuf_add(&label, arg, colon - arg);
+ item = string_list_insert(expect, strbuf_detach(&label, NULL));
+ if (item->util)
+ die("malformed --expect option, duplicate %s", label.buf);
+ item->util = (void *)arg;
+ return 0;
+}
+
+__attribute__((format (printf,3,4)))
+static void show(struct string_list *expect, int *status, const char *fmt, ...)
+{
+ struct string_list_item *item;
+ struct strbuf buf = STRBUF_INIT;
+ va_list args;
+
+ va_start(args, fmt);
+ strbuf_vaddf(&buf, fmt, args);
+ va_end(args);
+
+ if (!expect->nr)
+ printf("%s\n", buf.buf);
+ else {
+ char *colon = strchr(buf.buf, ':');
+ if (!colon)
+ die("malformed output format, output lacking colon: %s", fmt);
+ *colon = '\0';
+ item = string_list_lookup(expect, buf.buf);
+ *colon = ':';
+ if (!item)
+ ; /* not among entries being checked */
+ else {
+ if (strcmp((const char *)item->util, buf.buf)) {
+ printf("-%s\n", (char *)item->util);
+ printf("+%s\n", buf.buf);
+ *status = 1;
+ }
+ }
+ }
+ strbuf_release(&buf);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ const char *prefix = "prefix/";
+ const char *usage[] = {
+ "test-parse-options <options>",
+ NULL
+ };
+ struct string_list expect = STRING_LIST_INIT_NODUP;
+ struct option options[] = {
+ OPT_BOOL(0, "yes", &boolean, "get a boolean"),
+ OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"),
+ { OPTION_SET_INT, 'B', "no-fear", &boolean, NULL,
+ "be brave", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ OPT_COUNTUP('b', "boolean", &boolean, "increment by one"),
+ OPT_BIT('4', "or4", &boolean,
+ "bitwise-or boolean with ...0100", 4),
+ OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4),
+ OPT_GROUP(""),
+ OPT_INTEGER('i', "integer", &integer, "get a integer"),
+ OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
+ OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"),
+ OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23),
+ OPT_DATE('t', NULL, &timestamp, "get timestamp of <time>"),
+ OPT_CALLBACK('L', "length", &integer, "str",
+ "get length of <str>", length_callback),
+ OPT_FILENAME('F', "file", &file, "set file to <file>"),
+ OPT_GROUP("String options"),
+ OPT_STRING('s', "string", &string, "string", "get a string"),
+ OPT_STRING(0, "string2", &string, "str", "get another string"),
+ OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"),
+ OPT_STRING('o', NULL, &string, "str", "get another string"),
+ OPT_NOOP_NOARG(0, "obsolete"),
+ OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
+ OPT_GROUP("Magic arguments"),
+ OPT_ARGUMENT("quux", "means --quux"),
+ OPT_NUMBER_CALLBACK(&integer, "set integer to NUM",
+ number_callback),
+ { OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH },
+ { OPTION_COUNTUP, 0, "ambiguous", &ambiguous, NULL,
+ "positive ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ { OPTION_COUNTUP, 0, "no-ambiguous", &ambiguous, NULL,
+ "negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ OPT_GROUP("Standard options"),
+ OPT__ABBREV(&abbrev),
+ OPT__VERBOSE(&verbose, "be verbose"),
+ OPT__DRY_RUN(&dry_run, "dry run"),
+ OPT__QUIET(&quiet, "be quiet"),
+ OPT_CALLBACK(0, "expect", &expect, "string",
+ "expected output in the variable dump",
+ collect_expect),
+ OPT_END(),
+ };
+ int i;
+ int ret = 0;
+
+ argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
+
+ if (length_cb.called) {
+ const char *arg = length_cb.arg;
+ int unset = length_cb.unset;
+ show(&expect, &ret, "Callback: \"%s\", %d",
+ (arg ? arg : "not set"), unset);
+ }
+ show(&expect, &ret, "boolean: %d", boolean);
+ show(&expect, &ret, "integer: %d", integer);
+ show(&expect, &ret, "magnitude: %lu", magnitude);
+ show(&expect, &ret, "timestamp: %lu", timestamp);
+ show(&expect, &ret, "string: %s", string ? string : "(not set)");
+ show(&expect, &ret, "abbrev: %d", abbrev);
+ show(&expect, &ret, "verbose: %d", verbose);
+ show(&expect, &ret, "quiet: %d", quiet);
+ show(&expect, &ret, "dry run: %s", dry_run ? "yes" : "no");
+ show(&expect, &ret, "file: %s", file ? file : "(not set)");
+
+ for (i = 0; i < list.nr; i++)
+ show(&expect, &ret, "list: %s", list.items[i].string);
+
+ for (i = 0; i < argc; i++)
+ show(&expect, &ret, "arg %02d: %s", i, argv[i]);
+
+ return ret;
+}
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
new file mode 100644
index 0000000000..1ebe0f750c
--- /dev/null
+++ b/t/helper/test-path-utils.c
@@ -0,0 +1,262 @@
+#include "cache.h"
+#include "string-list.h"
+
+/*
+ * A "string_list_each_func_t" function that normalizes an entry from
+ * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason,
+ * die with an explanation.
+ */
+static int normalize_ceiling_entry(struct string_list_item *item, void *unused)
+{
+ char *ceil = item->string;
+
+ if (!*ceil)
+ die("Empty path is not supported");
+ if (!is_absolute_path(ceil))
+ die("Path \"%s\" is not absolute", ceil);
+ if (normalize_path_copy(ceil, ceil) < 0)
+ die("Path \"%s\" could not be normalized", ceil);
+ return 1;
+}
+
+static void normalize_argv_string(const char **var, const char *input)
+{
+ if (!strcmp(input, "<null>"))
+ *var = NULL;
+ else if (!strcmp(input, "<empty>"))
+ *var = "";
+ else
+ *var = input;
+
+ if (*var && (**var == '<' || **var == '('))
+ die("Bad value: %s\n", input);
+}
+
+struct test_data {
+ const char *from; /* input: transform from this ... */
+ const char *to; /* output: ... to this. */
+ const char *alternative; /* output: ... or this. */
+};
+
+static int test_function(struct test_data *data, char *(*func)(char *input),
+ const char *funcname)
+{
+ int failed = 0, i;
+ char buffer[1024];
+ char *to;
+
+ for (i = 0; data[i].to; i++) {
+ if (!data[i].from)
+ to = func(NULL);
+ else {
+ xsnprintf(buffer, sizeof(buffer), "%s", data[i].from);
+ to = func(buffer);
+ }
+ if (!strcmp(to, data[i].to))
+ continue;
+ if (!data[i].alternative)
+ error("FAIL: %s(%s) => '%s' != '%s'\n",
+ funcname, data[i].from, to, data[i].to);
+ else if (!strcmp(to, data[i].alternative))
+ continue;
+ else
+ error("FAIL: %s(%s) => '%s' != '%s', '%s'\n",
+ funcname, data[i].from, to, data[i].to,
+ data[i].alternative);
+ failed = 1;
+ }
+ return failed;
+}
+
+static struct test_data basename_data[] = {
+ /* --- POSIX type paths --- */
+ { NULL, "." },
+ { "", "." },
+ { ".", "." },
+ { "..", ".." },
+ { "/", "/" },
+ { "//", "/", "//" },
+ { "///", "/", "//" },
+ { "////", "/", "//" },
+ { "usr", "usr" },
+ { "/usr", "usr" },
+ { "/usr/", "usr" },
+ { "/usr//", "usr" },
+ { "/usr/lib", "lib" },
+ { "usr/lib", "lib" },
+ { "usr/lib///", "lib" },
+
+#if defined(__MINGW32__) || defined(_MSC_VER)
+ /* --- win32 type paths --- */
+ { "\\usr", "usr" },
+ { "\\usr\\", "usr" },
+ { "\\usr\\\\", "usr" },
+ { "\\usr\\lib", "lib" },
+ { "usr\\lib", "lib" },
+ { "usr\\lib\\\\\\", "lib" },
+ { "C:/usr", "usr" },
+ { "C:/usr", "usr" },
+ { "C:/usr/", "usr" },
+ { "C:/usr//", "usr" },
+ { "C:/usr/lib", "lib" },
+ { "C:usr/lib", "lib" },
+ { "C:usr/lib///", "lib" },
+ { "C:", "." },
+ { "C:a", "a" },
+ { "C:/", "/" },
+ { "C:///", "/" },
+ { "\\", "\\", "/" },
+ { "\\\\", "\\", "/" },
+ { "\\\\\\", "\\", "/" },
+#endif
+ { NULL, NULL }
+};
+
+static struct test_data dirname_data[] = {
+ /* --- POSIX type paths --- */
+ { NULL, "." },
+ { "", "." },
+ { ".", "." },
+ { "..", "." },
+ { "/", "/" },
+ { "//", "/", "//" },
+ { "///", "/", "//" },
+ { "////", "/", "//" },
+ { "usr", "." },
+ { "/usr", "/" },
+ { "/usr/", "/" },
+ { "/usr//", "/" },
+ { "/usr/lib", "/usr" },
+ { "usr/lib", "usr" },
+ { "usr/lib///", "usr" },
+
+#if defined(__MINGW32__) || defined(_MSC_VER)
+ /* --- win32 type paths --- */
+ { "\\", "\\" },
+ { "\\\\", "\\\\" },
+ { "\\usr", "\\" },
+ { "\\usr\\", "\\" },
+ { "\\usr\\\\", "\\" },
+ { "\\usr\\lib", "\\usr" },
+ { "usr\\lib", "usr" },
+ { "usr\\lib\\\\\\", "usr" },
+ { "C:a", "C:." },
+ { "C:/", "C:/" },
+ { "C:///", "C:/" },
+ { "C:/usr", "C:/" },
+ { "C:/usr/", "C:/" },
+ { "C:/usr//", "C:/" },
+ { "C:/usr/lib", "C:/usr" },
+ { "C:usr/lib", "C:usr" },
+ { "C:usr/lib///", "C:usr" },
+ { "\\\\\\", "\\" },
+ { "\\\\\\\\", "\\" },
+ { "C:", "C:.", "." },
+#endif
+ { NULL, NULL }
+};
+
+int cmd_main(int argc, const char **argv)
+{
+ if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
+ char *buf = xmallocz(strlen(argv[2]));
+ int rv = normalize_path_copy(buf, argv[2]);
+ if (rv)
+ buf = "++failed++";
+ puts(buf);
+ return 0;
+ }
+
+ if (argc >= 2 && !strcmp(argv[1], "real_path")) {
+ while (argc > 2) {
+ puts(real_path(argv[2]));
+ argc--;
+ argv++;
+ }
+ return 0;
+ }
+
+ if (argc >= 2 && !strcmp(argv[1], "absolute_path")) {
+ while (argc > 2) {
+ puts(absolute_path(argv[2]));
+ argc--;
+ argv++;
+ }
+ return 0;
+ }
+
+ if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
+ int len;
+ struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
+ char *path = xstrdup(argv[2]);
+
+ /*
+ * We have to normalize the arguments because under
+ * Windows, bash mangles arguments that look like
+ * absolute POSIX paths or colon-separate lists of
+ * absolute POSIX paths into DOS paths (e.g.,
+ * "/foo:/foo/bar" might be converted to
+ * "D:\Src\msysgit\foo;D:\Src\msysgit\foo\bar"),
+ * whereas longest_ancestor_length() requires paths
+ * that use forward slashes.
+ */
+ if (normalize_path_copy(path, path))
+ die("Path \"%s\" could not be normalized", argv[2]);
+ string_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1);
+ filter_string_list(&ceiling_dirs, 0,
+ normalize_ceiling_entry, NULL);
+ len = longest_ancestor_length(path, &ceiling_dirs);
+ string_list_clear(&ceiling_dirs, 0);
+ free(path);
+ printf("%d\n", len);
+ return 0;
+ }
+
+ if (argc >= 4 && !strcmp(argv[1], "prefix_path")) {
+ const char *prefix = argv[2];
+ int prefix_len = strlen(prefix);
+ int nongit_ok;
+ setup_git_directory_gently(&nongit_ok);
+ while (argc > 3) {
+ puts(prefix_path(prefix, prefix_len, argv[3]));
+ argc--;
+ argv++;
+ }
+ return 0;
+ }
+
+ if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) {
+ char *prefix = strip_path_suffix(argv[2], argv[3]);
+ printf("%s\n", prefix ? prefix : "(null)");
+ return 0;
+ }
+
+ if (argc == 3 && !strcmp(argv[1], "print_path")) {
+ puts(argv[2]);
+ return 0;
+ }
+
+ if (argc == 4 && !strcmp(argv[1], "relative_path")) {
+ struct strbuf sb = STRBUF_INIT;
+ const char *in, *prefix, *rel;
+ normalize_argv_string(&in, argv[2]);
+ normalize_argv_string(&prefix, argv[3]);
+ rel = relative_path(in, prefix, &sb);
+ if (!rel)
+ puts("(null)");
+ else
+ puts(strlen(rel) > 0 ? rel : "(empty)");
+ strbuf_release(&sb);
+ return 0;
+ }
+
+ if (argc == 2 && !strcmp(argv[1], "basename"))
+ return test_function(basename_data, basename, argv[1]);
+
+ if (argc == 2 && !strcmp(argv[1], "dirname"))
+ return test_function(dirname_data, dirname, argv[1]);
+
+ fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
+ argv[1] ? argv[1] : "(there was none)");
+ return 1;
+}
diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c
new file mode 100644
index 0000000000..ae58fff359
--- /dev/null
+++ b/t/helper/test-prio-queue.c
@@ -0,0 +1,39 @@
+#include "cache.h"
+#include "prio-queue.h"
+
+static int intcmp(const void *va, const void *vb, void *data)
+{
+ const int *a = va, *b = vb;
+ return *a - *b;
+}
+
+static void show(int *v)
+{
+ if (!v)
+ printf("NULL\n");
+ else
+ printf("%d\n", *v);
+ free(v);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ struct prio_queue pq = { intcmp };
+
+ while (*++argv) {
+ if (!strcmp(*argv, "get"))
+ show(prio_queue_get(&pq));
+ else if (!strcmp(*argv, "dump")) {
+ int *v;
+ while ((v = prio_queue_get(&pq)))
+ show(v);
+ }
+ else {
+ int *v = malloc(sizeof(*v));
+ *v = atoi(*argv);
+ prio_queue_put(&pq, v);
+ }
+ }
+
+ return 0;
+}
diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c
new file mode 100644
index 0000000000..2a7990efc3
--- /dev/null
+++ b/t/helper/test-read-cache.c
@@ -0,0 +1,13 @@
+#include "cache.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ int i, cnt = 1;
+ if (argc == 2)
+ cnt = strtol(argv[1], NULL, 0);
+ for (i = 0; i < cnt; i++) {
+ read_cache();
+ discard_cache();
+ }
+ return 0;
+}
diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c
new file mode 100644
index 0000000000..b5ea8a97c5
--- /dev/null
+++ b/t/helper/test-regex.c
@@ -0,0 +1,75 @@
+#include "git-compat-util.h"
+#include "gettext.h"
+
+struct reg_flag {
+ const char *name;
+ int flag;
+};
+
+static struct reg_flag reg_flags[] = {
+ { "EXTENDED", REG_EXTENDED },
+ { "NEWLINE", REG_NEWLINE },
+ { "ICASE", REG_ICASE },
+ { "NOTBOL", REG_NOTBOL },
+#ifdef REG_STARTEND
+ { "STARTEND", REG_STARTEND },
+#endif
+ { NULL, 0 }
+};
+
+static int test_regex_bug(void)
+{
+ char *pat = "[^={} \t]+";
+ char *str = "={}\nfred";
+ regex_t r;
+ regmatch_t m[1];
+
+ if (regcomp(&r, pat, REG_EXTENDED | REG_NEWLINE))
+ die("failed regcomp() for pattern '%s'", pat);
+ if (regexec(&r, str, 1, m, 0))
+ die("no match of pattern '%s' to string '%s'", pat, str);
+
+ /* http://sourceware.org/bugzilla/show_bug.cgi?id=3957 */
+ if (m[0].rm_so == 3) /* matches '\n' when it should not */
+ die("regex bug confirmed: re-build git with NO_REGEX=1");
+
+ return 0;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ const char *pat;
+ const char *str;
+ int flags = 0;
+ regex_t r;
+ regmatch_t m[1];
+
+ if (argc == 2 && !strcmp(argv[1], "--bug"))
+ return test_regex_bug();
+ else if (argc < 3)
+ usage("test-regex --bug\n"
+ "test-regex <pattern> <string> [<options>]");
+
+ argv++;
+ pat = *argv++;
+ str = *argv++;
+ while (*argv) {
+ struct reg_flag *rf;
+ for (rf = reg_flags; rf->name; rf++)
+ if (!strcmp(*argv, rf->name)) {
+ flags |= rf->flag;
+ break;
+ }
+ if (!rf->name)
+ die("do not recognize %s", *argv);
+ argv++;
+ }
+ git_setup_gettext();
+
+ if (regcomp(&r, pat, flags))
+ die("failed regcomp() for pattern '%s'", pat);
+ if (regexec(&r, str, 1, m, 0))
+ return 1;
+
+ return 0;
+}
diff --git a/t/helper/test-revision-walking.c b/t/helper/test-revision-walking.c
new file mode 100644
index 0000000000..b8e6fe1d00
--- /dev/null
+++ b/t/helper/test-revision-walking.c
@@ -0,0 +1,68 @@
+/*
+ * test-revision-walking.c: test revision walking API.
+ *
+ * (C) 2012 Heiko Voigt <hvoigt@hvoigt.net>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+
+static void print_commit(struct commit *commit)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode.type = DATE_NORMAL;
+ format_commit_message(commit, " %m %s", &sb, &ctx);
+ printf("%s\n", sb.buf);
+ strbuf_release(&sb);
+}
+
+static int run_revision_walk(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ const char *argv[] = {NULL, "--all", NULL};
+ int argc = ARRAY_SIZE(argv) - 1;
+ int got_revision = 0;
+
+ init_revisions(&rev, NULL);
+ setup_revisions(argc, argv, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ while ((commit = get_revision(&rev)) != NULL) {
+ print_commit(commit);
+ got_revision = 1;
+ }
+
+ reset_revision_walk();
+ return got_revision;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ if (argc < 2)
+ return 1;
+
+ setup_git_directory();
+
+ if (!strcmp(argv[1], "run-twice")) {
+ printf("1st\n");
+ if (!run_revision_walk())
+ return 1;
+ printf("2nd\n");
+ if (!run_revision_walk())
+ return 1;
+
+ return 0;
+ }
+
+ fprintf(stderr, "check usage\n");
+ return 1;
+}
diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c
new file mode 100644
index 0000000000..d24d157379
--- /dev/null
+++ b/t/helper/test-run-command.c
@@ -0,0 +1,87 @@
+/*
+ * test-run-command.c: test run command API.
+ *
+ * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "git-compat-util.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include <string.h>
+#include <errno.h>
+
+static int number_callbacks;
+static int parallel_next(struct child_process *cp,
+ struct strbuf *err,
+ void *cb,
+ void **task_cb)
+{
+ struct child_process *d = cb;
+ if (number_callbacks >= 4)
+ return 0;
+
+ argv_array_pushv(&cp->args, d->argv);
+ strbuf_addstr(err, "preloaded output of a child\n");
+ number_callbacks++;
+ return 1;
+}
+
+static int no_job(struct child_process *cp,
+ struct strbuf *err,
+ void *cb,
+ void **task_cb)
+{
+ strbuf_addstr(err, "no further jobs available\n");
+ return 0;
+}
+
+static int task_finished(int result,
+ struct strbuf *err,
+ void *pp_cb,
+ void *pp_task_cb)
+{
+ strbuf_addstr(err, "asking for a quick stop\n");
+ return 1;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ struct child_process proc = CHILD_PROCESS_INIT;
+ int jobs;
+
+ if (argc < 3)
+ return 1;
+ proc.argv = (const char **)argv + 2;
+
+ if (!strcmp(argv[1], "start-command-ENOENT")) {
+ if (start_command(&proc) < 0 && errno == ENOENT)
+ return 0;
+ fprintf(stderr, "FAIL %s\n", argv[1]);
+ return 1;
+ }
+ if (!strcmp(argv[1], "run-command"))
+ exit(run_command(&proc));
+
+ jobs = atoi(argv[2]);
+ proc.argv = (const char **)argv + 3;
+
+ if (!strcmp(argv[1], "run-command-parallel"))
+ exit(run_processes_parallel(jobs, parallel_next,
+ NULL, NULL, &proc));
+
+ if (!strcmp(argv[1], "run-command-abort"))
+ exit(run_processes_parallel(jobs, parallel_next,
+ NULL, task_finished, &proc));
+
+ if (!strcmp(argv[1], "run-command-no-jobs"))
+ exit(run_processes_parallel(jobs, no_job,
+ NULL, task_finished, &proc));
+
+ fprintf(stderr, "check usage\n");
+ return 1;
+}
diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c
new file mode 100644
index 0000000000..d2a63bea43
--- /dev/null
+++ b/t/helper/test-scrap-cache-tree.c
@@ -0,0 +1,18 @@
+#include "cache.h"
+#include "lockfile.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+static struct lock_file index_lock;
+
+int cmd_main(int ac, const char **av)
+{
+ setup_git_directory();
+ hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
+ if (read_cache() < 0)
+ die("unable to read index file");
+ active_cache_tree = NULL;
+ if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
+ die("unable to write index file");
+ return 0;
+}
diff --git a/t/helper/test-sha1-array.c b/t/helper/test-sha1-array.c
new file mode 100644
index 0000000000..f7a53c4ad6
--- /dev/null
+++ b/t/helper/test-sha1-array.c
@@ -0,0 +1,35 @@
+#include "cache.h"
+#include "sha1-array.h"
+
+static int print_sha1(const unsigned char sha1[20], void *data)
+{
+ puts(sha1_to_hex(sha1));
+ return 0;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ struct sha1_array array = SHA1_ARRAY_INIT;
+ struct strbuf line = STRBUF_INIT;
+
+ while (strbuf_getline(&line, stdin) != EOF) {
+ const char *arg;
+ unsigned char sha1[20];
+
+ if (skip_prefix(line.buf, "append ", &arg)) {
+ if (get_sha1_hex(arg, sha1))
+ die("not a hexadecimal SHA1: %s", arg);
+ sha1_array_append(&array, sha1);
+ } else if (skip_prefix(line.buf, "lookup ", &arg)) {
+ if (get_sha1_hex(arg, sha1))
+ die("not a hexadecimal SHA1: %s", arg);
+ printf("%d\n", sha1_array_lookup(&array, sha1));
+ } else if (!strcmp(line.buf, "clear"))
+ sha1_array_clear(&array);
+ else if (!strcmp(line.buf, "for_each_unique"))
+ sha1_array_for_each_unique(&array, print_sha1, NULL);
+ else
+ die("unknown command: %s", line.buf);
+ }
+ return 0;
+}
diff --git a/t/helper/test-sha1.c b/t/helper/test-sha1.c
new file mode 100644
index 0000000000..a1c13f54ec
--- /dev/null
+++ b/t/helper/test-sha1.c
@@ -0,0 +1,56 @@
+#include "cache.h"
+
+int cmd_main(int ac, const char **av)
+{
+ git_SHA_CTX ctx;
+ unsigned char sha1[20];
+ unsigned bufsz = 8192;
+ int binary = 0;
+ char *buffer;
+
+ if (ac == 2) {
+ if (!strcmp(av[1], "-b"))
+ binary = 1;
+ else
+ bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024;
+ }
+
+ if (!bufsz)
+ bufsz = 8192;
+
+ while ((buffer = malloc(bufsz)) == NULL) {
+ fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz);
+ bufsz /= 2;
+ if (bufsz < 1024)
+ die("OOPS");
+ }
+
+ git_SHA1_Init(&ctx);
+
+ while (1) {
+ ssize_t sz, this_sz;
+ char *cp = buffer;
+ unsigned room = bufsz;
+ this_sz = 0;
+ while (room) {
+ sz = xread(0, cp, room);
+ if (sz == 0)
+ break;
+ if (sz < 0)
+ die_errno("test-sha1");
+ this_sz += sz;
+ cp += sz;
+ room -= sz;
+ }
+ if (this_sz == 0)
+ break;
+ git_SHA1_Update(&ctx, buffer, this_sz);
+ }
+ git_SHA1_Final(sha1, &ctx);
+
+ if (binary)
+ fwrite(sha1, 1, 20, stdout);
+ else
+ puts(sha1_to_hex(sha1));
+ exit(0);
+}
diff --git a/t/helper/test-sha1.sh b/t/helper/test-sha1.sh
new file mode 100755
index 0000000000..750b95a0a1
--- /dev/null
+++ b/t/helper/test-sha1.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+dd if=/dev/zero bs=1048576 count=100 2>/dev/null |
+/usr/bin/time t/helper/test-sha1 >/dev/null
+
+while read expect cnt pfx
+do
+ case "$expect" in '#'*) continue ;; esac
+ actual=$(
+ {
+ test -z "$pfx" || echo "$pfx"
+ dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
+ perl -pe 'y/\000/g/'
+ } | ./t/helper/test-sha1 $cnt
+ )
+ if test "$expect" = "$actual"
+ then
+ echo "OK: $expect $cnt $pfx"
+ else
+ echo >&2 "OOPS: $cnt"
+ echo >&2 "expect: $expect"
+ echo >&2 "actual: $actual"
+ exit 1
+ fi
+done <<EOF
+da39a3ee5e6b4b0d3255bfef95601890afd80709 0
+3f786850e387550fdab836ed7e6dc881de23001b 0 a
+5277cbb45a15902137d332d97e89cf8136545485 0 ab
+03cfd743661f07975fa2f1220c5194cbaff48451 0 abc
+3330b4373640f9e4604991e73c7e86bfd8da2dc3 0 abcd
+ec11312386ad561674f724b8cca7cf1796e26d1d 0 abcde
+bdc37c074ec4ee6050d68bc133c6b912f36474df 0 abcdef
+69bca99b923859f2dc486b55b87f49689b7358c7 0 abcdefg
+e414af7161c9554089f4106d6f1797ef14a73666 0 abcdefgh
+0707f2970043f9f7c22029482db27733deaec029 0 abcdefghi
+a4dd8aa74a5636728fe52451636e2e17726033aa 1
+9986b45e2f4d7086372533bb6953a8652fa3644a 1 frotz
+23d8d4f788e8526b4877548a32577543cbaaf51f 10
+8cd23f822ab44c7f481b8c92d591f6d1fcad431c 10 frotz
+f3b5604a4e604899c1233edb3bf1cc0ede4d8c32 512
+b095bd837a371593048136e429e9ac4b476e1bb3 512 frotz
+08fa81d6190948de5ccca3966340cc48c10cceac 1200 xyzzy
+e33a291f42c30a159733dd98b8b3e4ff34158ca0 4090 4G
+#a3bf783bc20caa958f6cb24dd140a7b21984838d 9999 nitfol
+EOF
+
+exit
+
+# generating test vectors
+# inputs are number of megabytes followed by some random string to prefix.
+
+while read cnt pfx
+do
+ actual=$(
+ {
+ test -z "$pfx" || echo "$pfx"
+ dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
+ perl -pe 'y/\000/g/'
+ } | sha1sum |
+ sed -e 's/ .*//'
+ )
+ echo "$actual $cnt $pfx"
+done <<EOF
+0
+0 a
+0 ab
+0 abc
+0 abcd
+0 abcde
+0 abcdef
+0 abcdefg
+0 abcdefgh
+0 abcdefghi
+1
+1 frotz
+10
+10 frotz
+512
+512 frotz
+1200 xyzzy
+4090 4G
+9999 nitfol
+EOF
diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c
new file mode 100644
index 0000000000..b71edbd442
--- /dev/null
+++ b/t/helper/test-sigchain.c
@@ -0,0 +1,22 @@
+#include "cache.h"
+#include "sigchain.h"
+
+#define X(f) \
+static void f(int sig) { \
+ puts(#f); \
+ fflush(stdout); \
+ sigchain_pop(sig); \
+ raise(sig); \
+}
+X(one)
+X(two)
+X(three)
+#undef X
+
+int cmd_main(int argc, const char **argv) {
+ sigchain_push(SIGTERM, one);
+ sigchain_push(SIGTERM, two);
+ sigchain_push(SIGTERM, three);
+ raise(SIGTERM);
+ return 0;
+}
diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c
new file mode 100644
index 0000000000..c502fa16d3
--- /dev/null
+++ b/t/helper/test-string-list.c
@@ -0,0 +1,128 @@
+#include "cache.h"
+#include "string-list.h"
+
+/*
+ * Parse an argument into a string list. arg should either be a
+ * ':'-separated list of strings, or "-" to indicate an empty string
+ * list (as opposed to "", which indicates a string list containing a
+ * single empty string). list->strdup_strings must be set.
+ */
+static void parse_string_list(struct string_list *list, const char *arg)
+{
+ if (!strcmp(arg, "-"))
+ return;
+
+ (void)string_list_split(list, arg, ':', -1);
+}
+
+static void write_list(const struct string_list *list)
+{
+ int i;
+ for (i = 0; i < list->nr; i++)
+ printf("[%d]: \"%s\"\n", i, list->items[i].string);
+}
+
+static void write_list_compact(const struct string_list *list)
+{
+ int i;
+ if (!list->nr)
+ printf("-\n");
+ else {
+ printf("%s", list->items[0].string);
+ for (i = 1; i < list->nr; i++)
+ printf(":%s", list->items[i].string);
+ printf("\n");
+ }
+}
+
+static int prefix_cb(struct string_list_item *item, void *cb_data)
+{
+ const char *prefix = (const char *)cb_data;
+ return starts_with(item->string, prefix);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ if (argc == 5 && !strcmp(argv[1], "split")) {
+ struct string_list list = STRING_LIST_INIT_DUP;
+ int i;
+ const char *s = argv[2];
+ int delim = *argv[3];
+ int maxsplit = atoi(argv[4]);
+
+ i = string_list_split(&list, s, delim, maxsplit);
+ printf("%d\n", i);
+ write_list(&list);
+ string_list_clear(&list, 0);
+ return 0;
+ }
+
+ if (argc == 5 && !strcmp(argv[1], "split_in_place")) {
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ int i;
+ char *s = xstrdup(argv[2]);
+ int delim = *argv[3];
+ int maxsplit = atoi(argv[4]);
+
+ i = string_list_split_in_place(&list, s, delim, maxsplit);
+ printf("%d\n", i);
+ write_list(&list);
+ string_list_clear(&list, 0);
+ free(s);
+ return 0;
+ }
+
+ if (argc == 4 && !strcmp(argv[1], "filter")) {
+ /*
+ * Retain only the items that have the specified prefix.
+ * Arguments: list|- prefix
+ */
+ struct string_list list = STRING_LIST_INIT_DUP;
+ const char *prefix = argv[3];
+
+ parse_string_list(&list, argv[2]);
+ filter_string_list(&list, 0, prefix_cb, (void *)prefix);
+ write_list_compact(&list);
+ string_list_clear(&list, 0);
+ return 0;
+ }
+
+ if (argc == 3 && !strcmp(argv[1], "remove_duplicates")) {
+ struct string_list list = STRING_LIST_INIT_DUP;
+
+ parse_string_list(&list, argv[2]);
+ string_list_remove_duplicates(&list, 0);
+ write_list_compact(&list);
+ string_list_clear(&list, 0);
+ return 0;
+ }
+
+ if (argc == 2 && !strcmp(argv[1], "sort")) {
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ struct strbuf sb = STRBUF_INIT;
+ struct string_list_item *item;
+
+ strbuf_read(&sb, 0, 0);
+
+ /*
+ * Split by newline, but don't create a string_list item
+ * for the empty string after the last separator.
+ */
+ if (sb.buf[sb.len - 1] == '\n')
+ strbuf_setlen(&sb, sb.len - 1);
+ string_list_split_in_place(&list, sb.buf, '\n', -1);
+
+ string_list_sort(&list);
+
+ for_each_string_list_item(item, &list)
+ puts(item->string);
+
+ string_list_clear(&list, 0);
+ strbuf_release(&sb);
+ return 0;
+ }
+
+ fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
+ argv[1] ? argv[1] : "(there was none)");
+ return 1;
+}
diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c
new file mode 100644
index 0000000000..2f144d539a
--- /dev/null
+++ b/t/helper/test-submodule-config.c
@@ -0,0 +1,76 @@
+#include "cache.h"
+#include "submodule-config.h"
+#include "submodule.h"
+
+static void die_usage(int argc, const char **argv, const char *msg)
+{
+ fprintf(stderr, "%s\n", msg);
+ fprintf(stderr, "Usage: %s [<commit> <submodulepath>] ...\n", argv[0]);
+ exit(1);
+}
+
+static int git_test_config(const char *var, const char *value, void *cb)
+{
+ return parse_submodule_config_option(var, value);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ const char **arg = argv;
+ int my_argc = argc;
+ int output_url = 0;
+ int lookup_name = 0;
+
+ arg++;
+ my_argc--;
+ while (arg[0] && starts_with(arg[0], "--")) {
+ if (!strcmp(arg[0], "--url"))
+ output_url = 1;
+ if (!strcmp(arg[0], "--name"))
+ lookup_name = 1;
+ arg++;
+ my_argc--;
+ }
+
+ if (my_argc % 2 != 0)
+ die_usage(argc, argv, "Wrong number of arguments.");
+
+ setup_git_directory();
+ gitmodules_config();
+ git_config(git_test_config, NULL);
+
+ while (*arg) {
+ unsigned char commit_sha1[20];
+ const struct submodule *submodule;
+ const char *commit;
+ const char *path_or_name;
+
+ commit = arg[0];
+ path_or_name = arg[1];
+
+ if (commit[0] == '\0')
+ hashclr(commit_sha1);
+ else if (get_sha1(commit, commit_sha1) < 0)
+ die_usage(argc, argv, "Commit not found.");
+
+ if (lookup_name) {
+ submodule = submodule_from_name(commit_sha1, path_or_name);
+ } else
+ submodule = submodule_from_path(commit_sha1, path_or_name);
+ if (!submodule)
+ die_usage(argc, argv, "Submodule not found.");
+
+ if (output_url)
+ printf("Submodule url: '%s' for path '%s'\n",
+ submodule->url, submodule->path);
+ else
+ printf("Submodule name: '%s' for path '%s'\n",
+ submodule->name, submodule->path);
+
+ arg += 2;
+ }
+
+ submodule_free();
+
+ return 0;
+}
diff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c
new file mode 100644
index 0000000000..30c5765bfc
--- /dev/null
+++ b/t/helper/test-subprocess.c
@@ -0,0 +1,19 @@
+#include "cache.h"
+#include "run-command.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ int nogit = 0;
+
+ setup_git_directory_gently(&nogit);
+ if (nogit)
+ die("No git repo found");
+ if (argc > 1 && !strcmp(argv[1], "--setup-work-tree")) {
+ setup_work_tree();
+ argv++;
+ }
+ cp.git_cmd = 1;
+ cp.argv = (const char **)argv + 1;
+ return run_command(&cp);
+}
diff --git a/t/helper/test-svn-fe.c b/t/helper/test-svn-fe.c
new file mode 100644
index 0000000000..7667c0803f
--- /dev/null
+++ b/t/helper/test-svn-fe.c
@@ -0,0 +1,52 @@
+/*
+ * test-svn-fe: Code to exercise the svn import lib
+ */
+
+#include "git-compat-util.h"
+#include "vcs-svn/svndump.h"
+#include "vcs-svn/svndiff.h"
+#include "vcs-svn/sliding_window.h"
+#include "vcs-svn/line_buffer.h"
+
+static const char test_svnfe_usage[] =
+ "test-svn-fe (<dumpfile> | [-d] <preimage> <delta> <len>)";
+
+static int apply_delta(int argc, const char **argv)
+{
+ struct line_buffer preimage = LINE_BUFFER_INIT;
+ struct line_buffer delta = LINE_BUFFER_INIT;
+ struct sliding_view preimage_view = SLIDING_VIEW_INIT(&preimage, -1);
+
+ if (argc != 5)
+ usage(test_svnfe_usage);
+
+ if (buffer_init(&preimage, argv[2]))
+ die_errno("cannot open preimage");
+ if (buffer_init(&delta, argv[3]))
+ die_errno("cannot open delta");
+ if (svndiff0_apply(&delta, (off_t) strtoumax(argv[4], NULL, 0),
+ &preimage_view, stdout))
+ return 1;
+ if (buffer_deinit(&preimage))
+ die_errno("cannot close preimage");
+ if (buffer_deinit(&delta))
+ die_errno("cannot close delta");
+ strbuf_release(&preimage_view.buf);
+ return 0;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ if (argc == 2) {
+ if (svndump_init(argv[1]))
+ return 1;
+ svndump_read(NULL, "refs/heads/master", "refs/notes/svn/revs");
+ svndump_deinit();
+ svndump_reset();
+ return 0;
+ }
+
+ if (argc >= 2 && !strcmp(argv[1], "-d"))
+ return apply_delta(argc, argv);
+ usage(test_svnfe_usage);
+}
diff --git a/t/helper/test-urlmatch-normalization.c b/t/helper/test-urlmatch-normalization.c
new file mode 100644
index 0000000000..49b6e836be
--- /dev/null
+++ b/t/helper/test-urlmatch-normalization.c
@@ -0,0 +1,50 @@
+#include "git-compat-util.h"
+#include "urlmatch.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char usage[] = "test-urlmatch-normalization [-p | -l] <url1> | <url1> <url2>";
+ char *url1, *url2;
+ int opt_p = 0, opt_l = 0;
+
+ /*
+ * For one url, succeed if url_normalize succeeds on it, fail otherwise.
+ * For two urls, succeed only if url_normalize succeeds on both and
+ * the results compare equal with strcmp. If -p is given (one url only)
+ * and url_normalize succeeds, print the result followed by "\n". If
+ * -l is given (one url only) and url_normalize succeeds, print the
+ * returned length in decimal followed by "\n".
+ */
+
+ if (argc > 1 && !strcmp(argv[1], "-p")) {
+ opt_p = 1;
+ argc--;
+ argv++;
+ } else if (argc > 1 && !strcmp(argv[1], "-l")) {
+ opt_l = 1;
+ argc--;
+ argv++;
+ }
+
+ if (argc < 2 || argc > 3)
+ die("%s", usage);
+
+ if (argc == 2) {
+ struct url_info info;
+ url1 = url_normalize(argv[1], &info);
+ if (!url1)
+ return 1;
+ if (opt_p)
+ printf("%s\n", url1);
+ if (opt_l)
+ printf("%u\n", (unsigned)info.url_len);
+ return 0;
+ }
+
+ if (opt_p || opt_l)
+ die("%s", usage);
+
+ url1 = url_normalize(argv[1], NULL);
+ url2 = url_normalize(argv[2], NULL);
+ return (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;
+}
diff --git a/t/helper/test-wildmatch.c b/t/helper/test-wildmatch.c
new file mode 100644
index 0000000000..52be876fed
--- /dev/null
+++ b/t/helper/test-wildmatch.c
@@ -0,0 +1,21 @@
+#include "cache.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ int i;
+ for (i = 2; i < argc; i++) {
+ if (argv[i][0] == '/')
+ die("Forward slash is not allowed at the beginning of the\n"
+ "pattern because Windows does not like it. Use `XXX/' instead.");
+ else if (!strncmp(argv[i], "XXX/", 4))
+ argv[i] += 3;
+ }
+ if (!strcmp(argv[1], "wildmatch"))
+ return !!wildmatch(argv[3], argv[2], WM_PATHNAME, NULL);
+ else if (!strcmp(argv[1], "iwildmatch"))
+ return !!wildmatch(argv[3], argv[2], WM_PATHNAME | WM_CASEFOLD, NULL);
+ else if (!strcmp(argv[1], "pathmatch"))
+ return !!wildmatch(argv[3], argv[2], 0, NULL);
+ else
+ return 1;
+}