зеркало из https://github.com/microsoft/git.git
Merge branch 'pw/use-in-process-checkout-in-rebase'
Use an internal call to reset_head() helper function instead of spawning "git checkout" in "rebase", and update code paths that are involved in the change. * pw/use-in-process-checkout-in-rebase: rebase -m: don't fork git checkout rebase --apply: set ORIG_HEAD correctly rebase --apply: fix reflog reset_head(): take struct rebase_head_opts rebase: cleanup reset_head() calls create_autostash(): remove unneeded parameter reset_head(): make default_reflog_action optional reset_head(): factor out ref updates reset_head(): remove action parameter rebase --apply: don't run post-checkout hook if there is an error rebase: do not remove untracked files on checkout rebase: pass correct arguments to post-checkout hook t5403: refactor rebase post-checkout hook tests rebase: factor out checkout for up to date branch
This commit is contained in:
Коммит
bcd020f88e
|
@ -1568,8 +1568,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||
|
||||
if (autostash)
|
||||
create_autostash(the_repository,
|
||||
git_path_merge_autostash(the_repository),
|
||||
"merge");
|
||||
git_path_merge_autostash(the_repository));
|
||||
if (checkout_fast_forward(the_repository,
|
||||
&head_commit->object.oid,
|
||||
&commit->object.oid,
|
||||
|
@ -1640,8 +1639,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||
|
||||
if (autostash)
|
||||
create_autostash(the_repository,
|
||||
git_path_merge_autostash(the_repository),
|
||||
"merge");
|
||||
git_path_merge_autostash(the_repository));
|
||||
|
||||
/* We are going to make a new commit. */
|
||||
git_committer_info(IDENT_STRICT);
|
||||
|
|
101
builtin/rebase.c
101
builtin/rebase.c
|
@ -571,7 +571,8 @@ static int finish_rebase(struct rebase_options *opts)
|
|||
|
||||
static int move_to_original_branch(struct rebase_options *opts)
|
||||
{
|
||||
struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
|
||||
struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
|
||||
struct reset_head_opts ropts = { 0 };
|
||||
int ret;
|
||||
|
||||
if (!opts->head_name)
|
||||
|
@ -580,16 +581,17 @@ static int move_to_original_branch(struct rebase_options *opts)
|
|||
if (!opts->onto)
|
||||
BUG("move_to_original_branch without onto");
|
||||
|
||||
strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
|
||||
strbuf_addf(&branch_reflog, "rebase finished: %s onto %s",
|
||||
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(the_repository, NULL, "", opts->head_name,
|
||||
RESET_HEAD_REFS_ONLY,
|
||||
orig_head_reflog.buf, head_reflog.buf,
|
||||
DEFAULT_REFLOG_ACTION);
|
||||
ropts.branch = opts->head_name;
|
||||
ropts.flags = RESET_HEAD_REFS_ONLY;
|
||||
ropts.branch_msg = branch_reflog.buf;
|
||||
ropts.head_msg = head_reflog.buf;
|
||||
ret = reset_head(the_repository, &ropts);
|
||||
|
||||
strbuf_release(&orig_head_reflog);
|
||||
strbuf_release(&branch_reflog);
|
||||
strbuf_release(&head_reflog);
|
||||
return ret;
|
||||
}
|
||||
|
@ -671,13 +673,15 @@ static int run_am(struct rebase_options *opts)
|
|||
|
||||
status = run_command(&format_patch);
|
||||
if (status) {
|
||||
struct reset_head_opts ropts = { 0 };
|
||||
unlink(rebased_patches);
|
||||
free(rebased_patches);
|
||||
strvec_clear(&am.args);
|
||||
|
||||
reset_head(the_repository, &opts->orig_head, "checkout",
|
||||
opts->head_name, 0,
|
||||
"HEAD", NULL, DEFAULT_REFLOG_ACTION);
|
||||
ropts.oid = &opts->orig_head;
|
||||
ropts.branch = opts->head_name;
|
||||
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
|
||||
reset_head(the_repository, &ropts);
|
||||
error(_("\ngit encountered an error while preparing the "
|
||||
"patches to replay\n"
|
||||
"these revisions:\n"
|
||||
|
@ -813,6 +817,26 @@ static int rebase_config(const char *var, const char *value, void *data)
|
|||
return git_default_config(var, value, data);
|
||||
}
|
||||
|
||||
static int checkout_up_to_date(struct rebase_options *options)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reset_head_opts ropts = { 0 };
|
||||
int ret = 0;
|
||||
|
||||
strbuf_addf(&buf, "%s: checkout %s",
|
||||
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
|
||||
options->switch_to);
|
||||
ropts.oid = &options->orig_head;
|
||||
ropts.branch = options->head_name;
|
||||
ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
|
||||
ropts.head_msg = buf.buf;
|
||||
if (reset_head(the_repository, &ropts) < 0)
|
||||
ret = error(_("could not switch to %s"), options->switch_to);
|
||||
strbuf_release(&buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determines whether the commits in from..to are linear, i.e. contain
|
||||
* no merge commits. This function *expects* `from` to be an ancestor of
|
||||
|
@ -1018,6 +1042,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||
int reschedule_failed_exec = -1;
|
||||
int allow_preemptive_ff = 1;
|
||||
int preserve_merges_selected = 0;
|
||||
struct reset_head_opts ropts = { 0 };
|
||||
struct option builtin_rebase_options[] = {
|
||||
OPT_STRING(0, "onto", &options.onto_name,
|
||||
N_("revision"),
|
||||
|
@ -1255,9 +1280,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(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
|
||||
NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
|
||||
ropts.flags = RESET_HEAD_HARD;
|
||||
if (reset_head(the_repository, &ropts) < 0)
|
||||
die(_("could not discard worktree changes"));
|
||||
remove_branch_state(the_repository, 0);
|
||||
if (read_basic_state(&options))
|
||||
|
@ -1274,9 +1298,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||
|
||||
if (read_basic_state(&options))
|
||||
exit(1);
|
||||
if (reset_head(the_repository, &options.orig_head, "reset",
|
||||
options.head_name, RESET_HEAD_HARD,
|
||||
NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
|
||||
ropts.oid = &options.orig_head;
|
||||
ropts.branch = options.head_name;
|
||||
ropts.flags = RESET_HEAD_HARD;
|
||||
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
|
||||
if (reset_head(the_repository, &ropts) < 0)
|
||||
die(_("could not move back to %s"),
|
||||
oid_to_hex(&options.orig_head));
|
||||
remove_branch_state(the_repository, 0);
|
||||
|
@ -1642,10 +1668,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||
if (repo_read_index(the_repository) < 0)
|
||||
die(_("could not read index"));
|
||||
|
||||
if (options.autostash) {
|
||||
create_autostash(the_repository, state_dir_path("autostash", &options),
|
||||
DEFAULT_REFLOG_ACTION);
|
||||
}
|
||||
if (options.autostash)
|
||||
create_autostash(the_repository,
|
||||
state_dir_path("autostash", &options));
|
||||
|
||||
|
||||
if (require_clean_work_tree(the_repository, "rebase",
|
||||
_("Please commit or stash them."), 1, 1)) {
|
||||
|
@ -1674,21 +1700,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||
if (!(options.flags & REBASE_FORCE)) {
|
||||
/* Lazily switch to the target branch if needed... */
|
||||
if (options.switch_to) {
|
||||
strbuf_reset(&buf);
|
||||
strbuf_addf(&buf, "%s: checkout %s",
|
||||
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
|
||||
options.switch_to);
|
||||
if (reset_head(the_repository,
|
||||
&options.orig_head, "checkout",
|
||||
options.head_name,
|
||||
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
|
||||
NULL, buf.buf,
|
||||
DEFAULT_REFLOG_ACTION) < 0) {
|
||||
ret = error(_("could not switch to "
|
||||
"%s"),
|
||||
options.switch_to);
|
||||
ret = checkout_up_to_date(&options);
|
||||
if (ret)
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(options.flags & REBASE_NO_QUIET))
|
||||
|
@ -1755,10 +1769,13 @@ 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(the_repository, &options.onto->object.oid, "checkout", NULL,
|
||||
RESET_HEAD_DETACH | RESET_ORIG_HEAD |
|
||||
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
|
||||
NULL, msg.buf, DEFAULT_REFLOG_ACTION))
|
||||
ropts.oid = &options.onto->object.oid;
|
||||
ropts.orig_head = &options.orig_head,
|
||||
ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
|
||||
RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
|
||||
ropts.head_msg = msg.buf;
|
||||
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
|
||||
if (reset_head(the_repository, &ropts))
|
||||
die(_("Could not detach HEAD"));
|
||||
strbuf_release(&msg);
|
||||
|
||||
|
@ -1773,9 +1790,11 @@ 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(the_repository, NULL, "Fast-forwarded", options.head_name,
|
||||
RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
|
||||
DEFAULT_REFLOG_ACTION);
|
||||
memset(&ropts, 0, sizeof(ropts));
|
||||
ropts.branch = options.head_name;
|
||||
ropts.flags = RESET_HEAD_REFS_ONLY;
|
||||
ropts.head_msg = msg.buf;
|
||||
reset_head(the_repository, &ropts);
|
||||
strbuf_release(&msg);
|
||||
ret = finish_rebase(&options);
|
||||
goto cleanup;
|
||||
|
|
149
reset.c
149
reset.c
|
@ -9,37 +9,106 @@
|
|||
#include "unpack-trees.h"
|
||||
#include "hook.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)
|
||||
static int update_refs(const struct reset_head_opts *opts,
|
||||
const struct object_id *oid,
|
||||
const struct object_id *head)
|
||||
{
|
||||
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;
|
||||
unsigned detach_head = opts->flags & RESET_HEAD_DETACH;
|
||||
unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
|
||||
unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
|
||||
const struct object_id *orig_head = opts->orig_head;
|
||||
const char *switch_to_branch = opts->branch;
|
||||
const char *reflog_branch = opts->branch_msg;
|
||||
const char *reflog_head = opts->head_msg;
|
||||
const char *reflog_orig_head = opts->orig_head_msg;
|
||||
const char *default_reflog_action = opts->default_reflog_action;
|
||||
struct object_id *old_orig = NULL, oid_old_orig;
|
||||
struct strbuf msg = STRBUF_INIT;
|
||||
const char *reflog_action;
|
||||
size_t prefix_len;
|
||||
int ret;
|
||||
|
||||
if ((update_orig_head && !reflog_orig_head) || !reflog_head) {
|
||||
if (!default_reflog_action)
|
||||
BUG("default_reflog_action must be given when reflog messages are omitted");
|
||||
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 (head) {
|
||||
if (!reflog_orig_head) {
|
||||
strbuf_addstr(&msg, "updating ORIG_HEAD");
|
||||
reflog_orig_head = msg.buf;
|
||||
}
|
||||
update_ref(reflog_orig_head, "ORIG_HEAD",
|
||||
orig_head ? orig_head : head,
|
||||
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, head,
|
||||
detach_head ? REF_NO_DEREF : 0,
|
||||
UPDATE_REFS_MSG_ON_ERR);
|
||||
else {
|
||||
ret = update_ref(reflog_branch ? reflog_branch : 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 (!ret && run_hook)
|
||||
run_hooks_l("post-checkout",
|
||||
oid_to_hex(head ? head : null_oid()),
|
||||
oid_to_hex(oid), "1", NULL);
|
||||
strbuf_release(&msg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int reset_head(struct repository *r, const struct reset_head_opts *opts)
|
||||
{
|
||||
const struct object_id *oid = opts->oid;
|
||||
const char *switch_to_branch = opts->branch;
|
||||
unsigned reset_hard = opts->flags & RESET_HEAD_HARD;
|
||||
unsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;
|
||||
unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
|
||||
struct object_id *head = NULL, head_oid;
|
||||
struct tree_desc desc[2] = { { NULL }, { NULL } };
|
||||
struct lock_file lock = LOCK_INIT;
|
||||
struct unpack_trees_options unpack_tree_opts = { 0 };
|
||||
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;
|
||||
const char *action;
|
||||
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 (opts->orig_head_msg && !update_orig_head)
|
||||
BUG("ORIG_HEAD reflog message given without updating ORIG_HEAD");
|
||||
|
||||
if (opts->branch_msg && !opts->branch)
|
||||
BUG("branch reflog message given without a 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)) {
|
||||
if (!get_oid("HEAD", &head_oid)) {
|
||||
head = &head_oid;
|
||||
} else if (!oid || !reset_hard) {
|
||||
ret = error(_("could not determine HEAD revision"));
|
||||
goto leave_reset_head;
|
||||
}
|
||||
|
@ -48,8 +117,9 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
|
|||
oid = &head_oid;
|
||||
|
||||
if (refs_only)
|
||||
goto reset_head_refs;
|
||||
return update_refs(opts, oid, head);
|
||||
|
||||
action = reset_hard ? "reset" : "checkout";
|
||||
setup_unpack_trees_porcelain(&unpack_tree_opts, action);
|
||||
unpack_tree_opts.head_idx = 1;
|
||||
unpack_tree_opts.src_index = r->index;
|
||||
|
@ -59,7 +129,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
|
|||
unpack_tree_opts.merge = 1;
|
||||
unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
|
||||
init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
|
||||
if (!detach_head)
|
||||
if (reset_hard)
|
||||
unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
|
||||
|
||||
if (repo_read_index_unmerged(r) < 0) {
|
||||
|
@ -91,49 +161,10 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
|
|||
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_hooks_l("post-checkout",
|
||||
oid_to_hex(orig ? orig : null_oid()),
|
||||
oid_to_hex(oid), "1", NULL);
|
||||
if (oid != &head_oid || update_orig_head || switch_to_branch)
|
||||
ret = update_refs(opts, oid, head);
|
||||
|
||||
leave_reset_head:
|
||||
strbuf_release(&msg);
|
||||
rollback_lock_file(&lock);
|
||||
clear_unpack_trees_porcelain(&unpack_tree_opts);
|
||||
while (nr)
|
||||
|
|
48
reset.h
48
reset.h
|
@ -6,15 +6,55 @@
|
|||
|
||||
#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
|
||||
|
||||
/* Request a detached checkout */
|
||||
#define RESET_HEAD_DETACH (1<<0)
|
||||
/* Request a reset rather than a checkout */
|
||||
#define RESET_HEAD_HARD (1<<1)
|
||||
/* Run the post-checkout hook */
|
||||
#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
|
||||
/* Only update refs, do not touch the worktree */
|
||||
#define RESET_HEAD_REFS_ONLY (1<<3)
|
||||
/* Update ORIG_HEAD as well as HEAD */
|
||||
#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);
|
||||
struct reset_head_opts {
|
||||
/*
|
||||
* The commit to checkout/reset to. Defaults to HEAD.
|
||||
*/
|
||||
const struct object_id *oid;
|
||||
/*
|
||||
* Optional value to set ORIG_HEAD. Defaults to HEAD.
|
||||
*/
|
||||
const struct object_id *orig_head;
|
||||
/*
|
||||
* Optional branch to switch to.
|
||||
*/
|
||||
const char *branch;
|
||||
/*
|
||||
* Flags defined above.
|
||||
*/
|
||||
unsigned flags;
|
||||
/*
|
||||
* Optional reflog message for branch, defaults to head_msg.
|
||||
*/
|
||||
const char *branch_msg;
|
||||
/*
|
||||
* Optional reflog message for HEAD, if this omitted but oid or branch
|
||||
* are given then default_reflog_action must be given.
|
||||
*/
|
||||
const char *head_msg;
|
||||
/*
|
||||
* Optional reflog message for ORIG_HEAD, if this omitted and flags
|
||||
* contains RESET_ORIG_HEAD then default_reflog_action must be given.
|
||||
*/
|
||||
const char *orig_head_msg;
|
||||
/*
|
||||
* Action to use in default reflog messages, only required if a ref is
|
||||
* being updated and the reflog messages above are omitted.
|
||||
*/
|
||||
const char *default_reflog_action;
|
||||
};
|
||||
|
||||
int reset_head(struct repository *r, const struct reset_head_opts *opts);
|
||||
|
||||
#endif
|
||||
|
|
52
sequencer.c
52
sequencer.c
|
@ -4085,8 +4085,7 @@ 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)
|
||||
void create_autostash(struct repository *r, const char *path)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct lock_file lock_file = LOCK_INIT;
|
||||
|
@ -4101,6 +4100,7 @@ void create_autostash(struct repository *r, const char *path,
|
|||
if (has_unstaged_changes(r, 1) ||
|
||||
has_uncommitted_changes(r, 1)) {
|
||||
struct child_process stash = CHILD_PROCESS_INIT;
|
||||
struct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };
|
||||
struct object_id oid;
|
||||
|
||||
strvec_pushl(&stash.args,
|
||||
|
@ -4122,11 +4122,8 @@ void create_autostash(struct repository *r, const char *path,
|
|||
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)
|
||||
if (reset_head(r, &ropts) < 0)
|
||||
die(_("could not reset --hard"));
|
||||
|
||||
if (discard_index(r->index) < 0 ||
|
||||
repo_read_index(r) < 0)
|
||||
die(_("could not read index"));
|
||||
|
@ -4211,47 +4208,26 @@ int apply_autostash_oid(const char *stash_oid)
|
|||
return apply_save_autostash_oid(stash_oid, 1);
|
||||
}
|
||||
|
||||
static int run_git_checkout(struct repository *r, struct replay_opts *opts,
|
||||
const char *commit, const char *action)
|
||||
{
|
||||
struct child_process cmd = CHILD_PROCESS_INIT;
|
||||
int ret;
|
||||
|
||||
cmd.git_cmd = 1;
|
||||
|
||||
if (startup_info->original_cwd) {
|
||||
cmd.dir = startup_info->original_cwd;
|
||||
strvec_pushf(&cmd.env_array, "%s=%s",
|
||||
GIT_WORK_TREE_ENVIRONMENT, r->worktree);
|
||||
}
|
||||
strvec_push(&cmd.args, "checkout");
|
||||
strvec_push(&cmd.args, commit);
|
||||
strvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
|
||||
|
||||
if (opts->verbose)
|
||||
ret = run_command(&cmd);
|
||||
else
|
||||
ret = run_command_silent_on_success(&cmd);
|
||||
|
||||
if (!ret)
|
||||
discard_index(r->index);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int checkout_onto(struct repository *r, struct replay_opts *opts,
|
||||
const char *onto_name, const struct object_id *onto,
|
||||
const struct object_id *orig_head)
|
||||
{
|
||||
const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
|
||||
|
||||
if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
|
||||
struct reset_head_opts ropts = {
|
||||
.oid = onto,
|
||||
.orig_head = orig_head,
|
||||
.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
|
||||
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
|
||||
.head_msg = reflog_message(opts, "start", "checkout %s",
|
||||
onto_name),
|
||||
.default_reflog_action = "rebase"
|
||||
};
|
||||
if (reset_head(r, &ropts)) {
|
||||
apply_autostash(rebase_path_autostash());
|
||||
sequencer_remove_state(opts);
|
||||
return error(_("could not detach HEAD"));
|
||||
}
|
||||
|
||||
return update_ref(NULL, "ORIG_HEAD", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stopped_at_head(struct repository *r)
|
||||
|
|
|
@ -197,8 +197,7 @@ 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);
|
||||
void create_autostash(struct repository *r, const char *path);
|
||||
int save_autostash(const char *path);
|
||||
int apply_autostash(const char *path);
|
||||
int apply_autostash_oid(const char *stash_oid);
|
||||
|
|
|
@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' '
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase --apply reflog' '
|
||||
git checkout -b reflog-apply start &&
|
||||
old_head_reflog="$(git log -g --format=%gs -1 HEAD)" &&
|
||||
|
||||
git rebase --apply Y &&
|
||||
|
||||
git log -g --format=%gs -4 HEAD >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
rebase finished: returning to refs/heads/reflog-apply
|
||||
rebase: Z
|
||||
rebase: checkout Y
|
||||
$old_head_reflog
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
|
||||
git log -g --format=%gs -2 reflog-apply >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
rebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)
|
||||
branch: Created from start
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i onto unrelated history' '
|
||||
git init unrelated &&
|
||||
test_commit -C unrelated 1 &&
|
||||
|
|
|
@ -308,4 +308,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas
|
|||
test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
|
||||
'
|
||||
|
||||
test_orig_head_helper () {
|
||||
test_when_finished 'git rebase --abort &&
|
||||
git checkout topic &&
|
||||
git reset --hard commit-new-file-F2-on-topic-branch' &&
|
||||
git update-ref -d ORIG_HEAD &&
|
||||
test_must_fail git rebase "$@" &&
|
||||
test_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch
|
||||
}
|
||||
|
||||
test_orig_head () {
|
||||
type=$1
|
||||
test_expect_success "rebase $type sets ORIG_HEAD correctly" '
|
||||
git checkout topic &&
|
||||
git reset --hard commit-new-file-F2-on-topic-branch &&
|
||||
test_orig_head_helper $type main
|
||||
'
|
||||
|
||||
test_expect_success "rebase $type <upstream> <branch> sets ORIG_HEAD correctly" '
|
||||
git checkout main &&
|
||||
test_orig_head_helper $type main topic
|
||||
'
|
||||
}
|
||||
|
||||
test_orig_head --apply
|
||||
test_orig_head --merge
|
||||
|
||||
test_done
|
||||
|
|
|
@ -49,23 +49,60 @@ test_expect_success 'post-checkout receives the right args when not switching br
|
|||
test $old = $new && test $flag = 0
|
||||
'
|
||||
|
||||
test_expect_success 'post-checkout is triggered on rebase' '
|
||||
test_when_finished "rm -f .git/post-checkout.args" &&
|
||||
git checkout -b rebase-test main &&
|
||||
rm -f .git/post-checkout.args &&
|
||||
git rebase rebase-on-me &&
|
||||
read old new flag <.git/post-checkout.args &&
|
||||
test $old != $new && test $flag = 1
|
||||
'
|
||||
test_rebase () {
|
||||
args="$*" &&
|
||||
test_expect_success "post-checkout is triggered on rebase $args" '
|
||||
test_when_finished "rm -f .git/post-checkout.args" &&
|
||||
git checkout -B rebase-test main &&
|
||||
rm -f .git/post-checkout.args &&
|
||||
git rebase $args rebase-on-me &&
|
||||
read old new flag <.git/post-checkout.args &&
|
||||
test_cmp_rev main $old &&
|
||||
test_cmp_rev rebase-on-me $new &&
|
||||
test $flag = 1
|
||||
'
|
||||
|
||||
test_expect_success "post-checkout is triggered on rebase $args with fast-forward" '
|
||||
test_when_finished "rm -f .git/post-checkout.args" &&
|
||||
git checkout -B ff-rebase-test rebase-on-me^ &&
|
||||
rm -f .git/post-checkout.args &&
|
||||
git rebase $args rebase-on-me &&
|
||||
read old new flag <.git/post-checkout.args &&
|
||||
test_cmp_rev rebase-on-me^ $old &&
|
||||
test_cmp_rev rebase-on-me $new &&
|
||||
test $flag = 1
|
||||
'
|
||||
|
||||
test_expect_success "rebase $args fast-forward branch checkout runs post-checkout hook" '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
test_when_finished "rm -f .git/post-checkout.args" &&
|
||||
git update-ref refs/heads/rebase-fast-forward three &&
|
||||
git checkout two &&
|
||||
rm -f .git/post-checkout.args &&
|
||||
git rebase $args HEAD rebase-fast-forward &&
|
||||
read old new flag <.git/post-checkout.args &&
|
||||
test_cmp_rev two $old &&
|
||||
test_cmp_rev three $new &&
|
||||
test $flag = 1
|
||||
'
|
||||
|
||||
test_expect_success "rebase $args checkout does not remove untracked files" '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
test_when_finished "rm -f .git/post-checkout.args" &&
|
||||
git update-ref refs/heads/rebase-fast-forward three &&
|
||||
git checkout two &&
|
||||
rm -f .git/post-checkout.args &&
|
||||
echo untracked >three.t &&
|
||||
test_when_finished "rm three.t" &&
|
||||
test_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&
|
||||
grep "untracked working tree files would be overwritten by checkout" err &&
|
||||
test_path_is_missing .git/post-checkout.args
|
||||
|
||||
test_expect_success 'post-checkout is triggered on rebase with fast-forward' '
|
||||
test_when_finished "rm -f .git/post-checkout.args" &&
|
||||
git checkout -b ff-rebase-test rebase-on-me^ &&
|
||||
rm -f .git/post-checkout.args &&
|
||||
git rebase rebase-on-me &&
|
||||
read old new flag <.git/post-checkout.args &&
|
||||
test $old != $new && test $flag = 1
|
||||
'
|
||||
}
|
||||
|
||||
test_rebase --apply &&
|
||||
test_rebase --merge
|
||||
|
||||
test_expect_success 'post-checkout hook is triggered by clone' '
|
||||
mkdir -p templates/hooks &&
|
||||
|
|
Загрузка…
Ссылка в новой задаче