diff options
Diffstat (limited to 't')
-rw-r--r-- | t/helper/test-bitmap.c | 24 | ||||
-rw-r--r-- | t/helper/test-read-midx.c | 24 | ||||
-rw-r--r-- | t/helper/test-simple-ipc.c | 787 | ||||
-rw-r--r-- | t/helper/test-tool.c | 2 | ||||
-rw-r--r-- | t/helper/test-tool.h | 2 | ||||
-rwxr-xr-x | t/perf/p5310-pack-bitmaps.sh | 14 | ||||
-rwxr-xr-x | t/t0052-simple-ipc.sh | 122 | ||||
-rwxr-xr-x | t/t3206-range-diff.sh | 24 | ||||
-rwxr-xr-x | t/t3510-cherry-pick-sequence.sh | 32 | ||||
-rwxr-xr-x | t/t4014-format-patch.sh | 34 | ||||
-rwxr-xr-x | t/t5310-pack-bitmaps.sh | 38 | ||||
-rwxr-xr-x | t/t5319-multi-pack-index.sh | 42 | ||||
-rwxr-xr-x | t/t5601-clone.sh | 9 | ||||
-rwxr-xr-x | t/t5606-clone-options.sh | 27 | ||||
-rwxr-xr-x | t/t5611-clone-config.sh | 25 | ||||
-rwxr-xr-x | t/t6300-for-each-ref.sh | 10 | ||||
-rwxr-xr-x | t/t6423-merge-rename-directories.sh | 71 | ||||
-rwxr-xr-x | t/t7502-commit-porcelain.sh | 312 |
18 files changed, 1593 insertions, 6 deletions
diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c new file mode 100644 index 0000000000..134a1e9d76 --- /dev/null +++ b/t/helper/test-bitmap.c @@ -0,0 +1,24 @@ +#include "test-tool.h" +#include "cache.h" +#include "pack-bitmap.h" + +static int bitmap_list_commits(void) +{ + return test_bitmap_commits(the_repository); +} + +int cmd__bitmap(int argc, const char **argv) +{ + setup_git_directory(); + + if (argc != 2) + goto usage; + + if (!strcmp(argv[1], "list-commits")) + return bitmap_list_commits(); + +usage: + usage("\ttest-tool bitmap list-commits"); + + return -1; +} diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index 2430880f78..7c2eb11a8e 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c @@ -4,7 +4,7 @@ #include "repository.h" #include "object-store.h" -static int read_midx_file(const char *object_dir) +static int read_midx_file(const char *object_dir, int show_objects) { uint32_t i; struct multi_pack_index *m; @@ -43,13 +43,29 @@ static int read_midx_file(const char *object_dir) printf("object-dir: %s\n", m->object_dir); + if (show_objects) { + struct object_id oid; + struct pack_entry e; + + for (i = 0; i < m->num_objects; i++) { + nth_midxed_object_oid(&oid, m, i); + fill_midx_entry(the_repository, &oid, &e, m); + + printf("%s %"PRIu64"\t%s\n", + oid_to_hex(&oid), e.offset, e.p->pack_name); + } + return 0; + } + return 0; } int cmd__read_midx(int argc, const char **argv) { - if (argc != 2) - usage("read-midx <object-dir>"); + if (!(argc == 2 || argc == 3)) + usage("read-midx [--show-objects] <object-dir>"); - return read_midx_file(argv[1]); + if (!strcmp(argv[1], "--show-objects")) + return read_midx_file(argv[2], 1); + return read_midx_file(argv[1], 0); } diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c new file mode 100644 index 0000000000..42040ef81b --- /dev/null +++ b/t/helper/test-simple-ipc.c @@ -0,0 +1,787 @@ +/* + * test-simple-ipc.c: verify that the Inter-Process Communication works. + */ + +#include "test-tool.h" +#include "cache.h" +#include "strbuf.h" +#include "simple-ipc.h" +#include "parse-options.h" +#include "thread-utils.h" +#include "strvec.h" + +#ifndef SUPPORTS_SIMPLE_IPC +int cmd__simple_ipc(int argc, const char **argv) +{ + die("simple IPC not available on this platform"); +} +#else + +/* + * The test daemon defines an "application callback" that supports a + * series of commands (see `test_app_cb()`). + * + * Unknown commands are caught here and we send an error message back + * to the client process. + */ +static int app__unhandled_command(const char *command, + ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int ret; + + strbuf_addf(&buf, "unhandled command: %s", command); + ret = reply_cb(reply_data, buf.buf, buf.len); + strbuf_release(&buf); + + return ret; +} + +/* + * Reply with a single very large buffer. This is to ensure that + * long response are properly handled -- whether the chunking occurs + * in the kernel or in the (probably pkt-line) layer. + */ +#define BIG_ROWS (10000) +static int app__big_command(ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int row; + int ret; + + for (row = 0; row < BIG_ROWS; row++) + strbuf_addf(&buf, "big: %.75d\n", row); + + ret = reply_cb(reply_data, buf.buf, buf.len); + strbuf_release(&buf); + + return ret; +} + +/* + * Reply with a series of lines. This is to ensure that we can incrementally + * compute the response and chunk it to the client. + */ +#define CHUNK_ROWS (10000) +static int app__chunk_command(ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int row; + int ret; + + for (row = 0; row < CHUNK_ROWS; row++) { + strbuf_setlen(&buf, 0); + strbuf_addf(&buf, "big: %.75d\n", row); + ret = reply_cb(reply_data, buf.buf, buf.len); + } + + strbuf_release(&buf); + + return ret; +} + +/* + * Slowly reply with a series of lines. This is to model an expensive to + * compute chunked response (which might happen if this callback is running + * in a thread and is fighting for a lock with other threads). + */ +#define SLOW_ROWS (1000) +#define SLOW_DELAY_MS (10) +static int app__slow_command(ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int row; + int ret; + + for (row = 0; row < SLOW_ROWS; row++) { + strbuf_setlen(&buf, 0); + strbuf_addf(&buf, "big: %.75d\n", row); + ret = reply_cb(reply_data, buf.buf, buf.len); + sleep_millisec(SLOW_DELAY_MS); + } + + strbuf_release(&buf); + + return ret; +} + +/* + * The client sent a command followed by a (possibly very) large buffer. + */ +static int app__sendbytes_command(const char *received, + ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf_resp = STRBUF_INIT; + const char *p = "?"; + int len_ballast = 0; + int k; + int errs = 0; + int ret; + + if (skip_prefix(received, "sendbytes ", &p)) + len_ballast = strlen(p); + + /* + * Verify that the ballast is n copies of a single letter. + * And that the multi-threaded IO layer didn't cross the streams. + */ + for (k = 1; k < len_ballast; k++) + if (p[k] != p[0]) + errs++; + + if (errs) + strbuf_addf(&buf_resp, "errs:%d\n", errs); + else + strbuf_addf(&buf_resp, "rcvd:%c%08d\n", p[0], len_ballast); + + ret = reply_cb(reply_data, buf_resp.buf, buf_resp.len); + + strbuf_release(&buf_resp); + + return ret; +} + +/* + * An arbitrary fixed address to verify that the application instance + * data is handled properly. + */ +static int my_app_data = 42; + +static ipc_server_application_cb test_app_cb; + +/* + * This is the "application callback" that sits on top of the + * "ipc-server". It completely defines the set of commands supported + * by this application. + */ +static int test_app_cb(void *application_data, + const char *command, + ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + /* + * Verify that we received the application-data that we passed + * when we started the ipc-server. (We have several layers of + * callbacks calling callbacks and it's easy to get things mixed + * up (especially when some are "void*").) + */ + if (application_data != (void*)&my_app_data) + BUG("application_cb: application_data pointer wrong"); + + if (!strcmp(command, "quit")) { + /* + * The client sent a "quit" command. This is an async + * request for the server to shutdown. + * + * We DO NOT send the client a response message + * (because we have nothing to say and the other + * server threads have not yet stopped). + * + * Tell the ipc-server layer to start shutting down. + * This includes: stop listening for new connections + * on the socket/pipe and telling all worker threads + * to finish/drain their outgoing responses to other + * clients. + * + * This DOES NOT force an immediate sync shutdown. + */ + return SIMPLE_IPC_QUIT; + } + + if (!strcmp(command, "ping")) { + const char *answer = "pong"; + return reply_cb(reply_data, answer, strlen(answer)); + } + + if (!strcmp(command, "big")) + return app__big_command(reply_cb, reply_data); + + if (!strcmp(command, "chunk")) + return app__chunk_command(reply_cb, reply_data); + + if (!strcmp(command, "slow")) + return app__slow_command(reply_cb, reply_data); + + if (starts_with(command, "sendbytes ")) + return app__sendbytes_command(command, reply_cb, reply_data); + + return app__unhandled_command(command, reply_cb, reply_data); +} + +struct cl_args +{ + const char *subcommand; + const char *path; + const char *token; + + int nr_threads; + int max_wait_sec; + int bytecount; + int batchsize; + + char bytevalue; +}; + +static struct cl_args cl_args = { + .subcommand = NULL, + .path = "ipc-test", + .token = NULL, + + .nr_threads = 5, + .max_wait_sec = 60, + .bytecount = 1024, + .batchsize = 10, + + .bytevalue = 'x', +}; + +/* + * This process will run as a simple-ipc server and listen for IPC commands + * from client processes. + */ +static int daemon__run_server(void) +{ + int ret; + + struct ipc_server_opts opts = { + .nr_threads = cl_args.nr_threads, + }; + + /* + * Synchronously run the ipc-server. We don't need any application + * instance data, so pass an arbitrary pointer (that we'll later + * verify made the round trip). + */ + ret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data); + if (ret == -2) + error(_("socket/pipe already in use: '%s'"), cl_args.path); + else if (ret == -1) + error_errno(_("could not start server on: '%s'"), cl_args.path); + + return ret; +} + +#ifndef GIT_WINDOWS_NATIVE +/* + * This is adapted from `daemonize()`. Use `fork()` to directly create and + * run the daemon in a child process. + */ +static int spawn_server(pid_t *pid) +{ + struct ipc_server_opts opts = { + .nr_threads = cl_args.nr_threads, + }; + + *pid = fork(); + + switch (*pid) { + case 0: + if (setsid() == -1) + error_errno(_("setsid failed")); + close(0); + close(1); + close(2); + sanitize_stdfds(); + + return ipc_server_run(cl_args.path, &opts, test_app_cb, + (void*)&my_app_data); + + case -1: + return error_errno(_("could not spawn daemon in the background")); + + default: + return 0; + } +} +#else +/* + * Conceptually like `daemonize()` but different because Windows does not + * have `fork(2)`. Spawn a normal Windows child process but without the + * limitations of `start_command()` and `finish_command()`. + */ +static int spawn_server(pid_t *pid) +{ + char test_tool_exe[MAX_PATH]; + struct strvec args = STRVEC_INIT; + int in, out; + + GetModuleFileNameA(NULL, test_tool_exe, MAX_PATH); + + in = open("/dev/null", O_RDONLY); + out = open("/dev/null", O_WRONLY); + + strvec_push(&args, test_tool_exe); + strvec_push(&args, "simple-ipc"); + strvec_push(&args, "run-daemon"); + strvec_pushf(&args, "--name=%s", cl_args.path); + strvec_pushf(&args, "--threads=%d", cl_args.nr_threads); + + *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out); + close(in); + close(out); + + strvec_clear(&args); + + if (*pid < 0) + return error(_("could not spawn daemon in the background")); + + return 0; +} +#endif + +/* + * This is adapted from `wait_or_whine()`. Watch the child process and + * let it get started and begin listening for requests on the socket + * before reporting our success. + */ +static int wait_for_server_startup(pid_t pid_child) +{ + int status; + pid_t pid_seen; + enum ipc_active_state s; + time_t time_limit, now; + + time(&time_limit); + time_limit += cl_args.max_wait_sec; + + for (;;) { + pid_seen = waitpid(pid_child, &status, WNOHANG); + + if (pid_seen == -1) + return error_errno(_("waitpid failed")); + + else if (pid_seen == 0) { + /* + * The child is still running (this should be + * the normal case). Try to connect to it on + * the socket and see if it is ready for + * business. + * + * If there is another daemon already running, + * our child will fail to start (possibly + * after a timeout on the lock), but we don't + * care (who responds) if the socket is live. + */ + s = ipc_get_active_state(cl_args.path); + if (s == IPC_STATE__LISTENING) + return 0; + + time(&now); + if (now > time_limit) + return error(_("daemon not online yet")); + + continue; + } + + else if (pid_seen == pid_child) { + /* + * The new child daemon process shutdown while + * it was starting up, so it is not listening + * on the socket. + * + * Try to ping the socket in the odd chance + * that another daemon started (or was already + * running) while our child was starting. + * + * Again, we don't care who services the socket. + */ + s = ipc_get_active_state(cl_args.path); + if (s == IPC_STATE__LISTENING) + return 0; + + /* + * We don't care about the WEXITSTATUS() nor + * any of the WIF*(status) values because + * `cmd__simple_ipc()` does the `!!result` + * trick on all function return values. + * + * So it is sufficient to just report the + * early shutdown as an error. + */ + return error(_("daemon failed to start")); + } + + else + return error(_("waitpid is confused")); + } +} + +/* + * This process will start a simple-ipc server in a background process and + * wait for it to become ready. This is like `daemonize()` but gives us + * more control and better error reporting (and makes it easier to write + * unit tests). + */ +static int daemon__start_server(void) +{ + pid_t pid_child; + int ret; + + /* + * Run the actual daemon in a background process. + */ + ret = spawn_server(&pid_child); + if (pid_child <= 0) + return ret; + + /* + * Let the parent wait for the child process to get started + * and begin listening for requests on the socket. + */ + ret = wait_for_server_startup(pid_child); + + return ret; +} + +/* + * This process will run a quick probe to see if a simple-ipc server + * is active on this path. + * + * Returns 0 if the server is alive. + */ +static int client__probe_server(void) +{ + enum ipc_active_state s; + + s = ipc_get_active_state(cl_args.path); + switch (s) { + case IPC_STATE__LISTENING: + return 0; + + case IPC_STATE__NOT_LISTENING: + return error("no server listening at '%s'", cl_args.path); + + case IPC_STATE__PATH_NOT_FOUND: + return error("path not found '%s'", cl_args.path); + + case IPC_STATE__INVALID_PATH: + return error("invalid pipe/socket name '%s'", cl_args.path); + + case IPC_STATE__OTHER_ERROR: + default: + return error("other error for '%s'", cl_args.path); + } +} + +/* + * Send an IPC command token to an already-running server daemon and + * print the response. + * + * This is a simple 1 word command/token that `test_app_cb()` (in the + * daemon process) will understand. + */ +static int client__send_ipc(void) +{ + const char *command = "(no-command)"; + struct strbuf buf = STRBUF_INIT; + struct ipc_client_connect_options options + = IPC_CLIENT_CONNECT_OPTIONS_INIT; + + if (cl_args.token && *cl_args.token) + command = cl_args.token; + + options.wait_if_busy = 1; + options.wait_if_not_found = 0; + + if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) { + if (buf.len) { + printf("%s\n", buf.buf); + fflush(stdout); + } + strbuf_release(&buf); + + return 0; + } + + return error("failed to send '%s' to '%s'", command, cl_args.path); +} + +/* + * Send an IPC command to an already-running server and ask it to + * shutdown. "send quit" is an async request and queues a shutdown + * event in the server, so we spin and wait here for it to actually + * shutdown to make the unit tests a little easier to write. + */ +static int client__stop_server(void) +{ + int ret; + time_t time_limit, now; + enum ipc_active_state s; + + time(&time_limit); + time_limit += cl_args.max_wait_sec; + + cl_args.token = "quit"; + + ret = client__send_ipc(); + if (ret) + return ret; + + for (;;) { + sleep_millisec(100); + + s = ipc_get_active_state(cl_args.path); + + if (s != IPC_STATE__LISTENING) { + /* + * The socket/pipe is gone and/or has stopped + * responding. Lets assume that the daemon + * process has exited too. + */ + return 0; + } + + time(&now); + if (now > time_limit) + return error(_("daemon has not shutdown yet")); + } +} + +/* + * Send an IPC command followed by ballast to confirm that a large + * message can be sent and that the kernel or pkt-line layers will + * properly chunk it and that the daemon receives the entire message. + */ +static int do_sendbytes(int bytecount, char byte, const char *path, + const struct ipc_client_connect_options *options) +{ + struct strbuf buf_send = STRBUF_INIT; + struct strbuf buf_resp = STRBUF_INIT; + + strbuf_addstr(&buf_send, "sendbytes "); + strbuf_addchars(&buf_send, byte, bytecount); + + if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) { + strbuf_rtrim(&buf_resp); + printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf); + fflush(stdout); + strbuf_release(&buf_send); + strbuf_release(&buf_resp); + + return 0; + } + + return error("client failed to sendbytes(%d, '%c') to '%s'", + bytecount, byte, path); +} + +/* + * Send an IPC command with ballast to an already-running server daemon. + */ +static int client__sendbytes(void) +{ + struct ipc_client_connect_options options + = IPC_CLIENT_CONNECT_OPTIONS_INIT; + + options.wait_if_busy = 1; + options.wait_if_not_found = 0; + options.uds_disallow_chdir = 0; + + return do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path, + &options); +} + +struct multiple_thread_data { + pthread_t pthread_id; + struct multiple_thread_data *next; + const char *path; + int bytecount; + int batchsize; + int sum_errors; + int sum_good; + char letter; +}; + +static void *multiple_thread_proc(void *_multiple_thread_data) +{ + struct multiple_thread_data *d = _multiple_thread_data; + int k; + struct ipc_client_connect_options options + = IPC_CLIENT_CONNECT_OPTIONS_INIT; + + options.wait_if_busy = 1; + options.wait_if_not_found = 0; + /* + * A multi-threaded client should not be randomly calling chdir(). + * The test will pass without this restriction because the test is + * not otherwise accessing the filesystem, but it makes us honest. + */ + options.uds_disallow_chdir = 1; + + trace2_thread_start("multiple"); + + for (k = 0; k < d->batchsize; k++) { + if (do_sendbytes(d->bytecount + k, d->letter, d->path, &options)) + d->sum_errors++; + else + d->sum_good++; + } + + trace2_thread_exit(); + return NULL; +} + +/* + * Start a client-side thread pool. Each thread sends a series of + * IPC requests. Each request is on a new connection to the server. + */ +static int client__multiple(void) +{ + struct multiple_thread_data *list = NULL; + int k; + int sum_join_errors = 0; + int sum_thread_errors = 0; + int sum_good = 0; + + for (k = 0; k < cl_args.nr_threads; k++) { + struct multiple_thread_data *d = xcalloc(1, sizeof(*d)); + d->next = list; + d->path = cl_args.path; + d->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26); + d->batchsize = cl_args.batchsize; + d->sum_errors = 0; + d->sum_good = 0; + d->letter = 'A' + (k % 26); + + if (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) { + warning("failed to create thread[%d] skipping remainder", k); + free(d); + break; + } + + list = d; + } + + while (list) { + struct multiple_thread_data *d = list; + + if (pthread_join(d->pthread_id, NULL)) + sum_join_errors++; + + sum_thread_errors += d->sum_errors; + sum_good += d->sum_good; + + list = d->next; + free(d); + } + + printf("client (good %d) (join %d), (errors %d)\n", + sum_good, sum_join_errors, sum_thread_errors); + + return (sum_join_errors + sum_thread_errors) ? 1 : 0; +} + +int cmd__simple_ipc(int argc, const char **argv) +{ + const char * const simple_ipc_usage[] = { + N_("test-helper simple-ipc is-active [<name>] [<options>]"), + N_("test-helper simple-ipc run-daemon [<name>] [<threads>]"), + N_("test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]"), + N_("test-helper simple-ipc stop-daemon [<name>] [<max-wait>]"), + N_("test-helper simple-ipc send [<name>] [<token>]"), + N_("test-helper simple-ipc sendbytes [<name>] [<bytecount>] [<byte>]"), + N_("test-helper simple-ipc multiple [<name>] [<threads>] [<bytecount>] [<batchsize>]"), + NULL + }; + + const char *bytevalue = NULL; + + struct option options[] = { +#ifndef GIT_WINDOWS_NATIVE + OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("name or pathname of unix domain socket")), +#else + OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("named-pipe name")), +#endif + OPT_INTEGER(0, "threads", &cl_args.nr_threads, N_("number of threads in server thread pool")), + OPT_INTEGER(0, "max-wait", &cl_args.max_wait_sec, N_("seconds to wait for daemon to start or stop")), + + OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")), + OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")), + + OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")), + OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")), + + OPT_END() + }; + + if (argc < 2) + usage_with_options(simple_ipc_usage, options); + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(simple_ipc_usage, options); + + if (argc == 2 && !strcmp(argv[1], "SUPPORTS_SIMPLE_IPC")) + return 0; + + cl_args.subcommand = argv[1]; + + argc--; + argv++; + + argc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0); + + if (cl_args.nr_threads < 1) + cl_args.nr_threads = 1; + if (cl_args.max_wait_sec < 0) + cl_args.max_wait_sec = 0; + if (cl_args.bytecount < 1) + cl_args.bytecount = 1; + if (cl_args.batchsize < 1) + cl_args.batchsize = 1; + + if (bytevalue && *bytevalue) + cl_args.bytevalue = bytevalue[0]; + + /* + * Use '!!' on all dispatch functions to map from `error()` style + * (returns -1) style to `test_must_fail` style (expects 1). This + * makes shell error messages less confusing. + */ + + if (!strcmp(cl_args.subcommand, "is-active")) + return !!client__probe_server(); + + if (!strcmp(cl_args.subcommand, "run-daemon")) + return !!daemon__run_server(); + + if (!strcmp(cl_args.subcommand, "start-daemon")) + return !!daemon__start_server(); + + /* + * Client commands follow. Ensure a server is running before + * sending any data. This might be overkill, but then again + * this is a test harness. + */ + + if (!strcmp(cl_args.subcommand, "stop-daemon")) { + if (client__probe_server()) + return 1; + return !!client__stop_server(); + } + + if (!strcmp(cl_args.subcommand, "send")) { + if (client__probe_server()) + return 1; + return !!client__send_ipc(); + } + + if (!strcmp(cl_args.subcommand, "sendbytes")) { + if (client__probe_server()) + return 1; + return !!client__sendbytes(); + } + + if (!strcmp(cl_args.subcommand, "multiple")) { + if (client__probe_server()) + return 1; + return !!client__multiple(); + } + + die("Unhandled subcommand: '%s'", cl_args.subcommand); +} +#endif diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index f97cd9f48a..25c6a37e93 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -15,6 +15,7 @@ struct test_cmd { static struct test_cmd cmds[] = { { "advise", cmd__advise_if_enabled }, + { "bitmap", cmd__bitmap }, { "bloom", cmd__bloom }, { "chmtime", cmd__chmtime }, { "config", cmd__config }, @@ -65,6 +66,7 @@ static struct test_cmd cmds[] = { { "sha1", cmd__sha1 }, { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, + { "simple-ipc", cmd__simple_ipc }, { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, { "submodule-config", cmd__submodule_config }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 28072c0ad5..f03c5988b2 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -5,6 +5,7 @@ #include "git-compat-util.h" int cmd__advise_if_enabled(int argc, const char **argv); +int cmd__bitmap(int argc, const char **argv); int cmd__bloom(int argc, const char **argv); int cmd__chmtime(int argc, const char **argv); int cmd__config(int argc, const char **argv); @@ -55,6 +56,7 @@ int cmd__sha1(int argc, const char **argv); int cmd__oid_array(int argc, const char **argv); int cmd__sha256(int argc, const char **argv); int cmd__sigchain(int argc, const char **argv); +int cmd__simple_ipc(int argc, const char **argv); int cmd__strcmp_offset(int argc, const char **argv); int cmd__string_list(int argc, const char **argv); int cmd__submodule_config(int argc, const char **argv); diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index b3e725f031..452be01056 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -15,6 +15,12 @@ test_expect_success 'setup bitmap config' ' git config pack.writebitmaps true ' +# we need to create the tag up front such that it is covered by the repack and +# thus by generated bitmaps. +test_expect_success 'create tags' ' + git tag --message="tag pointing to HEAD" perf-tag HEAD +' + test_perf 'repack to disk' ' git repack -ad ' @@ -43,6 +49,14 @@ test_perf 'rev-list (objects)' ' git rev-list --all --use-bitmap-index --objects >/dev/null ' +test_perf 'rev-list with tag negated via --not --all (objects)' ' + git rev-list perf-tag --not --all --use-bitmap-index --objects >/dev/null +' + +test_perf 'rev-list with negative tag (objects)' ' + git rev-list HEAD --not perf-tag --use-bitmap-index --objects >/dev/null +' + test_perf 'rev-list count with blob:none' ' git rev-list --use-bitmap-index --count --objects --all \ --filter=blob:none >/dev/null diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh new file mode 100755 index 0000000000..ff98be31a5 --- /dev/null +++ b/t/t0052-simple-ipc.sh @@ -0,0 +1,122 @@ +#!/bin/sh + +test_description='simple command server' + +. ./test-lib.sh + +test-tool simple-ipc SUPPORTS_SIMPLE_IPC || { + skip_all='simple IPC not supported on this platform' + test_done +} + +stop_simple_IPC_server () { + test-tool simple-ipc stop-daemon +} + +test_expect_success 'start simple command server' ' + test_atexit stop_simple_IPC_server && + test-tool simple-ipc start-daemon --threads=8 && + test-tool simple-ipc is-active +' + +test_expect_success 'simple command server' ' + test-tool simple-ipc send --token=ping >actual && + echo pong >expect && + test_cmp expect actual +' + +test_expect_success 'servers cannot share the same path' ' + test_must_fail test-tool simple-ipc run-daemon && + test-tool simple-ipc is-active +' + +test_expect_success 'big response' ' + test-tool simple-ipc send --token=big >actual && + test_line_count -ge 10000 actual && + grep -q "big: [0]*9999\$" actual +' + +test_expect_success 'chunk response' ' + test-tool simple-ipc send --token=chunk >actual && + test_line_count -ge 10000 actual && + grep -q "big: [0]*9999\$" actual +' + +test_expect_success 'slow response' ' + test-tool simple-ipc send --token=slow >actual && + test_line_count -ge 100 actual && + grep -q "big: [0]*99\$" actual +' + +# Send an IPC with n=100,000 bytes of ballast. This should be large enough +# to force both the kernel and the pkt-line layer to chunk the message to the +# daemon and for the daemon to receive it in chunks. +# +test_expect_success 'sendbytes' ' + test-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual && + grep "sent:A00100000 rcvd:A00100000" actual +' + +# Start a series of <threads> client threads that each make <batchsize> +# IPC requests to the server. Each (<threads> * <batchsize>) request +# will open a new connection to the server and randomly bind to a server +# thread. Each client thread exits after completing its batch. So the +# total number of live client threads will be smaller than the total. +# Each request will send a message containing at least <bytecount> bytes +# of ballast. (Responses are small.) +# +# The purpose here is to test threading in the server and responding to +# many concurrent client requests (regardless of whether they come from +# 1 client process or many). And to test that the server side of the +# named pipe/socket is stable. (On Windows this means that the server +# pipe is properly recycled.) +# +# On Windows it also lets us adjust the connection timeout in the +# `ipc_client_send_command()`. +# +# Note it is easy to drive the system into failure by requesting an +# insane number of threads on client or server and/or increasing the +# per-thread batchsize or the per-request bytecount (ballast). +# On Windows these failures look like "pipe is busy" errors. +# So I've chosen fairly conservative values for now. +# +# We expect output of the form "sent:<letter><length> ..." +# With terms (7, 19, 13) we expect: +# <letter> in [A-G] +# <length> in [19+0 .. 19+(13-1)] +# and (7 * 13) successful responses. +# +test_expect_success 'stress test threads' ' + test-tool simple-ipc multiple \ + --threads=7 \ + --bytecount=19 \ + --batchsize=13 \ + >actual && + test_line_count = 92 actual && + grep "good 91" actual && + grep "sent:A" <actual >actual_a && + cat >expect_a <<-EOF && + sent:A00000019 rcvd:A00000019 + sent:A00000020 rcvd:A00000020 + sent:A00000021 rcvd:A00000021 + sent:A00000022 rcvd:A00000022 + sent:A00000023 rcvd:A00000023 + sent:A00000024 rcvd:A00000024 + sent:A00000025 rcvd:A00000025 + sent:A00000026 rcvd:A00000026 + sent:A00000027 rcvd:A00000027 + sent:A00000028 rcvd:A00000028 + sent:A00000029 rcvd:A00000029 + sent:A00000030 rcvd:A00000030 + sent:A00000031 rcvd:A00000031 + EOF + test_cmp expect_a actual_a +' + +test_expect_success 'stop-daemon works' ' + test-tool simple-ipc stop-daemon && + test_must_fail test-tool simple-ipc is-active && + test_must_fail test-tool simple-ipc send --token=ping +' + +test_done diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index 1b26c4c2ef..e30bc48a29 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -521,6 +521,30 @@ test_expect_success 'format-patch --range-diff as commentary' ' grep "> 1: .* new message" 0001-* ' +test_expect_success 'format-patch --range-diff reroll-count with a non-integer' ' + git format-patch --range-diff=HEAD~1 -v2.9 HEAD~1 >actual && + test_when_finished "rm v2.9-0001-*" && + test_line_count = 1 actual && + test_i18ngrep "^Range-diff:$" v2.9-0001-* && + grep "> 1: .* new message" v2.9-0001-* +' + +test_expect_success 'format-patch --range-diff reroll-count with a integer' ' + git format-patch --range-diff=HEAD~1 -v2 HEAD~1 >actual && + test_when_finished "rm v2-0001-*" && + test_line_count = 1 actual && + test_i18ngrep "^Range-diff ..* v1:$" v2-0001-* && + grep "> 1: .* new message" v2-0001-* +' + +test_expect_success 'format-patch --range-diff with v0' ' + git format-patch --range-diff=HEAD~1 -v0 HEAD~1 >actual && + test_when_finished "rm v0-0001-*" && + test_line_count = 1 actual && + test_i18ngrep "^Range-diff:$" v0-0001-* && + grep "> 1: .* new message" v0-0001-* +' + test_expect_success 'range-diff overrides diff.noprefix internally' ' git -c diff.noprefix=true range-diff HEAD^... ' diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index b76cb6de91..49010aa946 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -65,7 +65,7 @@ test_expect_success 'cherry-pick persists opts correctly' ' # gets interrupted, use a high-enough number that is larger # than the number of parents of any commit we have created mainline=4 && - test_expect_code 128 git cherry-pick -s -m $mainline --strategy=recursive -X patience -X ours initial..anotherpick && + test_expect_code 128 git cherry-pick -s -m $mainline --strategy=recursive -X patience -X ours --edit initial..anotherpick && test_path_is_dir .git/sequencer && test_path_is_file .git/sequencer/head && test_path_is_file .git/sequencer/todo && @@ -84,6 +84,36 @@ test_expect_success 'cherry-pick persists opts correctly' ' ours EOF git config --file=.git/sequencer/opts --get-all options.strategy-option >actual && + test_cmp expect actual && + echo "true" >expect && + git config --file=.git/sequencer/opts --get-all options.edit >actual && + test_cmp expect actual +' + +test_expect_success 'revert persists opts correctly' ' + pristine_detach initial && + # to make sure that the session to revert a sequence + # gets interrupted, revert commits that are not in the history + # of HEAD. + test_expect_code 1 git revert -s --strategy=recursive -X patience -X ours --no-edit picked yetanotherpick && + test_path_is_dir .git/sequencer && + test_path_is_file .git/sequencer/head && + test_path_is_file .git/sequencer/todo && + test_path_is_file .git/sequencer/opts && + echo "true" >expect && + git config --file=.git/sequencer/opts --get-all options.signoff >actual && + test_cmp expect actual && + echo "recursive" >expect && + git config --file=.git/sequencer/opts --get-all options.strategy >actual && + test_cmp expect actual && + cat >expect <<-\EOF && + patience + ours + EOF + git config --file=.git/sequencer/opts --get-all options.strategy-option >actual && + test_cmp expect actual && + echo "false" >expect && + git config --file=.git/sequencer/opts --get-all options.edit >actual && test_cmp expect actual ' diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index cdd3154e70..712d4b5ddf 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -386,6 +386,30 @@ test_expect_success 'reroll count (-v)' ' ! grep -v "^Subject: \[PATCH v4 [0-3]/3\] " subjects ' +test_expect_success 'reroll count (-v) with a fractional number' ' + rm -fr patches && + git format-patch -o patches --cover-letter -v4.4 main..side >list && + ! grep -v "^patches/v4.4-000[0-3]-" list && + sed -n -e "/^Subject: /p" $(cat list) >subjects && + ! grep -v "^Subject: \[PATCH v4.4 [0-3]/3\] " subjects +' + +test_expect_success 'reroll (-v) count with a non number' ' + rm -fr patches && + git format-patch -o patches --cover-letter -v4rev2 main..side >list && + ! grep -v "^patches/v4rev2-000[0-3]-" list && + sed -n -e "/^Subject: /p" $(cat list) >subjects && + ! grep -v "^Subject: \[PATCH v4rev2 [0-3]/3\] " subjects +' + +test_expect_success 'reroll (-v) count with a non-pathname character' ' + rm -fr patches && + git format-patch -o patches --cover-letter -v4---..././../--1/.2// main..side >list && + ! grep -v "patches/v4-\.-\.-\.-1-\.2-000[0-3]-" list && + sed -n -e "/^Subject: /p" $(cat list) >subjects && + ! grep -v "^Subject: \[PATCH v4---\.\.\./\./\.\./--1/\.2// [0-3]/3\] " subjects +' + check_threading () { expect="$1" && shift && @@ -2255,6 +2279,16 @@ test_expect_success 'interdiff: reroll-count' ' test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch ' +test_expect_success 'interdiff: reroll-count with a non-integer' ' + git format-patch --cover-letter --interdiff=boop~2 -v2.2 -1 boop && + test_i18ngrep "^Interdiff:$" v2.2-0000-cover-letter.patch +' + +test_expect_success 'interdiff: reroll-count with a integer' ' + git format-patch --cover-letter --interdiff=boop~2 -v2 -1 boop && + test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch +' + test_expect_success 'interdiff: solo-patch' ' cat >expect <<-\EOF && +fleep diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 40b9f63244..f53efc8229 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -554,4 +554,42 @@ test_expect_success 'fetch with bitmaps can reuse old base' ' ) ' +test_expect_success 'pack.preferBitmapTips' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + # create enough commits that not all are receive bitmap + # coverage even if they are all at the tip of some reference. + test_commit_bulk --message="%s" 103 && + + git rev-list HEAD >commits.raw && + sort <commits.raw >commits && + + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin <refs && + + git repack -adb && + test-tool bitmap list-commits | sort >bitmaps && + + # remember which commits did not receive bitmaps + comm -13 bitmaps commits >before && + test_file_not_empty before && + + # mark the commits which did not receive bitmaps as preferred, + # and generate the bitmap again + perl -pe "s{^}{create refs/tags/include/$. }" <before | + git update-ref --stdin && + git -c pack.preferBitmapTips=refs/tags/include repack -adb && + + # finally, check that the commit(s) without bitmap coverage + # are not the same ones as before + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >after && + + ! test_cmp before after + ) +' + test_done diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index b4afab1dfc..5641d158df 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -234,6 +234,48 @@ test_expect_success 'warn on improper hash version' ' ) ' +test_expect_success 'midx picks objects from preferred pack' ' + test_when_finished rm -rf preferred.git && + git init --bare preferred.git && + ( + cd preferred.git && + + a=$(echo "a" | git hash-object -w --stdin) && + b=$(echo "b" | git hash-object -w --stdin) && + c=$(echo "c" | git hash-object -w --stdin) && + + # Set up two packs, duplicating the object "B" at different + # offsets. + # + # Note that the "BC" pack (the one we choose as preferred) sorts + # lexically after the "AB" pack, meaning that omitting the + # --preferred-pack argument would cause this test to fail (since + # the MIDX code would select the copy of "b" in the "AB" pack). + git pack-objects objects/pack/test-AB <<-EOF && + $a + $b + EOF + bc=$(git pack-objects objects/pack/test-BC <<-EOF + $b + $c + EOF + ) && + + git multi-pack-index --object-dir=objects \ + write --preferred-pack=test-BC-$bc.idx 2>err && + test_must_be_empty err && + + test-tool read-midx --show-objects objects >out && + + ofs=$(git show-index <objects/pack/test-BC-$bc.idx | grep $b | + cut -d" " -f1) && + printf "%s %s\tobjects/pack/test-BC-%s.pack\n" \ + "$b" "$ofs" "$bc" >expect && + grep ^$b out >actual && + + test_cmp expect actual + ) +' test_expect_success 'verify multi-pack-index success' ' git multi-pack-index verify --object-dir=$objdir diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index e7e6c08955..329ae599fd 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -759,6 +759,15 @@ test_expect_success 'partial clone using HTTP' ' partial_clone "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server" ' +test_expect_success 'reject cloning shallow repository using HTTP' ' + test_when_finished "rm -rf repo" && + git clone --bare --no-local --depth=1 src "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + test_must_fail git clone --reject-shallow $HTTPD_URL/smart/repo.git repo 2>err && + test_i18ngrep -e "source repository is shallow, reject to clone." err && + + git clone --no-reject-shallow $HTTPD_URL/smart/repo.git repo +' + # DO NOT add non-httpd-specific tests here, because the last part of this # test script is only executed when httpd is available and enabled. diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh index 428b0aac93..3a595c0f82 100755 --- a/t/t5606-clone-options.sh +++ b/t/t5606-clone-options.sh @@ -11,7 +11,8 @@ test_expect_success 'setup' ' mkdir parent && (cd parent && git init && echo one >file && git add file && - git commit -m one) + git commit -m one) && + git clone --depth=1 --no-local parent shallow-repo ' @@ -45,6 +46,30 @@ test_expect_success 'disallows --bare with --separate-git-dir' ' ' +test_expect_success 'reject cloning shallow repository' ' + test_when_finished "rm -rf repo" && + test_must_fail git clone --reject-shallow shallow-repo out 2>err && + test_i18ngrep -e "source repository is shallow, reject to clone." err && + + git clone --no-reject-shallow shallow-repo repo +' + +test_expect_success 'reject cloning non-local shallow repository' ' + test_when_finished "rm -rf repo" && + test_must_fail git clone --reject-shallow --no-local shallow-repo out 2>err && + test_i18ngrep -e "source repository is shallow, reject to clone." err && + + git clone --no-reject-shallow --no-local shallow-repo repo +' + +test_expect_success 'succeed cloning normal repository' ' + test_when_finished "rm -rf chilad1 child2 child3 child4 " && + git clone --reject-shallow parent child1 && + git clone --reject-shallow --no-local parent child2 && + git clone --no-reject-shallow parent child3 && + git clone --no-reject-shallow --no-local parent child4 +' + test_expect_success 'uses "origin" for default remote name' ' git clone parent clone-default-origin && diff --git a/t/t5611-clone-config.sh b/t/t5611-clone-config.sh index 9f555b87ec..f8625f9158 100755 --- a/t/t5611-clone-config.sh +++ b/t/t5611-clone-config.sh @@ -95,6 +95,31 @@ test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' ' test_cmp expect actual ' +test_expect_success 'set up shallow repository' ' + git clone --depth=1 --no-local . shallow-repo +' + +test_expect_success 'clone.rejectshallow=true should reject cloning shallow repo' ' + test_when_finished "rm -rf out" && + test_must_fail git -c clone.rejectshallow=true clone --no-local shallow-repo out 2>err && + test_i18ngrep -e "source repository is shallow, reject to clone." err && + + git -c clone.rejectshallow=false clone --no-local shallow-repo out +' + +test_expect_success 'option --[no-]reject-shallow override clone.rejectshallow config' ' + test_when_finished "rm -rf out" && + test_must_fail git -c clone.rejectshallow=false clone --reject-shallow --no-local shallow-repo out 2>err && + test_i18ngrep -e "source repository is shallow, reject to clone." err && + + git -c clone.rejectshallow=true clone --no-reject-shallow --no-local shallow-repo out +' + +test_expect_success 'clone.rejectshallow=true should succeed cloning normal repo' ' + test_when_finished "rm -rf out" && + git -c clone.rejectshallow=true clone --no-local . out +' + test_expect_success MINGW 'clone -c core.hideDotFiles' ' test_commit attributes .gitattributes "" && rm -rf child && diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index cac7f443d0..2e7c32d50c 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -1134,4 +1134,14 @@ test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' ' test_cmp expect actual ' +test_expect_success 'for-each-ref reports broken tags' ' + git tag -m "good tag" broken-tag-good HEAD && + git cat-file tag broken-tag-good >good && + sed s/commit/blob/ <good >bad && + bad=$(git hash-object -w -t tag bad) && + git update-ref refs/tags/broken-tag-bad $bad && + test_must_fail git for-each-ref --format="%(*objectname)" \ + refs/tags/broken-tag-* +' + test_done diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index 5d3b711fe6..379aac0103 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -4895,6 +4895,77 @@ test_expect_merge_algorithm failure success '12f: Trivial directory resolve, cac ) ' +# Testcase 12g, Testcase with two kinds of "relevant" renames +# Commit O: somefile_O, subdir/{a_O,b_O} +# Commit A: somefile_A, subdir/{a_O,b_O,c_A} +# Commit B: newfile_B, newdir/{a_B,b_B} +# Expected: newfile_{merged}, newdir/{a_B,b_B,c_A} + +test_setup_12g () { + test_create_repo 12g && + ( + cd 12g && + + mkdir -p subdir && + test_write_lines upon a time there was a >somefile && + test_write_lines 1 2 3 4 5 6 7 8 9 10 >subdir/a && + test_write_lines one two three four five six >subdir/b && + git add . && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + test_write_lines once upon a time there was a >somefile && + > subdir/c && + git add somefile subdir/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv somefile newfile && + git mv subdir newdir && + echo repo >>newfile && + test_write_lines 1 2 3 4 5 6 7 8 9 10 11 >newdir/a && + test_write_lines one two three four five six seven >newdir/b && + git add newfile newdir && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '12g: Testcase with two kinds of "relevant" renames' ' + test_setup_12g && + ( + cd 12g && + + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 && + + test_write_lines once upon a time there was a repo >expect && + test_cmp expect newfile && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:newdir/a HEAD:newdir/b HEAD:newdir/c && + git rev-parse >expect \ + B:newdir/a B:newdir/b A:subdir/c && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:subdir/a && + test_must_fail git rev-parse HEAD:subdir/b && + test_must_fail git rev-parse HEAD:subdir/c && + test_path_is_missing subdir/ && + test_path_is_file newdir/c + ) +' + ########################################################################### # SECTION 13: Checking informational and conflict messages # diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh index 6396897cc8..38a532d81c 100755 --- a/t/t7502-commit-porcelain.sh +++ b/t/t7502-commit-porcelain.sh @@ -38,6 +38,16 @@ check_summary_oneline() { test_cmp exp act } +trailer_commit_base () { + echo "fun" >>file && + git add file && + git commit -s --trailer "Signed-off-by=C1 E1 " \ + --trailer "Helped-by:C2 E2 " \ + --trailer "Reported-by=C3 E3" \ + --trailer "Mentored-by:C4 E4" \ + -m "hello" +} + test_expect_success 'output summary format' ' echo new >file1 && @@ -154,6 +164,308 @@ test_expect_success 'sign off' ' ' +test_expect_success 'commit --trailer with "="' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + EOF + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "replace" as ifexists' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Helped-by: C3 E3 + EOF + git -c trailer.ifexists="replace" \ + commit --trailer "Mentored-by: C4 E4" \ + --trailer "Helped-by: C3 E3" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "add" as ifexists' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Reported-by: C3 E3 + Mentored-by: C4 E4 + EOF + git -c trailer.ifexists="add" \ + commit --trailer "Reported-by: C3 E3" \ + --trailer "Mentored-by: C4 E4" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "donothing" as ifexists' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Reviewed-by: C6 E6 + EOF + git -c trailer.ifexists="donothing" \ + commit --trailer "Mentored-by: C5 E5" \ + --trailer "Reviewed-by: C6 E6" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Mentored-by: C5 E5 + EOF + git -c trailer.ifexists="addIfDifferent" \ + commit --trailer "Reported-by: C3 E3" \ + --trailer "Mentored-by: C5 E5" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Reported-by: C3 E3 + EOF + git -c trailer.ifexists="addIfDifferentNeighbor" \ + commit --trailer "Mentored-by: C4 E4" \ + --trailer "Reported-by: C3 E3" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "end" as where' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Reported-by: C3 E3 + Mentored-by: C4 E4 + EOF + git -c trailer.where="end" \ + commit --trailer "Reported-by: C3 E3" \ + --trailer "Mentored-by: C4 E4" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "start" as where' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C1 E1 + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + EOF + git -c trailer.where="start" \ + commit --trailer "Signed-off-by: C O Mitter <committer@example.com>" \ + --trailer "Signed-off-by: C1 E1" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "after" as where' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Mentored-by: C5 E5 + EOF + git -c trailer.where="after" \ + commit --trailer "Mentored-by: C4 E4" \ + --trailer "Mentored-by: C5 E5" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "before" as where' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C2 E2 + Mentored-by: C3 E3 + Mentored-by: C4 E4 + EOF + git -c trailer.where="before" \ + commit --trailer "Mentored-by: C3 E3" \ + --trailer "Mentored-by: C2 E2" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Helped-by: C5 E5 + EOF + git -c trailer.ifmissing="donothing" \ + commit --trailer "Helped-by: C5 E5" \ + --trailer "Based-by: C6 E6" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and "add" as ifmissing' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Reported-by: C3 E3 + Mentored-by: C4 E4 + Helped-by: C5 E5 + Based-by: C6 E6 + EOF + git -c trailer.ifmissing="add" \ + commit --trailer "Helped-by: C5 E5" \ + --trailer "Based-by: C6 E6" \ + --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c ack.key ' ' + echo "fun" >>file1 && + git add file1 && + cat >expected <<-\EOF && + hello + + Acked-by: Peff + EOF + git -c trailer.ack.key="Acked-by" \ + commit --trailer "ack = Peff" -m "hello" && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and ":=#" as separators' ' + echo "fun" >>file1 && + git add file1 && + cat >expected <<-\EOF && + I hate bug + + Bug #42 + EOF + git -c trailer.separators=":=#" \ + -c trailer.bug.key="Bug #" \ + commit --trailer "bug = 42" -m "I hate bug" && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + +test_expect_success 'commit --trailer with -c and command' ' + trailer_commit_base && + cat >expected <<-\EOF && + hello + + Signed-off-by: C O Mitter <committer@example.com> + Signed-off-by: C1 E1 + Helped-by: C2 E2 + Mentored-by: C4 E4 + Reported-by: A U Thor <author@example.com> + EOF + git -c trailer.report.key="Reported-by: " \ + -c trailer.report.ifexists="replace" \ + -c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \ + git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \ + commit --trailer "report = author" --amend && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && |