From efcf6cf0492c4a78d968c6246698d9aafae61578 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:27:55 -0400 Subject: rebase: use read_oneliner() Since in sequencer.c, read_one() basically duplicates the functionality of read_oneliner(), reduce code duplication by replacing read_one() with read_oneliner(). This was done with the following Coccinelle script @@ expression a, b; @@ - read_one(a, b) + !read_oneliner(b, a, READ_ONELINER_WARN_MISSING) and long lines were manually broken up. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/rebase.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index bff53d5d16..57dbfd2a4f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -586,15 +586,6 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o return path.buf; } -/* Read one file, then strip line endings */ -static int read_one(const char *path, struct strbuf *buf) -{ - if (strbuf_read_file(buf, path, 0) < 0) - return error_errno(_("could not read '%s'"), path); - strbuf_trim_trailing_newline(buf); - return 0; -} - /* Initialize the rebase options from the state directory. */ static int read_basic_state(struct rebase_options *opts) { @@ -602,8 +593,10 @@ static int read_basic_state(struct rebase_options *opts) struct strbuf buf = STRBUF_INIT; struct object_id oid; - if (read_one(state_dir_path("head-name", opts), &head_name) || - read_one(state_dir_path("onto", opts), &buf)) + if (!read_oneliner(&head_name, state_dir_path("head-name", opts), + READ_ONELINER_WARN_MISSING) || + !read_oneliner(&buf, state_dir_path("onto", opts), + READ_ONELINER_WARN_MISSING)) return -1; opts->head_name = starts_with(head_name.buf, "refs/") ? xstrdup(head_name.buf) : NULL; @@ -619,9 +612,11 @@ static int read_basic_state(struct rebase_options *opts) */ strbuf_reset(&buf); if (file_exists(state_dir_path("orig-head", opts))) { - if (read_one(state_dir_path("orig-head", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("orig-head", opts), + READ_ONELINER_WARN_MISSING)) return -1; - } else if (read_one(state_dir_path("head", opts), &buf)) + } else if (!read_oneliner(&buf, state_dir_path("head", opts), + READ_ONELINER_WARN_MISSING)) return -1; if (get_oid(buf.buf, &opts->orig_head)) return error(_("invalid orig-head: '%s'"), buf.buf); @@ -641,8 +636,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("allow_rerere_autoupdate", opts), - &buf)) + if (!read_oneliner(&buf, state_dir_path("allow_rerere_autoupdate", opts), + READ_ONELINER_WARN_MISSING)) return -1; if (!strcmp(buf.buf, "--rerere-autoupdate")) opts->allow_rerere_autoupdate = RERERE_AUTOUPDATE; @@ -655,8 +650,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("gpg_sign_opt", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("gpg_sign_opt", opts), - &buf)) + if (!read_oneliner(&buf, state_dir_path("gpg_sign_opt", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->gpg_sign_opt); opts->gpg_sign_opt = xstrdup(buf.buf); @@ -664,7 +659,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("strategy", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("strategy", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("strategy", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->strategy); opts->strategy = xstrdup(buf.buf); @@ -672,7 +668,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("strategy_opts", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("strategy_opts", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("strategy_opts", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->strategy_opts); opts->strategy_opts = xstrdup(buf.buf); @@ -724,7 +721,7 @@ static int apply_autostash(struct rebase_options *opts) if (!file_exists(path)) return 0; - if (read_one(path, &autostash)) + if (!read_oneliner(&autostash, path, READ_ONELINER_WARN_MISSING)) return error(_("Could not read '%s'"), path); /* Ensure that the hash is not mistaken for a number */ strbuf_addstr(&autostash, "^0"); -- cgit v1.3 From 86ed00aff4b86eed6ecc606e9d5f7828a1dca353 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:27:58 -0400 Subject: rebase: use apply_autostash() from sequencer.c The apply_autostash() function in builtin/rebase.c is similar enough to the apply_autostash() function in sequencer.c that they are almost interchangeable, except for the type of arg they accept. Make the sequencer.c version extern and use it in rebase. The rebase version was introduced in 6defce2b02 (builtin rebase: support `--autostash` option, 2018-09-04) as part of the shell to C conversion. It opted to duplicate the function because, at the time, there was another in-progress project converting interactive rebase from shell to C as well and they did not want to clash with them by refactoring sequencer.c version of apply_autostash(). Since both efforts are long done, we can freely combine them together now. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/rebase.c | 49 ++----------------------------------------------- sequencer.c | 2 +- sequencer.h | 2 ++ 3 files changed, 5 insertions(+), 48 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index 57dbfd2a4f..cb0bdfc3e4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -712,51 +712,6 @@ static int rebase_write_basic_state(struct rebase_options *opts) return 0; } -static int apply_autostash(struct rebase_options *opts) -{ - const char *path = state_dir_path("autostash", opts); - struct strbuf autostash = STRBUF_INIT; - struct child_process stash_apply = CHILD_PROCESS_INIT; - - if (!file_exists(path)) - return 0; - - if (!read_oneliner(&autostash, path, READ_ONELINER_WARN_MISSING)) - return error(_("Could not read '%s'"), path); - /* Ensure that the hash is not mistaken for a number */ - strbuf_addstr(&autostash, "^0"); - argv_array_pushl(&stash_apply.args, - "stash", "apply", autostash.buf, NULL); - stash_apply.git_cmd = 1; - stash_apply.no_stderr = stash_apply.no_stdout = - stash_apply.no_stdin = 1; - if (!run_command(&stash_apply)) - printf(_("Applied autostash.\n")); - else { - struct argv_array args = ARGV_ARRAY_INIT; - int res = 0; - - argv_array_pushl(&args, - "stash", "store", "-m", "autostash", "-q", - autostash.buf, NULL); - if (run_command_v_opt(args.argv, RUN_GIT_CMD)) - res = error(_("Cannot store %s"), autostash.buf); - argv_array_clear(&args); - strbuf_release(&autostash); - if (res) - return res; - - fprintf(stderr, - _("Applying autostash resulted in conflicts.\n" - "Your changes are safe in the stash.\n" - "You can run \"git stash pop\" or \"git stash drop\" " - "at any time.\n")); - } - - strbuf_release(&autostash); - return 0; -} - static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; @@ -764,7 +719,7 @@ static int finish_rebase(struct rebase_options *opts) int ret = 0; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - apply_autostash(opts); + apply_autostash(state_dir_path("autostash", opts)); close_object_store(the_repository->objects); /* * We ignore errors in 'gc --auto', since the @@ -1209,7 +1164,7 @@ finished_rebase: } else if (status == 2) { struct strbuf dir = STRBUF_INIT; - apply_autostash(opts); + apply_autostash(state_dir_path("autostash", opts)); strbuf_addstr(&dir, opts->state_dir); remove_dir_recursively(&dir, 0); strbuf_release(&dir); diff --git a/sequencer.c b/sequencer.c index 4a6812e90e..f5bb1cc1fb 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3657,7 +3657,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } -static int apply_autostash(const char *path) +int apply_autostash(const char *path) { struct strbuf stash_oid = STRBUF_INIT; struct child_process child = CHILD_PROCESS_INIT; diff --git a/sequencer.h b/sequencer.h index ab43717fd6..ab686146d1 100644 --- a/sequencer.h +++ b/sequencer.h @@ -191,6 +191,8 @@ void commit_post_rewrite(struct repository *r, const struct commit *current_head, const struct object_id *new_head); +int apply_autostash(const char *path); + #define SUMMARY_INITIAL_COMMIT (1 << 0) #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1) void print_commit_summary(struct repository *repo, -- cgit v1.3 From f213f069fb57996505f9d6c9dd0f4a3ef53e87e4 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:27:59 -0400 Subject: rebase: generify reset_head() In the future, we plan on lib-ifying reset_head() so we need it to be more generic. Make it more generic by making it accept a `struct repository` argument instead of implicitly using the non-repo functions. Also, make it accept a `const char *default_reflog_action` argument so that the default action of "rebase" isn't hardcoded in. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/rebase.c | 64 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 27 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index cb0bdfc3e4..5532286142 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -28,6 +28,8 @@ #include "sequencer.h" #include "rebase-interactive.h" +#define DEFAULT_REFLOG_ACTION "rebase" + static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] " "[--onto | --keep-base] [ []]"), @@ -772,9 +774,10 @@ static void add_var(struct strbuf *buf, const char *name, const char *value) #define RESET_HEAD_REFS_ONLY (1<<3) #define RESET_ORIG_HEAD (1<<4) -static int reset_head(struct object_id *oid, const char *action, +static int reset_head(struct repository *r, struct object_id *oid, const char *action, const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head) + const char *reflog_orig_head, const char *reflog_head, + const char *default_reflog_action) { unsigned detach_head = flags & RESET_HEAD_DETACH; unsigned reset_hard = flags & RESET_HEAD_HARD; @@ -796,7 +799,7 @@ static int reset_head(struct object_id *oid, const char *action, if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) BUG("Not a fully qualified branch: '%s'", switch_to_branch); - if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { + if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) { ret = -1; goto leave_reset_head; } @@ -815,26 +818,26 @@ static int reset_head(struct object_id *oid, const char *action, memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); setup_unpack_trees_porcelain(&unpack_tree_opts, action); unpack_tree_opts.head_idx = 1; - unpack_tree_opts.src_index = the_repository->index; - unpack_tree_opts.dst_index = the_repository->index; + unpack_tree_opts.src_index = r->index; + unpack_tree_opts.dst_index = r->index; unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; unpack_tree_opts.update = 1; unpack_tree_opts.merge = 1; if (!detach_head) unpack_tree_opts.reset = 1; - if (repo_read_index_unmerged(the_repository) < 0) { + if (repo_read_index_unmerged(r) < 0) { ret = error(_("could not read index")); goto leave_reset_head; } - if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) { + if (!reset_hard && !fill_tree_descriptor(r, &desc[nr++], &head_oid)) { ret = error(_("failed to find tree of %s"), oid_to_hex(&head_oid)); goto leave_reset_head; } - if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) { + if (!fill_tree_descriptor(r, &desc[nr++], oid)) { ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); goto leave_reset_head; } @@ -845,16 +848,16 @@ static int reset_head(struct object_id *oid, const char *action, } tree = parse_tree_indirect(oid); - prime_cache_tree(the_repository, the_repository->index, tree); + prime_cache_tree(r, r->index, tree); - if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) { + if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) { ret = error(_("could not write index")); goto leave_reset_head; } reset_head_refs: reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action); prefix_len = msg.len; if (update_orig_head) { @@ -916,8 +919,10 @@ static int move_to_original_branch(struct rebase_options *opts) opts->head_name, oid_to_hex(&opts->onto->object.oid)); strbuf_addf(&head_reflog, "rebase finished: returning to %s", opts->head_name); - ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY, - orig_head_reflog.buf, head_reflog.buf); + ret = reset_head(the_repository, NULL, "", opts->head_name, + RESET_HEAD_REFS_ONLY, + orig_head_reflog.buf, head_reflog.buf, + DEFAULT_REFLOG_ACTION); strbuf_release(&orig_head_reflog); strbuf_release(&head_reflog); @@ -1005,8 +1010,9 @@ static int run_am(struct rebase_options *opts) free(rebased_patches); argv_array_clear(&am.args); - reset_head(&opts->orig_head, "checkout", opts->head_name, 0, - "HEAD", NULL); + reset_head(the_repository, &opts->orig_head, "checkout", + opts->head_name, 0, + "HEAD", NULL, DEFAULT_REFLOG_ACTION); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -1661,8 +1667,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); - if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD, - NULL, NULL) < 0) + if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD, + NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1679,9 +1685,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); - if (reset_head(&options.orig_head, "reset", + if (reset_head(the_repository, &options.orig_head, "reset", options.head_name, RESET_HEAD_HARD, - NULL, NULL) < 0) + NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); @@ -2073,8 +2079,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.state_dir); write_file(autostash, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(NULL, "reset --hard", - NULL, RESET_HEAD_HARD, NULL, NULL) < 0) + if (reset_head(the_repository, NULL, "reset --hard", + NULL, RESET_HEAD_HARD, NULL, NULL, + DEFAULT_REFLOG_ACTION) < 0) die(_("could not reset --hard")); if (discard_index(the_repository->index) < 0 || @@ -2114,10 +2121,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&buf, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.switch_to); - if (reset_head(&options.orig_head, "checkout", + if (reset_head(the_repository, + &options.orig_head, "checkout", options.head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf) < 0) { + NULL, buf.buf, + DEFAULT_REFLOG_ACTION) < 0) { ret = !!error(_("could not switch to " "%s"), options.switch_to); @@ -2189,10 +2198,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); - if (reset_head(&options.onto->object.oid, "checkout", NULL, + if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL, RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf)) + NULL, msg.buf, DEFAULT_REFLOG_ACTION)) die(_("Could not detach HEAD")); strbuf_release(&msg); @@ -2207,8 +2216,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "rebase finished: %s onto %s", options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); - reset_head(NULL, "Fast-forwarded", options.head_name, - RESET_HEAD_REFS_ONLY, "HEAD", msg.buf); + reset_head(the_repository, NULL, "Fast-forwarded", options.head_name, + RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, + DEFAULT_REFLOG_ACTION); strbuf_release(&msg); ret = !!finish_rebase(&options); goto cleanup; -- cgit v1.3 From b309a9710899dab2f76455526f573a90fad5d13c Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:28:00 -0400 Subject: reset: extract reset_head() from rebase Continue the process of lib-ifying the autostash code. In a future commit, this will be used to implement `--autostash` in other builtins. This patch is best viewed with `--color-moved`. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin/rebase.c | 139 +----------------------------------------------------- reset.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ reset.h | 20 ++++++++ 4 files changed, 162 insertions(+), 138 deletions(-) create mode 100644 reset.c create mode 100644 reset.h (limited to 'builtin') diff --git a/Makefile b/Makefile index f709fc3f77..338fb55b73 100644 --- a/Makefile +++ b/Makefile @@ -970,6 +970,7 @@ LIB_OBJS += replace-object.o LIB_OBJS += repo-settings.o LIB_OBJS += repository.o LIB_OBJS += rerere.o +LIB_OBJS += reset.o LIB_OBJS += resolve-undo.o LIB_OBJS += revision.o LIB_OBJS += run-command.o diff --git a/builtin/rebase.c b/builtin/rebase.c index 5532286142..48ef36bd10 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,6 +27,7 @@ #include "branch.h" #include "sequencer.h" #include "rebase-interactive.h" +#include "reset.h" #define DEFAULT_REFLOG_ACTION "rebase" @@ -766,144 +767,6 @@ static void add_var(struct strbuf *buf, const char *name, const char *value) } } -#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" - -#define RESET_HEAD_DETACH (1<<0) -#define RESET_HEAD_HARD (1<<1) -#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) -#define RESET_HEAD_REFS_ONLY (1<<3) -#define RESET_ORIG_HEAD (1<<4) - -static int reset_head(struct repository *r, struct object_id *oid, const char *action, - const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head, - const char *default_reflog_action) -{ - unsigned detach_head = flags & RESET_HEAD_DETACH; - unsigned reset_hard = flags & RESET_HEAD_HARD; - unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; - unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; - unsigned update_orig_head = flags & RESET_ORIG_HEAD; - struct object_id head_oid; - struct tree_desc desc[2] = { { NULL }, { NULL } }; - struct lock_file lock = LOCK_INIT; - struct unpack_trees_options unpack_tree_opts; - struct tree *tree; - const char *reflog_action; - struct strbuf msg = STRBUF_INIT; - size_t prefix_len; - struct object_id *orig = NULL, oid_orig, - *old_orig = NULL, oid_old_orig; - int ret = 0, nr = 0; - - if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) - BUG("Not a fully qualified branch: '%s'", switch_to_branch); - - if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) { - ret = -1; - goto leave_reset_head; - } - - if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { - ret = error(_("could not determine HEAD revision")); - goto leave_reset_head; - } - - if (!oid) - oid = &head_oid; - - if (refs_only) - goto reset_head_refs; - - memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); - setup_unpack_trees_porcelain(&unpack_tree_opts, action); - unpack_tree_opts.head_idx = 1; - unpack_tree_opts.src_index = r->index; - unpack_tree_opts.dst_index = r->index; - unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; - unpack_tree_opts.update = 1; - unpack_tree_opts.merge = 1; - if (!detach_head) - unpack_tree_opts.reset = 1; - - if (repo_read_index_unmerged(r) < 0) { - ret = error(_("could not read index")); - goto leave_reset_head; - } - - if (!reset_hard && !fill_tree_descriptor(r, &desc[nr++], &head_oid)) { - ret = error(_("failed to find tree of %s"), - oid_to_hex(&head_oid)); - goto leave_reset_head; - } - - if (!fill_tree_descriptor(r, &desc[nr++], oid)) { - ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); - goto leave_reset_head; - } - - if (unpack_trees(nr, desc, &unpack_tree_opts)) { - ret = -1; - goto leave_reset_head; - } - - tree = parse_tree_indirect(oid); - prime_cache_tree(r, r->index, tree); - - if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) { - ret = error(_("could not write index")); - goto leave_reset_head; - } - -reset_head_refs: - reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action); - prefix_len = msg.len; - - if (update_orig_head) { - if (!get_oid("ORIG_HEAD", &oid_old_orig)) - old_orig = &oid_old_orig; - if (!get_oid("HEAD", &oid_orig)) { - orig = &oid_orig; - if (!reflog_orig_head) { - strbuf_addstr(&msg, "updating ORIG_HEAD"); - reflog_orig_head = msg.buf; - } - update_ref(reflog_orig_head, "ORIG_HEAD", orig, - old_orig, 0, UPDATE_REFS_MSG_ON_ERR); - } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig, 0); - } - - if (!reflog_head) { - strbuf_setlen(&msg, prefix_len); - strbuf_addstr(&msg, "updating HEAD"); - reflog_head = msg.buf; - } - if (!switch_to_branch) - ret = update_ref(reflog_head, "HEAD", oid, orig, - detach_head ? REF_NO_DEREF : 0, - UPDATE_REFS_MSG_ON_ERR); - else { - ret = update_ref(reflog_head, switch_to_branch, oid, - NULL, 0, UPDATE_REFS_MSG_ON_ERR); - if (!ret) - ret = create_symref("HEAD", switch_to_branch, - reflog_head); - } - if (run_hook) - run_hook_le(NULL, "post-checkout", - oid_to_hex(orig ? orig : &null_oid), - oid_to_hex(oid), "1", NULL); - -leave_reset_head: - strbuf_release(&msg); - rollback_lock_file(&lock); - while (nr) - free((void *)desc[--nr].buffer); - return ret; -} - static int move_to_original_branch(struct rebase_options *opts) { struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; diff --git a/reset.c b/reset.c new file mode 100644 index 0000000000..79b683bffa --- /dev/null +++ b/reset.c @@ -0,0 +1,140 @@ +#include "git-compat-util.h" +#include "cache-tree.h" +#include "lockfile.h" +#include "refs.h" +#include "reset.h" +#include "run-command.h" +#include "tree-walk.h" +#include "tree.h" +#include "unpack-trees.h" + +int reset_head(struct repository *r, struct object_id *oid, const char *action, + const char *switch_to_branch, unsigned flags, + const char *reflog_orig_head, const char *reflog_head, + const char *default_reflog_action) +{ + unsigned detach_head = flags & RESET_HEAD_DETACH; + unsigned reset_hard = flags & RESET_HEAD_HARD; + unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; + unsigned update_orig_head = flags & RESET_ORIG_HEAD; + struct object_id head_oid; + struct tree_desc desc[2] = { { NULL }, { NULL } }; + struct lock_file lock = LOCK_INIT; + struct unpack_trees_options unpack_tree_opts; + struct tree *tree; + const char *reflog_action; + struct strbuf msg = STRBUF_INIT; + size_t prefix_len; + struct object_id *orig = NULL, oid_orig, + *old_orig = NULL, oid_old_orig; + int ret = 0, nr = 0; + + if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) + BUG("Not a fully qualified branch: '%s'", switch_to_branch); + + if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) { + ret = -1; + goto leave_reset_head; + } + + if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { + ret = error(_("could not determine HEAD revision")); + goto leave_reset_head; + } + + if (!oid) + oid = &head_oid; + + if (refs_only) + goto reset_head_refs; + + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(&unpack_tree_opts, action); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = r->index; + unpack_tree_opts.dst_index = r->index; + unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; + unpack_tree_opts.update = 1; + unpack_tree_opts.merge = 1; + if (!detach_head) + unpack_tree_opts.reset = 1; + + if (repo_read_index_unmerged(r) < 0) { + ret = error(_("could not read index")); + goto leave_reset_head; + } + + if (!reset_hard && !fill_tree_descriptor(r, &desc[nr++], &head_oid)) { + ret = error(_("failed to find tree of %s"), + oid_to_hex(&head_oid)); + goto leave_reset_head; + } + + if (!fill_tree_descriptor(r, &desc[nr++], oid)) { + ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); + goto leave_reset_head; + } + + if (unpack_trees(nr, desc, &unpack_tree_opts)) { + ret = -1; + goto leave_reset_head; + } + + tree = parse_tree_indirect(oid); + prime_cache_tree(r, r->index, tree); + + if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) { + ret = error(_("could not write index")); + goto leave_reset_head; + } + +reset_head_refs: + reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action); + prefix_len = msg.len; + + if (update_orig_head) { + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, + old_orig, 0, UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + } + + if (!reflog_head) { + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + reflog_head = msg.buf; + } + if (!switch_to_branch) + ret = update_ref(reflog_head, "HEAD", oid, orig, + detach_head ? REF_NO_DEREF : 0, + UPDATE_REFS_MSG_ON_ERR); + else { + ret = update_ref(reflog_head, switch_to_branch, oid, + NULL, 0, UPDATE_REFS_MSG_ON_ERR); + if (!ret) + ret = create_symref("HEAD", switch_to_branch, + reflog_head); + } + if (run_hook) + run_hook_le(NULL, "post-checkout", + oid_to_hex(orig ? orig : &null_oid), + oid_to_hex(oid), "1", NULL); + +leave_reset_head: + strbuf_release(&msg); + rollback_lock_file(&lock); + while (nr) + free((void *)desc[--nr].buffer); + return ret; + +} diff --git a/reset.h b/reset.h new file mode 100644 index 0000000000..12f83c78e2 --- /dev/null +++ b/reset.h @@ -0,0 +1,20 @@ +#ifndef RESET_H +#define RESET_H + +#include "hash.h" +#include "repository.h" + +#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" + +#define RESET_HEAD_DETACH (1<<0) +#define RESET_HEAD_HARD (1<<1) +#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) +#define RESET_HEAD_REFS_ONLY (1<<3) +#define RESET_ORIG_HEAD (1<<4) + +int reset_head(struct repository *r, struct object_id *oid, const char *action, + const char *switch_to_branch, unsigned flags, + const char *reflog_orig_head, const char *reflog_head, + const char *default_reflog_action); + +#endif -- cgit v1.3 From 4d4bc157f85316576d9f9bb90657d455fe76e281 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:28:01 -0400 Subject: rebase: extract create_autostash() In a future commit, we will lib-ify this code. In preparation for this, extract the code into the create_autostash() function so that it can be cleaned up before it is finally lib-ified. This patch is best viewed with `--color-moved` and `--color-moved-ws=allow-indentation-change`. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/rebase.c | 94 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 44 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index 48ef36bd10..581ad3af7b 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1274,6 +1274,55 @@ static int check_exec_cmd(const char *cmd) return 0; } +static void create_autostash(struct rebase_options *options) +{ + struct strbuf buf = STRBUF_INIT; + struct lock_file lock_file = LOCK_INIT; + int fd; + + fd = hold_locked_index(&lock_file, 0); + refresh_cache(REFRESH_QUIET); + if (0 <= fd) + repo_update_index_if_able(the_repository, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(the_repository, 1) || + has_uncommitted_changes(the_repository, 1)) { + const char *autostash = + state_dir_path("autostash", options); + struct child_process stash = CHILD_PROCESS_INIT; + struct object_id oid; + + argv_array_pushl(&stash.args, + "stash", "create", "autostash", NULL); + stash.git_cmd = 1; + stash.no_stdin = 1; + strbuf_reset(&buf); + if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) + die(_("Cannot autostash")); + strbuf_trim_trailing_newline(&buf); + if (get_oid(buf.buf, &oid)) + die(_("Unexpected stash response: '%s'"), + buf.buf); + strbuf_reset(&buf); + strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); + + if (safe_create_leading_directories_const(autostash)) + die(_("Could not create directory for '%s'"), + options->state_dir); + write_file(autostash, "%s", oid_to_hex(&oid)); + printf(_("Created autostash: %s\n"), buf.buf); + if (reset_head(the_repository, NULL, "reset --hard", + NULL, RESET_HEAD_HARD, NULL, NULL, + DEFAULT_REFLOG_ACTION) < 0) + die(_("could not reset --hard")); + + if (discard_index(the_repository->index) < 0 || + repo_read_index(the_repository) < 0) + die(_("could not read index")); + } + strbuf_release(&buf); +} int cmd_rebase(int argc, const char **argv, const char *prefix) { @@ -1907,50 +1956,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not read index")); if (options.autostash) { - struct lock_file lock_file = LOCK_INIT; - int fd; - - fd = hold_locked_index(&lock_file, 0); - refresh_cache(REFRESH_QUIET); - if (0 <= fd) - repo_update_index_if_able(the_repository, &lock_file); - rollback_lock_file(&lock_file); - - if (has_unstaged_changes(the_repository, 1) || - has_uncommitted_changes(the_repository, 1)) { - const char *autostash = - state_dir_path("autostash", &options); - struct child_process stash = CHILD_PROCESS_INIT; - struct object_id oid; - - argv_array_pushl(&stash.args, - "stash", "create", "autostash", NULL); - stash.git_cmd = 1; - stash.no_stdin = 1; - strbuf_reset(&buf); - if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) - die(_("Cannot autostash")); - strbuf_trim_trailing_newline(&buf); - if (get_oid(buf.buf, &oid)) - die(_("Unexpected stash response: '%s'"), - buf.buf); - strbuf_reset(&buf); - strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); - - if (safe_create_leading_directories_const(autostash)) - die(_("Could not create directory for '%s'"), - options.state_dir); - write_file(autostash, "%s", oid_to_hex(&oid)); - printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(the_repository, NULL, "reset --hard", - NULL, RESET_HEAD_HARD, NULL, NULL, - DEFAULT_REFLOG_ACTION) < 0) - die(_("could not reset --hard")); - - if (discard_index(the_repository->index) < 0 || - repo_read_index(the_repository) < 0) - die(_("could not read index")); - } + create_autostash(&options); } if (require_clean_work_tree(the_repository, "rebase", -- cgit v1.3 From 9bb3dea45d69921578b1729c7dc7b12bfbe6f69e Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:28:02 -0400 Subject: rebase: generify create_autostash() In the future, we plan on lib-ifying create_autostash() so we need it to be more generic. Make it more generic by making it accept a `struct repository` argument instead of implicitly using the non-repo functions and `the_repository`. Also, make it accept a `path` argument so that we no longer rely have to rely on `struct rebase_options`. Finally, make it accept a `default_reflog_action` argument so we no longer have to rely on `DEFAULT_REFLOG_ACTION`. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/rebase.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index 581ad3af7b..ae345c9e57 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1274,22 +1274,21 @@ static int check_exec_cmd(const char *cmd) return 0; } -static void create_autostash(struct rebase_options *options) +static void create_autostash(struct repository *r, const char *path, + const char *default_reflog_action) { struct strbuf buf = STRBUF_INIT; struct lock_file lock_file = LOCK_INIT; int fd; - fd = hold_locked_index(&lock_file, 0); - refresh_cache(REFRESH_QUIET); + fd = repo_hold_locked_index(r, &lock_file, 0); + refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL); if (0 <= fd) - repo_update_index_if_able(the_repository, &lock_file); + repo_update_index_if_able(r, &lock_file); rollback_lock_file(&lock_file); - if (has_unstaged_changes(the_repository, 1) || - has_uncommitted_changes(the_repository, 1)) { - const char *autostash = - state_dir_path("autostash", options); + if (has_unstaged_changes(r, 1) || + has_uncommitted_changes(r, 1)) { struct child_process stash = CHILD_PROCESS_INIT; struct object_id oid; @@ -1307,18 +1306,18 @@ static void create_autostash(struct rebase_options *options) strbuf_reset(&buf); strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); - if (safe_create_leading_directories_const(autostash)) + if (safe_create_leading_directories_const(path)) die(_("Could not create directory for '%s'"), - options->state_dir); - write_file(autostash, "%s", oid_to_hex(&oid)); + path); + write_file(path, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(the_repository, NULL, "reset --hard", + if (reset_head(r, NULL, "reset --hard", NULL, RESET_HEAD_HARD, NULL, NULL, - DEFAULT_REFLOG_ACTION) < 0) + default_reflog_action) < 0) die(_("could not reset --hard")); - if (discard_index(the_repository->index) < 0 || - repo_read_index(the_repository) < 0) + if (discard_index(r->index) < 0 || + repo_read_index(r) < 0) die(_("could not read index")); } strbuf_release(&buf); @@ -1956,7 +1955,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not read index")); if (options.autostash) { - create_autostash(&options); + create_autostash(the_repository, state_dir_path("autostash", &options), + DEFAULT_REFLOG_ACTION); } if (require_clean_work_tree(the_repository, "rebase", -- cgit v1.3 From 0816f1dff8715e80b5fa0b73b48dd36ef9e1e381 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:28:03 -0400 Subject: sequencer: extract perform_autostash() from rebase Lib-ify the autostash code by extracting perform_autostash() from rebase into sequencer. In a future commit, this will be used to implement `--autostash` in other builtins. This patch is best viewed with `--color-moved`. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/rebase.c | 49 ------------------------------------------------- sequencer.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ sequencer.h | 2 ++ 3 files changed, 52 insertions(+), 49 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index ae345c9e57..08b89869b3 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1274,55 +1274,6 @@ static int check_exec_cmd(const char *cmd) return 0; } -static void create_autostash(struct repository *r, const char *path, - const char *default_reflog_action) -{ - struct strbuf buf = STRBUF_INIT; - struct lock_file lock_file = LOCK_INIT; - int fd; - - fd = repo_hold_locked_index(r, &lock_file, 0); - refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL); - if (0 <= fd) - repo_update_index_if_able(r, &lock_file); - rollback_lock_file(&lock_file); - - if (has_unstaged_changes(r, 1) || - has_uncommitted_changes(r, 1)) { - struct child_process stash = CHILD_PROCESS_INIT; - struct object_id oid; - - argv_array_pushl(&stash.args, - "stash", "create", "autostash", NULL); - stash.git_cmd = 1; - stash.no_stdin = 1; - strbuf_reset(&buf); - if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) - die(_("Cannot autostash")); - strbuf_trim_trailing_newline(&buf); - if (get_oid(buf.buf, &oid)) - die(_("Unexpected stash response: '%s'"), - buf.buf); - strbuf_reset(&buf); - strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); - - if (safe_create_leading_directories_const(path)) - die(_("Could not create directory for '%s'"), - path); - write_file(path, "%s", oid_to_hex(&oid)); - printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(r, NULL, "reset --hard", - NULL, RESET_HEAD_HARD, NULL, NULL, - default_reflog_action) < 0) - die(_("could not reset --hard")); - - if (discard_index(r->index) < 0 || - repo_read_index(r) < 0) - die(_("could not read index")); - } - strbuf_release(&buf); -} - int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = REBASE_OPTIONS_INIT; diff --git a/sequencer.c b/sequencer.c index f5bb1cc1fb..e0b8262521 100644 --- a/sequencer.c +++ b/sequencer.c @@ -32,6 +32,7 @@ #include "alias.h" #include "commit-reach.h" #include "rebase-interactive.h" +#include "reset.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -3657,6 +3658,55 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } +void create_autostash(struct repository *r, const char *path, + const char *default_reflog_action) +{ + struct strbuf buf = STRBUF_INIT; + struct lock_file lock_file = LOCK_INIT; + int fd; + + fd = repo_hold_locked_index(r, &lock_file, 0); + refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL); + if (0 <= fd) + repo_update_index_if_able(r, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(r, 1) || + has_uncommitted_changes(r, 1)) { + struct child_process stash = CHILD_PROCESS_INIT; + struct object_id oid; + + argv_array_pushl(&stash.args, + "stash", "create", "autostash", NULL); + stash.git_cmd = 1; + stash.no_stdin = 1; + strbuf_reset(&buf); + if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) + die(_("Cannot autostash")); + strbuf_trim_trailing_newline(&buf); + if (get_oid(buf.buf, &oid)) + die(_("Unexpected stash response: '%s'"), + buf.buf); + strbuf_reset(&buf); + strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); + + if (safe_create_leading_directories_const(path)) + die(_("Could not create directory for '%s'"), + path); + write_file(path, "%s", oid_to_hex(&oid)); + printf(_("Created autostash: %s\n"), buf.buf); + if (reset_head(r, NULL, "reset --hard", + NULL, RESET_HEAD_HARD, NULL, NULL, + default_reflog_action) < 0) + die(_("could not reset --hard")); + + if (discard_index(r->index) < 0 || + repo_read_index(r) < 0) + die(_("could not read index")); + } + strbuf_release(&buf); +} + int apply_autostash(const char *path) { struct strbuf stash_oid = STRBUF_INIT; diff --git a/sequencer.h b/sequencer.h index ab686146d1..9d1fe0ccfb 100644 --- a/sequencer.h +++ b/sequencer.h @@ -191,6 +191,8 @@ void commit_post_rewrite(struct repository *r, const struct commit *current_head, const struct object_id *new_head); +void create_autostash(struct repository *r, const char *path, + const char *default_reflog_action); int apply_autostash(const char *path); #define SUMMARY_INITIAL_COMMIT (1 << 0) -- cgit v1.3 From a03b55530ab844974b7058042a015fcfcd9e7a53 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:28:07 -0400 Subject: merge: teach --autostash option In rebase, one can pass the `--autostash` option to cause the worktree to be automatically stashed before continuing with the rebase. This option is missing in merge, however. Implement the `--autostash` option and corresponding `merge.autoStash` option in merge which stashes before merging and then pops after. This option is useful when a developer has some local changes on a topic branch but they realize that their work depends on another branch. Previously, they had to run something like git fetch ... git stash push git merge FETCH_HEAD git stash pop but now, that is reduced to git fetch ... git merge --autostash FETCH_HEAD When an autostash is generated, it is automatically reapplied to the worktree only in three explicit situations: 1. An incomplete merge is commit using `git commit`. 2. A merge completes successfully. 3. A merge is aborted using `git merge --abort`. In all other situations where the merge state is removed using remove_merge_branch_state() such as aborting a merge via `git reset --hard`, the autostash is saved into the stash reflog instead keeping the worktree clean. Helped-by: Phillip Wood Suggested-by: Alban Gruin Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- Documentation/config/merge.txt | 10 +++ Documentation/git-merge.txt | 11 +++- Documentation/merge-options.txt | 8 +++ branch.c | 1 + builtin/commit.c | 2 + builtin/merge.c | 25 ++++++++ builtin/rebase.c | 3 +- parse-options.h | 1 + path.c | 1 + path.h | 4 +- t/t3033-merge-toplevel.sh | 22 +++++++ t/t7600-merge.sh | 132 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 214 insertions(+), 6 deletions(-) (limited to 'builtin') diff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt index 6a313937f8..cb2ed58907 100644 --- a/Documentation/config/merge.txt +++ b/Documentation/config/merge.txt @@ -70,6 +70,16 @@ merge.stat:: Whether to print the diffstat between ORIG_HEAD and the merge result at the end of the merge. True by default. +merge.autoStash:: + When set to true, automatically create a temporary stash entry + before the operation begins, and apply it after the operation + ends. This means that you can run merge on a dirty worktree. + However, use with care: the final stash application after a + successful merge might result in non-trivial conflicts. + This option can be overridden by the `--no-autostash` and + `--autostash` options of linkgit:git-merge[1]. + Defaults to false. + merge.tool:: Controls which merge tool is used by linkgit:git-mergetool[1]. The list below shows the valid built-in values. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 092529c619..ec06b2f8c2 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -94,7 +94,8 @@ will be appended to the specified message. --abort:: Abort the current conflict resolution process, and - try to reconstruct the pre-merge state. + try to reconstruct the pre-merge state. If an autostash entry is + present, apply it to the worktree. + If there were uncommitted worktree changes present when the merge started, 'git merge --abort' will in some cases be unable to @@ -102,11 +103,15 @@ reconstruct these changes. It is therefore recommended to always commit or stash your changes before running 'git merge'. + 'git merge --abort' is equivalent to 'git reset --merge' when -`MERGE_HEAD` is present. +`MERGE_HEAD` is present unless `MERGE_AUTOSTASH` is also present in +which case 'git merge --abort' applies the stash entry to the worktree +whereas 'git reset --merge' will save the stashed changes in the stash +reflog. --quit:: Forget about the current merge in progress. Leave the index - and the working tree as-is. + and the working tree as-is. If `MERGE_AUTOSTASH` is present, the + stash entry will be saved to the stash reflog. --continue:: After a 'git merge' stops due to conflicts you can conclude the diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 40dc4f5e8c..3985e6d4a9 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -155,6 +155,14 @@ ifndef::git-pull[] Note that not all merge strategies may support progress reporting. +--autostash:: +--no-autostash:: + Automatically create a temporary stash entry before the operation + begins, and apply it after the operation ends. This means + that you can run the operation on a dirty worktree. However, use + with care: the final stash application after a successful + merge might result in non-trivial conflicts. + endif::git-pull[] --allow-unrelated-histories:: diff --git a/branch.c b/branch.c index 579494738a..2d9e7675a6 100644 --- a/branch.c +++ b/branch.c @@ -344,6 +344,7 @@ void remove_merge_branch_state(struct repository *r) unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); + save_autostash(git_path_merge_autostash(r)); } void remove_branch_state(struct repository *r, int verbose) diff --git a/builtin/commit.c b/builtin/commit.c index 7ba33a3bec..c1b25d2954 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1713,6 +1713,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) &oid, flags); } + apply_autostash(git_path_merge_autostash(the_repository)); + UNLEAK(err); UNLEAK(sb); return 0; diff --git a/builtin/merge.c b/builtin/merge.c index d127d2225f..f86ae90bfc 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -82,6 +82,7 @@ static int show_progress = -1; static int default_to_upstream = 1; static int signoff; static const char *sign_commit; +static int autostash; static int no_verify; static struct strategy all_strategy[] = { @@ -286,6 +287,7 @@ static struct option builtin_merge_options[] = { OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_AUTOSTASH(&autostash), OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")), OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")), @@ -475,6 +477,7 @@ static void finish(struct commit *head_commit, /* Run a post-merge hook */ run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL); + apply_autostash(git_path_merge_autostash(the_repository)); strbuf_release(&reflog_message); } @@ -634,6 +637,9 @@ static int git_merge_config(const char *k, const char *v, void *cb) return 0; } else if (!strcmp(k, "gpg.mintrustlevel")) { check_trust_level = 0; + } else if (!strcmp(k, "merge.autostash")) { + autostash = git_config_bool(k, v); + return 0; } status = fmt_merge_msg_config(k, v, cb); @@ -1281,6 +1287,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (abort_current_merge) { int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; + struct strbuf stash_oid = STRBUF_INIT; if (orig_argc != 2) usage_msg_opt(_("--abort expects no arguments"), @@ -1289,8 +1296,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge to abort (MERGE_HEAD missing).")); + if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository), + READ_ONELINER_SKIP_IF_EMPTY)) + unlink(git_path_merge_autostash(the_repository)); + /* Invoke 'git reset --merge' */ ret = cmd_reset(nargc, nargv, prefix); + + if (stash_oid.len) + apply_autostash_oid(stash_oid.buf); + + strbuf_release(&stash_oid); goto done; } @@ -1513,6 +1529,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, @@ -1579,6 +1599,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (fast_forward == FF_ONLY) die(_("Not possible to fast-forward, aborting.")); + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); + /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); diff --git a/builtin/rebase.c b/builtin/rebase.c index 08b89869b3..bc4fc69906 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1376,8 +1376,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - OPT_BOOL(0, "autostash", &options.autostash, - N_("automatically stash/stash pop before and after")), + OPT_AUTOSTASH(&options.autostash), OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), diff --git a/parse-options.h b/parse-options.h index fece5ba628..46af942093 100644 --- a/parse-options.h +++ b/parse-options.h @@ -336,5 +336,6 @@ int parse_opt_passthru_argv(const struct option *, const char *, int); #define OPT_CLEANUP(v) OPT_STRING(0, "cleanup", v, N_("mode"), N_("how to strip spaces and #comments from message")) #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file")) #define OPT_PATHSPEC_FILE_NUL(v) OPT_BOOL(0, "pathspec-file-nul", v, N_("with --pathspec-from-file, pathspec elements are separated with NUL character")) +#define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after")) #endif diff --git a/path.c b/path.c index 88cf593007..d764738146 100644 --- a/path.c +++ b/path.c @@ -1535,5 +1535,6 @@ REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG") REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR") REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE") REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD") +REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH") REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD") REPO_GIT_PATH_FUNC(shallow, "shallow") diff --git a/path.h b/path.h index 14d6dcad16..1f1bf8f87a 100644 --- a/path.h +++ b/path.h @@ -177,11 +177,12 @@ struct path_cache { const char *merge_rr; const char *merge_mode; const char *merge_head; + const char *merge_autostash; const char *fetch_head; const char *shallow; }; -#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } +#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } const char *git_path_cherry_pick_head(struct repository *r); const char *git_path_revert_head(struct repository *r); @@ -190,6 +191,7 @@ const char *git_path_merge_msg(struct repository *r); const char *git_path_merge_rr(struct repository *r); const char *git_path_merge_mode(struct repository *r); const char *git_path_merge_head(struct repository *r); +const char *git_path_merge_autostash(struct repository *r); const char *git_path_fetch_head(struct repository *r); const char *git_path_shallow(struct repository *r); diff --git a/t/t3033-merge-toplevel.sh b/t/t3033-merge-toplevel.sh index d314599428..e29c284b9b 100755 --- a/t/t3033-merge-toplevel.sh +++ b/t/t3033-merge-toplevel.sh @@ -142,6 +142,17 @@ test_expect_success 'refuse two-project merge by default' ' test_must_fail git merge five ' +test_expect_success 'refuse two-project merge by default, quit before --autostash happens' ' + t3033_reset && + git reset --hard four && + echo change >>one.t && + git diff >expect && + test_must_fail git merge --autostash five 2>err && + test_i18ngrep ! "stash" err && + git diff >actual && + test_cmp expect actual +' + test_expect_success 'two-project merge with --allow-unrelated-histories' ' t3033_reset && git reset --hard four && @@ -149,4 +160,15 @@ test_expect_success 'two-project merge with --allow-unrelated-histories' ' git diff --exit-code five ' +test_expect_success 'two-project merge with --allow-unrelated-histories with --autostash' ' + t3033_reset && + git reset --hard four && + echo change >>one.t && + git diff one.t >expect && + git merge --allow-unrelated-histories --autostash five 2>err && + test_i18ngrep "Applied autostash." err && + git diff one.t >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 4fa0ef8e3b..5883a6adc3 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -30,13 +30,17 @@ Testing basic merge operations/option parsing. . "$TEST_DIRECTORY"/lib-gpg.sh test_write_lines 1 2 3 4 5 6 7 8 9 >file +cp file file.orig test_write_lines '1 X' 2 3 4 5 6 7 8 9 >file.1 +test_write_lines 1 2 '3 X' 4 5 6 7 8 9 >file.3 test_write_lines 1 2 3 4 '5 X' 6 7 8 9 >file.5 test_write_lines 1 2 3 4 5 6 7 8 '9 X' >file.9 test_write_lines 1 2 3 4 5 6 7 8 '9 Y' >file.9y test_write_lines '1 X' 2 3 4 5 6 7 8 9 >result.1 test_write_lines '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5 +test_write_lines '1 X' 2 3 4 5 6 7 8 '9 X' >result.1-9 test_write_lines '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9 +test_write_lines '1 X' 2 '3 X' 4 '5 X' 6 7 8 '9 X' >result.1-3-5-9 test_write_lines 1 2 3 4 5 6 7 8 '9 Z' >result.9z create_merge_msgs () { @@ -675,6 +679,134 @@ test_expect_success 'refresh the index before merging' ' git merge c3 ' +test_expect_success 'merge with --autostash' ' + git reset --hard c1 && + git merge-file file file.orig file.9 && + git merge --autostash c2 2>err && + test_i18ngrep "Applied autostash." err && + git show HEAD:file >merge-result && + test_cmp result.1-5 merge-result && + test_cmp result.1-5-9 file +' + +test_expect_success 'merge with merge.autoStash' ' + test_config merge.autoStash true && + git reset --hard c1 && + git merge-file file file.orig file.9 && + git merge c2 2>err && + test_i18ngrep "Applied autostash." err && + git show HEAD:file >merge-result && + test_cmp result.1-5 merge-result && + test_cmp result.1-5-9 file +' + +test_expect_success 'fast-forward merge with --autostash' ' + git reset --hard c0 && + git merge-file file file.orig file.5 && + git merge --autostash c1 2>err && + test_i18ngrep "Applied autostash." err && + test_cmp result.1-5 file +' + +test_expect_success 'octopus merge with --autostash' ' + git reset --hard c1 && + git merge-file file file.orig file.3 && + git merge --autostash c2 c3 2>err && + test_i18ngrep "Applied autostash." err && + git show HEAD:file >merge-result && + test_cmp result.1-5-9 merge-result && + test_cmp result.1-3-5-9 file +' + +test_expect_success 'conflicted merge with --autostash, --abort restores stash' ' + git reset --hard c3 && + cp file.1 file && + test_must_fail git merge --autostash c7 && + git merge --abort 2>err && + test_i18ngrep "Applied autostash." err && + test_cmp file.1 file +' + +test_expect_success 'completed merge (git commit) with --no-commit and --autostash' ' + git reset --hard c1 && + git merge-file file file.orig file.9 && + git diff >expect && + git merge --no-commit --autostash c2 && + git stash show -p MERGE_AUTOSTASH >actual && + test_cmp expect actual && + git commit 2>err && + test_i18ngrep "Applied autostash." err && + git show HEAD:file >merge-result && + test_cmp result.1-5 merge-result && + test_cmp result.1-5-9 file +' + +test_expect_success 'completed merge (git merge --continue) with --no-commit and --autostash' ' + git reset --hard c1 && + git merge-file file file.orig file.9 && + git diff >expect && + git merge --no-commit --autostash c2 && + git stash show -p MERGE_AUTOSTASH >actual && + test_cmp expect actual && + git merge --continue 2>err && + test_i18ngrep "Applied autostash." err && + git show HEAD:file >merge-result && + test_cmp result.1-5 merge-result && + test_cmp result.1-5-9 file +' + +test_expect_success 'aborted merge (merge --abort) with --no-commit and --autostash' ' + git reset --hard c1 && + git merge-file file file.orig file.9 && + git diff >expect && + git merge --no-commit --autostash c2 && + git stash show -p MERGE_AUTOSTASH >actual && + test_cmp expect actual && + git merge --abort 2>err && + test_i18ngrep "Applied autostash." err && + git diff >actual && + test_cmp expect actual +' + +test_expect_success 'aborted merge (reset --hard) with --no-commit and --autostash' ' + git reset --hard c1 && + git merge-file file file.orig file.9 && + git diff >expect && + git merge --no-commit --autostash c2 && + git stash show -p MERGE_AUTOSTASH >actual && + test_cmp expect actual && + git reset --hard 2>err && + test_i18ngrep "Autostash exists; creating a new stash entry." err && + git diff --exit-code +' + +test_expect_success 'quit merge with --no-commit and --autostash' ' + git reset --hard c1 && + git merge-file file file.orig file.9 && + git diff >expect && + git merge --no-commit --autostash c2 && + git stash show -p MERGE_AUTOSTASH >actual && + test_cmp expect actual && + git diff HEAD >expect && + git merge --quit 2>err && + test_i18ngrep "Autostash exists; creating a new stash entry." err && + git diff HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'merge with conflicted --autostash changes' ' + git reset --hard c1 && + git merge-file file file.orig file.9y && + git diff >expect && + test_when_finished "test_might_fail git stash drop" && + git merge --autostash c3 2>err && + test_i18ngrep "Applying autostash resulted in conflicts." err && + git show HEAD:file >merge-result && + test_cmp result.1-9 merge-result && + git stash show -p >actual && + test_cmp expect actual +' + cat >expected.branch <<\EOF Merge branch 'c5-branch' (early part) EOF -- cgit v1.3 From d9f15d37f1a545305988f6b2bb40de1fbf0049cf Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 7 Apr 2020 10:28:09 -0400 Subject: pull: pass --autostash to merge Before, `--autostash` only worked with `git pull --rebase`. However, in the last patch, merge learned `--autostash` as well so there's no reason why we should have this restriction anymore. Teach pull to pass `--autostash` to merge, just like it did for rebase. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- Documentation/git-pull.txt | 9 --------- Documentation/merge-options.txt | 4 ++-- builtin/pull.c | 9 +++++---- t/t5520-pull.sh | 43 +++++++++++++++++++++++++++++++++-------- 4 files changed, 42 insertions(+), 23 deletions(-) (limited to 'builtin') diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index dfb901f8b8..ba3772de9f 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -133,15 +133,6 @@ unless you have read linkgit:git-rebase[1] carefully. --no-rebase:: Override earlier --rebase. ---autostash:: ---no-autostash:: - Before starting rebase, stash local modifications away (see - linkgit:git-stash[1]) if needed, and apply the stash entry when - done. `--no-autostash` is useful to override the `rebase.autoStash` - configuration variable (see linkgit:git-config[1]). -+ -This option is only valid when "--rebase" is used. - Options related to fetching ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 3985e6d4a9..48bfcda084 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -155,6 +155,8 @@ ifndef::git-pull[] Note that not all merge strategies may support progress reporting. +endif::git-pull[] + --autostash:: --no-autostash:: Automatically create a temporary stash entry before the operation @@ -163,8 +165,6 @@ ifndef::git-pull[] with care: the final stash application after a successful merge might result in non-trivial conflicts. -endif::git-pull[] - --allow-unrelated-histories:: By default, `git merge` command refuses to merge histories that do not share a common ancestor. This option can be diff --git a/builtin/pull.c b/builtin/pull.c index 3e624d1e00..9beb4841d1 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -163,7 +163,7 @@ static struct option pull_options[] = { N_("verify that the named commit has a valid GPG signature"), PARSE_OPT_NOARG), OPT_BOOL(0, "autostash", &opt_autostash, - N_("automatically stash/stash pop before and after rebase")), + N_("automatically stash/stash pop before and after")), OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), N_("merge strategy to use"), 0), @@ -661,6 +661,10 @@ static int run_merge(void) argv_array_pushv(&args, opt_strategy_opts.argv); if (opt_gpg_sign) argv_array_push(&args, opt_gpg_sign); + if (opt_autostash == 0) + argv_array_push(&args, "--no-autostash"); + else if (opt_autostash == 1) + argv_array_push(&args, "--autostash"); if (opt_allow_unrelated_histories > 0) argv_array_push(&args, "--allow-unrelated-histories"); @@ -908,9 +912,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (get_oid("HEAD", &orig_head)) oidclr(&orig_head); - if (!opt_rebase && opt_autostash != -1) - die(_("--[no-]autostash option is only valid with --rebase.")); - autostash = config_autostash; if (opt_rebase) { if (opt_autostash != -1) diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index f610dc14de..37535d63a9 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -28,7 +28,7 @@ test_pull_autostash_fail () { echo dirty >new_file && git add new_file && test_must_fail git pull "$@" . copy 2>err && - test_i18ngrep "uncommitted changes." err + test_i18ngrep "\(uncommitted changes.\)\|\(overwritten by merge:\)" err } test_expect_success setup ' @@ -404,13 +404,40 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' ' test_pull_autostash_fail --rebase --no-autostash ' -for i in --autostash --no-autostash -do - test_expect_success "pull $i (without --rebase) is illegal" ' - test_must_fail git pull $i . copy 2>err && - test_i18ngrep "only valid with --rebase" err - ' -done +test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' ' + test_config merge.autostash true && + test_pull_autostash 2 +' + +test_expect_success 'pull --autostash & merge.autostash=true' ' + test_config merge.autostash true && + test_pull_autostash 2 --autostash +' + +test_expect_success 'pull --autostash & merge.autostash=false' ' + test_config merge.autostash false && + test_pull_autostash 2 --autostash +' + +test_expect_success 'pull --autostash & merge.autostash unset' ' + test_unconfig merge.autostash && + test_pull_autostash 2 --autostash +' + +test_expect_success 'pull --no-autostash & merge.autostash=true' ' + test_config merge.autostash true && + test_pull_autostash_fail --no-autostash +' + +test_expect_success 'pull --no-autostash & merge.autostash=false' ' + test_config merge.autostash false && + test_pull_autostash_fail --no-autostash +' + +test_expect_success 'pull --no-autostash & merge.autostash unset' ' + test_unconfig merge.autostash && + test_pull_autostash_fail --no-autostash +' test_expect_success 'pull.rebase' ' git reset --hard before-rebase && -- cgit v1.3