diff --git a/Documentation/git-rerere.adoc b/Documentation/git-rerere.adoc index 4e6ab9a27c..e04e9ef3ef 100644 --- a/Documentation/git-rerere.adoc +++ b/Documentation/git-rerere.adoc @@ -8,7 +8,13 @@ git-rerere - Reuse recorded resolution of conflicted merges SYNOPSIS -------- [verse] -'git rerere' [clear | forget ... | diff | status | remaining | gc] +'git rerere' [--[no-]rerere-autoupdate] +'git rerere' clear +'git rerere' diff +'git rerere' forget ... +'git rerere' gc +'git rerere' remaining +'git rerere' status DESCRIPTION ----------- diff --git a/apply.c b/apply.c index a0b7119c63..646fabeafa 100644 --- a/apply.c +++ b/apply.c @@ -4866,7 +4866,7 @@ static int write_out_results(struct apply_state *state, struct patch *list) * tree with conflict markers, but that isn't written with --cached. */ if (!state->cached) - repo_rerere(state->repo, 0); + rerere(state->repo, 0); } return errs; diff --git a/builtin/am.c b/builtin/am.c index e9623b8307..4a5b37bee0 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1649,7 +1649,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa o.verbosity = 0; if (merge_ort_generic(&o, &our_tree, &their_tree, 1, bases, &result)) { - repo_rerere(the_repository, state->allow_rerere_autoupdate); + rerere(the_repository, state->allow_rerere_autoupdate); free(their_tree_name); return error(_("Failed to merge in the changes.")); } @@ -1979,7 +1979,7 @@ static void am_resolve(struct am_state *state, int allow_empty) goto next; } - repo_rerere(the_repository, 0); + rerere(the_repository, 0); do_commit(state); diff --git a/builtin/commit.c b/builtin/commit.c index 5eedd371ac..b52c60d093 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1957,7 +1957,7 @@ int cmd_commit(int argc, git_test_write_commit_graph_or_die(the_repository); - repo_rerere(the_repository, 0); + rerere(the_repository, 0); run_auto_maintenance(the_repository, quiet); run_commit_hook(use_editor, repo_get_index_file(the_repository), NULL, "post-commit", NULL); diff --git a/builtin/merge.c b/builtin/merge.c index 43f306e768..44a07c41f5 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1061,7 +1061,7 @@ static int suggest_conflicts(void) fputs(msgbuf.buf, fp); strbuf_release(&msgbuf); fclose(fp); - repo_rerere(the_repository, allow_rerere_auto); + rerere(the_repository, allow_rerere_auto); printf(_("Automatic merge failed; " "fix conflicts and then commit the result.\n")); return 1; diff --git a/builtin/rerere.c b/builtin/rerere.c index 2c17f02f56..051e50838c 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "builtin.h" #include "config.h" #include "gettext.h" @@ -7,14 +5,18 @@ #include "rerere.h" #include "strbuf.h" #include "string-list.h" +#include "strvec.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" #include "pathspec.h" -static const char * const rerere_usage[] = { - N_("git rerere [clear | forget ... | diff | status | remaining | gc]"), - NULL, -}; +#define RERERE_USAGE N_("git rerere [--[no-]rerere-autoupdate]") +#define RERERE_CLEAR_USAGE N_("git rerere clear") +#define RERERE_DIFF_USAGE N_("git rerere diff") +#define RERERE_FORGET_USAGE N_("git rerere forget ...") +#define RERERE_GC_USAGE N_("git rerere gc") +#define RERERE_REMAINING_USAGE N_("git rerere remaining") +#define RERERE_STATUS_USAGE N_("git rerere status") static int outf(void *dummy UNUSED, mmfile_t *ptr, int nbuf) { @@ -54,98 +56,265 @@ static int diff_two(const char *file1, const char *label1, return ret; } -int cmd_rerere(int argc, - const char **argv, - const char *prefix, - struct repository *repo UNUSED) +static int cmd_rerere_clear(int argc, + const char **argv, + const char *prefix, + struct repository *repo) { + struct option options[] = { + OPT_END(), + }; + const char * const rerere_clear_usage[] = { + RERERE_CLEAR_USAGE, + NULL, + }; struct string_list merge_rr = STRING_LIST_INIT_DUP; - int autoupdate = -1, skip_locked = 0, flags = 0; + argc = parse_options(argc, argv, prefix, options, rerere_clear_usage, 0); + if (argc) + usage(_("too many arguments")); + + rerere_clear(repo, &merge_rr); + + string_list_clear(&merge_rr, 1); + return 0; +} + +static int cmd_rerere_diff(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + struct option options[] = { + OPT_END(), + }; + const char * const rerere_diff_usage[] = { + RERERE_DIFF_USAGE, + NULL, + }; + struct string_list merge_rr = STRING_LIST_INIT_DUP; + struct strbuf buf = STRBUF_INIT; + struct rerere_cache cache; + + argc = parse_options(argc, argv, prefix, options, rerere_diff_usage, 0); + if (argc) + usage(_("too many arguments")); + + if (rerere_cache_setup(&cache, repo, &merge_rr, + RERERE_READONLY) < 0) + return 0; + + for (size_t i = 0; i < merge_rr.nr; i++) { + const char *path = merge_rr.items[i].string; + const struct rerere_id *id = merge_rr.items[i].util; + if (diff_two(rerere_path(repo, &buf, id, "preimage"), path, path, path)) + die(_("unable to generate diff for '%s'"), + rerere_path(repo, &buf, id, NULL)); + } + + string_list_clear(&merge_rr, 1); + rerere_cache_release(&cache); + strbuf_release(&buf); + return 0; +} + +static int cmd_rerere_forget(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + struct option options[] = { + OPT_END(), + }; + const char * const rerere_forget_usage[] = { + RERERE_FORGET_USAGE, + NULL, + }; + struct pathspec pathspec; + int ret; + + argc = parse_options(argc, argv, prefix, options, rerere_forget_usage, 0); + if (!argc) + warning(_("'git rerere forget' without paths is deprecated")); + parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv); + + ret = rerere_forget(repo, &pathspec); + + clear_pathspec(&pathspec); + return ret; +} + +static int cmd_rerere_gc(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + int skip_locked = 0; + struct option options[] = { + OPT_HIDDEN_BOOL(0, "skip-locked", &skip_locked, + N_("skip gc while another process holds the lock")), + OPT_END(), + }; + const char * const rerere_gc_usage[] = { + RERERE_GC_USAGE, + NULL, + }; + struct string_list merge_rr = STRING_LIST_INIT_DUP; + + argc = parse_options(argc, argv, prefix, options, rerere_gc_usage, 0); + if (argc) + usage(_("too many arguments")); + + rerere_gc(repo, &merge_rr, + skip_locked ? RERERE_NOWAIT : 0); + + string_list_clear(&merge_rr, 1); + return 0; +} + +static int cmd_rerere_perform(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + int autoupdate = -1; struct option options[] = { OPT_SET_INT(0, "rerere-autoupdate", &autoupdate, N_("register clean resolutions in index"), 1), - OPT_HIDDEN_BOOL(0, "skip-locked", &skip_locked, - N_("skip gc while another process holds the lock")), OPT_END(), }; + const char * const rerere_usage[] = { + RERERE_USAGE, + NULL, + }; + enum rerere_flags flags = 0; argc = parse_options(argc, argv, prefix, options, rerere_usage, 0); - - if (skip_locked && (argc < 1 || strcmp(argv[0], "gc"))) - die(_("the option '%s' requires '%s'"), "--skip-locked", "gc"); - - repo_config(the_repository, git_xmerge_config, NULL); + if (argc) + usage(_("too many arguments")); if (autoupdate == 1) flags = RERERE_AUTOUPDATE; if (autoupdate == 0) flags = RERERE_NOAUTOUPDATE; - if (argc < 1) - return repo_rerere(the_repository, flags); + return rerere(repo, flags); +} - if (!strcmp(argv[0], "forget")) { - struct pathspec pathspec; - int ret; +static int cmd_rerere_remaining(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + struct option options[] = { + OPT_END(), + }; + const char * const rerere_remaining_usage[] = { + RERERE_REMAINING_USAGE, + NULL, + }; + struct string_list merge_rr = STRING_LIST_INIT_DUP; - if (argc < 2) - warning(_("'git rerere forget' without paths is deprecated")); - parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, - prefix, argv + 1); + argc = parse_options(argc, argv, prefix, options, rerere_remaining_usage, 0); + if (argc) + usage(_("too many arguments")); - ret = rerere_forget(the_repository, &pathspec); - - clear_pathspec(&pathspec); - return ret; - } - - if (!strcmp(argv[0], "clear")) { - rerere_clear(the_repository, &merge_rr); - } else if (!strcmp(argv[0], "gc")) - rerere_gc(the_repository, &merge_rr, - skip_locked ? RERERE_NOWAIT : 0); - else if (!strcmp(argv[0], "status")) { - struct rerere_cache cache; - - if (rerere_cache_setup(&cache, the_repository, &merge_rr, - flags | RERERE_READONLY) < 0) - return 0; - for (size_t i = 0; i < merge_rr.nr; i++) + rerere_remaining(repo, &merge_rr); + for (size_t i = 0; i < merge_rr.nr; i++) { + if (merge_rr.items[i].util != RERERE_RESOLVED) printf("%s\n", merge_rr.items[i].string); - - rerere_cache_release(&cache); - } else if (!strcmp(argv[0], "remaining")) { - rerere_remaining(the_repository, &merge_rr); - for (size_t i = 0; i < merge_rr.nr; i++) { - if (merge_rr.items[i].util != RERERE_RESOLVED) - printf("%s\n", merge_rr.items[i].string); - else - /* prepare for later call to - * string_list_clear() */ - merge_rr.items[i].util = NULL; - } - } else if (!strcmp(argv[0], "diff")) { - struct strbuf buf = STRBUF_INIT; - struct rerere_cache cache; - - if (rerere_cache_setup(&cache, the_repository, &merge_rr, - flags | RERERE_READONLY) < 0) - return 0; - for (size_t i = 0; i < merge_rr.nr; i++) { - const char *path = merge_rr.items[i].string; - const struct rerere_id *id = merge_rr.items[i].util; - if (diff_two(rerere_path(the_repository, &buf, id, "preimage"), - path, path, path)) - die(_("unable to generate diff for '%s'"), - rerere_path(the_repository, &buf, id, NULL)); - } - - rerere_cache_release(&cache); - strbuf_release(&buf); - } else - usage_with_options(rerere_usage, options); + else + /* prepare for later call to + * string_list_clear() */ + merge_rr.items[i].util = NULL; + } string_list_clear(&merge_rr, 1); return 0; } + +static int cmd_rerere_status(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + struct option options[] = { + OPT_END(), + }; + const char * const rerere_status_usage[] = { + RERERE_STATUS_USAGE, + NULL, + }; + struct string_list merge_rr = STRING_LIST_INIT_DUP; + struct rerere_cache cache; + + argc = parse_options(argc, argv, prefix, options, rerere_status_usage, 0); + if (argc) + usage(_("too many arguments")); + + if (rerere_cache_setup(&cache, repo, &merge_rr, + RERERE_READONLY) < 0) + return 0; + for (size_t i = 0; i < merge_rr.nr; i++) + printf("%s\n", merge_rr.items[i].string); + + string_list_clear(&merge_rr, 1); + rerere_cache_release(&cache); + return 0; +} + +int cmd_rerere(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + parse_opt_subcommand_fn *fn = NULL; + struct option options[] = { + OPT_SUBCOMMAND("clear", &fn, cmd_rerere_clear), + OPT_SUBCOMMAND("diff", &fn, cmd_rerere_diff), + OPT_SUBCOMMAND("forget", &fn, cmd_rerere_forget), + OPT_SUBCOMMAND("gc", &fn, cmd_rerere_gc), + OPT_SUBCOMMAND("remaining", &fn, cmd_rerere_remaining), + OPT_SUBCOMMAND("status", &fn, cmd_rerere_status), + OPT_END(), + }; + const char * const rerere_usage[] = { + RERERE_USAGE, + RERERE_CLEAR_USAGE, + RERERE_DIFF_USAGE, + RERERE_FORGET_USAGE, + RERERE_GC_USAGE, + RERERE_REMAINING_USAGE, + RERERE_STATUS_USAGE, + NULL, + }; + + argc = parse_options(argc, argv, prefix, options, rerere_usage, + PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_SUBCOMMAND_OPTIONAL); + repo_config(repo, git_xmerge_config, NULL); + + if (!fn) { + struct strvec args = STRVEC_INIT; + const char **args_copy; + int ret; + + strvec_push(&args, "perform"); + strvec_pushv(&args, argv); + + /* + * We need to copy the array as it will be modified by + * `parse_options()`, and if so we leak the strings + * referenced by `args`. + */ + CALLOC_ARRAY(args_copy, args.nr); + COPY_ARRAY(args_copy, args.v, args.nr); + + ret = cmd_rerere_perform(args.nr, args_copy, prefix, repo); + + strvec_clear(&args); + free(args_copy); + return ret; + } + + return fn(argc, argv, prefix, repo); +} diff --git a/builtin/stash.c b/builtin/stash.c index ec1462f7a2..9d35ae9881 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -680,7 +680,7 @@ static enum stash_apply_result do_apply_stash(const char *prefix, ret = error(_("could not write index")); if (ret) { - repo_rerere(the_repository, 0); + rerere(the_repository, 0); if (index) fprintf_ln(stderr, _("Index was not unstashed.")); diff --git a/rerere.c b/rerere.c index e089761208..3652b7ac2d 100644 --- a/rerere.c +++ b/rerere.c @@ -908,7 +908,7 @@ out: int rerere_cache_setup(struct rerere_cache *cache, struct repository *r, struct string_list *merge_rr, - int flags) + enum rerere_flags flags) { int lock_timeout_ms = 1000; int fd; @@ -951,12 +951,7 @@ int rerere_cache_setup(struct rerere_cache *cache, return fd; } -/* - * The main entry point that is called internally from codepaths that - * perform mergy operations, possibly leaving conflicted index entries - * and working tree files. - */ -int repo_rerere(struct repository *r, int flags) +int rerere(struct repository *r, enum rerere_flags flags) { struct string_list merge_rr = STRING_LIST_INIT_DUP; struct rerere_cache cache; @@ -1341,7 +1336,8 @@ out: return needed; } -void rerere_gc(struct repository *r, struct string_list *rr, int flags) +void rerere_gc(struct repository *r, struct string_list *rr, + enum rerere_flags flags) { struct rerere_cache cache; struct string_list to_remove = STRING_LIST_INIT_DUP; diff --git a/rerere.h b/rerere.h index d3cbd10e13..f13790c5dc 100644 --- a/rerere.h +++ b/rerere.h @@ -9,12 +9,6 @@ struct pathspec; struct repository; -#define RERERE_AUTOUPDATE 01 -#define RERERE_NOAUTOUPDATE 02 -#define RERERE_READONLY 04 -/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */ -#define RERERE_NOWAIT 010 - /* * Marks paths that have been hand-resolved and added to the * index. Set in the util field of such paths after calling @@ -39,13 +33,37 @@ struct rerere_id { int variant; }; +enum rerere_flags { + /* + * Auto-update the index with previously recorded resolutions. This can + * be used to explicitly override "rerere.autoUpdate". + */ + RERERE_AUTOUPDATE = (1 << 0), + + /* Reverse of RERERE_AUTOUPDATE. */ + RERERE_NOAUTOUPDATE = (1 << 1), + + /* Skip acquiring the MERGE_RR.lock for a read-only operation. */ + RERERE_READONLY = (1 << 2), + + /* If MERGE_RR.lock is taken, return -1 as if rerere were disabled. */ + RERERE_NOWAIT = (1 << 3), +}; + int rerere_cache_setup(struct rerere_cache *cache, struct repository *r, struct string_list *merge_rr, - int flags); + enum rerere_flags flags); void rerere_cache_release(struct rerere_cache *cache); -int repo_rerere(struct repository *, int); +/* + * The main entry point that is called internally from codepaths that + * perform mergy operations, possibly leaving conflicted index entries + * and working tree files. It records (and optionally updates) resolved + * conflicts and reuses recorded resolutions for unresolved conflicts. + */ +int rerere(struct repository *r, enum rerere_flags flags); + /* * Given the conflict ID and the name of a "file" used for replaying * the recorded resolution (e.g. "preimage", "postimage"), return the @@ -58,7 +76,8 @@ const char *rerere_path(struct repository *, int rerere_forget(struct repository *, struct pathspec *); int rerere_remaining(struct repository *, struct string_list *); void rerere_clear(struct repository *, struct string_list *); -void rerere_gc(struct repository *, struct string_list *, int); +void rerere_gc(struct repository *, struct string_list *, + enum rerere_flags flags); /* * Check whether garbage collection for rerere entries is needed, which is diff --git a/sequencer.c b/sequencer.c index 079ee3724d..cc7e5953d4 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2560,7 +2560,7 @@ static enum pick_result do_pick_commit(struct repository *r, : _("could not apply %s... %s"), short_commit_name(r, commit), msg.subject); print_advice(r, res == 1, opts); - repo_rerere(r, opts->allow_rerere_auto); + rerere(r, opts->allow_rerere_auto); goto leave; } @@ -4492,7 +4492,7 @@ static int do_merge(struct repository *r, rollback_lock_file(&lock); if (ret) - repo_rerere(r, opts->allow_rerere_auto); + rerere(r, opts->allow_rerere_auto); else /* * In case of problems, we now want to return a positive diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index ab412ecc6e..0d4cbf5114 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -263,11 +263,6 @@ test_expect_success 'gc --skip-locked prunes while MERGE_RR is not locked' ' test_path_is_missing $rr2/preimage ' -test_expect_success '--skip-locked is only accepted by gc' ' - test_must_fail git rerere --skip-locked clear 2>err && - test_grep "option .--skip-locked. requires .gc." err -' - test_expect_success 'a held lock is waited out within rerere.lockTimeout' ' git reset --hard && rm -rf $rr &&