{"thread":{"id":"56620","subject":"[PATCH 00/11] rebase: reset_head() related fixes and improvements","startedAt":"2021-10-01T10:05:07Z","lastAt":"2022-02-01T17:03:49Z","messageCount":85,"participants":["Phillip Wood via GitGitGadget","Junio C Hamano","Eric Sunshine","Phillip Wood","René Scharfe","Ævar Arnfjörð Bjarmason","Elijah Newren"],"isPatch":true,"patchVersion":1,"patchTotal":11},"messages":[{"id":"437655","messageId":"pull.1049.git.1633082702.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":null,"subject":"[PATCH 00/11] rebase: reset_head() related fixes and improvements","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:51Z","receivedAt":"2021-10-01T10:05:07Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Fix some issues with the implementation and use of reset_head(). The last\npatch was previously posted as [1], I have updated the commit message and\nrebased it onto the fixes in this series. There are a couple of small\nconflicts merging this into seen, I think they should be easy to resolve (in\nrebase.c take both sides in reset.c take the changed lines from each side).\nThese patches are based on pw/rebase-of-a-tag-fix\n\n[1]\nhttps://lore.kernel.org/git/39ad40c9297531a2d42b7263a1d41b1ecbc23c0a.1631108472.git.gitgitgadget@gmail.com/\n\nPhillip Wood (11):\n  rebase: factor out checkout for up to date branch\n  reset_head(): fix checkout\n  reset_head(): don't run checkout hook if there is an error\n  reset_head(): remove action parameter\n  reset_head(): factor out ref updates\n  reset_head(): make default_reflog_action optional\n  rebase: cleanup reset_head() calls\n  reset_head(): take struct rebase_head_opts\n  rebase --apply: fix reflog\n  rebase --apply: set ORIG_HEAD correctly\n  rebase -m: don't fork git checkout\n\n builtin/merge.c            |   6 +-\n builtin/rebase.c           |  97 +++++++++++++++----------\n reset.c                    | 143 ++++++++++++++++++++++---------------\n reset.h                    |  33 +++++++--\n sequencer.c                |  48 ++++---------\n sequencer.h                |   3 +-\n t/t3406-rebase-message.sh  |  23 ++++++\n t/t3418-rebase-continue.sh |  26 +++++++\n 8 files changed, 240 insertions(+), 139 deletions(-)\n\n\nbase-commit: 7740ac691d8e7f1bed67bcbdb1ee5c5c618f7373\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1049%2Fphillipwood%2Fwip%2Frebase-reset-head-fixes-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1049/phillipwood/wip/rebase-reset-head-fixes-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1049\n-- \ngitgitgadget\n"},{"id":"437656","messageId":"4d3441c2b2571b91cecca7c28232540b9aaf9993.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 01/11] rebase: factor out checkout for up to date branch","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:52Z","receivedAt":"2021-10-01T10:05:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis code is heavily indented and it will be convenient later in the\nseries to have it in its own function.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 33 +++++++++++++++++++--------------\n 1 file changed, 19 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f82bfaed118..f4c312dd8b5 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1099,6 +1099,23 @@ static int rebase_config(const char *var, const char *value, void *data)\n \treturn git_default_config(var, value, data);\n }\n \n+static int checkout_up_to_date(struct rebase_options *options)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tstrbuf_addf(&buf, \"%s: checkout %s\",\n+\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n+\t\t    options->switch_to);\n+\tif (reset_head(the_repository, &options->orig_head, \"checkout\",\n+\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n+\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n+\t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n /*\n  * Determines whether the commits in from..to are linear, i.e. contain\n  * no merge commits. This function *expects* `from` to be an ancestor of\n@@ -1978,21 +1995,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tif (!(options.flags & REBASE_FORCE)) {\n \t\t\t/* Lazily switch to the target branch if needed... */\n \t\t\tif (options.switch_to) {\n-\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\tstrbuf_addf(&buf, \"%s: checkout %s\",\n-\t\t\t\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n-\t\t\t\t\t    options.switch_to);\n-\t\t\t\tif (reset_head(the_repository,\n-\t\t\t\t\t       &options.orig_head, \"checkout\",\n-\t\t\t\t\t       options.head_name,\n-\t\t\t\t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t\t\t\t       NULL, buf.buf,\n-\t\t\t\t\t       DEFAULT_REFLOG_ACTION) < 0) {\n-\t\t\t\t\tret = error(_(\"could not switch to \"\n-\t\t\t\t\t\t\t\"%s\"),\n-\t\t\t\t\t\t      options.switch_to);\n+\t\t\t\tret = checkout_up_to_date(&options);\n+\t\t\t\tif (ret)\n \t\t\t\t\tgoto cleanup;\n-\t\t\t\t}\n \t\t\t}\n \n \t\t\tif (!(options.flags & REBASE_NO_QUIET))\n-- \ngitgitgadget\n\n"},{"id":"437657","messageId":"28872cbca687b057663a4e3408cb94d69fb60f94.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 03/11] reset_head(): don't run checkout hook if there is an error","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:54Z","receivedAt":"2021-10-01T10:05:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe hook should only be run if the worktree and refs were successfully\nupdated.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/reset.c b/reset.c\nindex fc4dae3fd2d..5abb1a5683e 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -125,7 +125,7 @@ reset_head_refs:\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n \t}\n-\tif (run_hook)\n+\tif (!ret && run_hook)\n \t\trun_hook_le(NULL, \"post-checkout\",\n \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\n-- \ngitgitgadget\n\n"},{"id":"437658","messageId":"fbaf64d6b282730f91b16ea7d5844fb0e8d779da.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 04/11] reset_head(): remove action parameter","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:55Z","receivedAt":"2021-10-01T10:05:13Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe action parameter is passed as the command name to\nsetup_unpack_trees_porcelain(). All but two cases pass either\n\"checkout\" or \"reset\". The case that passes \"reset --hard\" should be\npassing \"reset\" instead. The case that passes \"Fast-forwarded\" is only\nupdating HEAD and so does not call unpack_trees(). The value can be\ndetermined by checking whether flags contains RESET_HEAD_HARD so it\ndoes not need to be specified by the caller.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 14 +++++++-------\n reset.c          |  5 +++--\n reset.h          |  2 +-\n sequencer.c      |  3 +--\n 4 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f4c312dd8b5..26f53c55cc4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -789,7 +789,7 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n-\tret = reset_head(the_repository, NULL, \"\", opts->head_name,\n+\tret = reset_head(the_repository, NULL, opts->head_name,\n \t\t\t RESET_HEAD_REFS_ONLY,\n \t\t\t orig_head_reflog.buf, head_reflog.buf,\n \t\t\t DEFAULT_REFLOG_ACTION);\n@@ -880,7 +880,7 @@ static int run_am(struct rebase_options *opts)\n \t\tfree(rebased_patches);\n \t\tstrvec_clear(&am.args);\n \n-\t\treset_head(the_repository, &opts->orig_head, \"checkout\",\n+\t\treset_head(the_repository, &opts->orig_head,\n \t\t\t   opts->head_name, 0,\n \t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n@@ -1107,7 +1107,7 @@ static int checkout_up_to_date(struct rebase_options *options)\n \tstrbuf_addf(&buf, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n \t\t    options->switch_to);\n-\tif (reset_head(the_repository, &options->orig_head, \"checkout\",\n+\tif (reset_head(the_repository, &options->orig_head,\n \t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n@@ -1557,7 +1557,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\trerere_clear(the_repository, &merge_rr);\n \t\tstring_list_clear(&merge_rr, 1);\n \n-\t\tif (reset_head(the_repository, NULL, \"reset\", NULL, RESET_HEAD_HARD,\n+\t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n \t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n@@ -1575,7 +1575,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\tif (read_basic_state(&options))\n \t\t\texit(1);\n-\t\tif (reset_head(the_repository, &options.orig_head, \"reset\",\n+\t\tif (reset_head(the_repository, &options.orig_head,\n \t\t\t       options.head_name, RESET_HEAD_HARD,\n \t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n \t\t\tdie(_(\"could not move back to %s\"),\n@@ -2064,7 +2064,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n-\tif (reset_head(the_repository, &options.onto->object.oid, \"checkout\", NULL,\n+\tif (reset_head(the_repository, &options.onto->object.oid, NULL,\n \t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf, DEFAULT_REFLOG_ACTION))\n@@ -2082,7 +2082,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&msg, \"rebase finished: %s onto %s\",\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n-\t\treset_head(the_repository, NULL, \"Fast-forwarded\", options.head_name,\n+\t\treset_head(the_repository, NULL, options.head_name,\n \t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf,\n \t\t\t   DEFAULT_REFLOG_ACTION);\n \t\tstrbuf_release(&msg);\ndiff --git a/reset.c b/reset.c\nindex 5abb1a5683e..9ab007c0c34 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,7 +8,7 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n-int reset_head(struct repository *r, struct object_id *oid, const char *action,\n+int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action)\n@@ -23,7 +23,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n \tstruct tree *tree;\n-\tconst char *reflog_action;\n+\tconst char *action, *reflog_action;\n \tstruct strbuf msg = STRBUF_INIT;\n \tsize_t prefix_len;\n \tstruct object_id *orig = NULL, oid_orig,\n@@ -49,6 +49,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tif (refs_only)\n \t\tgoto reset_head_refs;\n \n+\taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n \tunpack_tree_opts.head_idx = 1;\n \tunpack_tree_opts.src_index = r->index;\ndiff --git a/reset.h b/reset.h\nindex 12f83c78e28..2daec804259 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -12,7 +12,7 @@\n #define RESET_HEAD_REFS_ONLY (1<<3)\n #define RESET_ORIG_HEAD (1<<4)\n \n-int reset_head(struct repository *r, struct object_id *oid, const char *action,\n+int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action);\ndiff --git a/sequencer.c b/sequencer.c\nindex 68316636921..07d2a582d39 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4130,8 +4130,7 @@ void create_autostash(struct repository *r, const char *path,\n \t\t\t    path);\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n-\t\tif (reset_head(r, NULL, \"reset --hard\",\n-\t\t\t       NULL, RESET_HEAD_HARD, NULL, NULL,\n+\t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n \t\t\t       default_reflog_action) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \n-- \ngitgitgadget\n\n"},{"id":"437659","messageId":"0744c3d143b5c2020267c5f9ad7da9303d7a7835.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 05/11] reset_head(): factor out ref updates","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:56Z","receivedAt":"2021-10-01T10:05:14Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIn the next commit we will stop trying to update HEAD when we are just\nclearing changes from the working tree. Move the code that updates the\nrefs to its own function in preparation for that.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c | 112 +++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 62 insertions(+), 50 deletions(-)\n\ndiff --git a/reset.c b/reset.c\nindex 9ab007c0c34..668c7639127 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,26 +8,75 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n+static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n+\t\t       const char *reflog_head, const char *reflog_orig_head,\n+\t\t       const char *default_reflog_action, unsigned flags)\n+{\n+\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n+\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tstruct object_id *orig = NULL, oid_orig, *old_orig = NULL, oid_old_orig;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *reflog_action;\n+\tsize_t prefix_len;\n+\tint ret;\n+\n+\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n+\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n+\tprefix_len = msg.len;\n+\n+\tif (update_orig_head) {\n+\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n+\t\t\told_orig = &oid_old_orig;\n+\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n+\t\t\torig = &oid_orig;\n+\t\t\tif (!reflog_orig_head) {\n+\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n+\t\t\t\treflog_orig_head = msg.buf;\n+\t\t\t}\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n+\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\t} else if (old_orig)\n+\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n+\t}\n+\n+\tif (!reflog_head) {\n+\t\tstrbuf_setlen(&msg, prefix_len);\n+\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n+\t\treflog_head = msg.buf;\n+\t}\n+\tif (!switch_to_branch)\n+\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n+\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\telse {\n+\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n+\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tif (!ret)\n+\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n+\t\t\t\t\t    reflog_head);\n+\t}\n+\tif (!ret && run_hook)\n+\t\trun_hook_le(NULL, \"post-checkout\",\n+\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n+\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tstrbuf_release(&msg);\n+\treturn ret;\n+}\n+\n int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action)\n {\n-\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n-\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n \tstruct object_id head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n \tstruct tree *tree;\n-\tconst char *action, *reflog_action;\n-\tstruct strbuf msg = STRBUF_INIT;\n-\tsize_t prefix_len;\n-\tstruct object_id *orig = NULL, oid_orig,\n-\t\t*old_orig = NULL, oid_old_orig;\n+\tconst char *action;\n \tint ret = 0, nr = 0;\n \n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n@@ -47,7 +96,9 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\toid = &head_oid;\n \n \tif (refs_only)\n-\t\tgoto reset_head_refs;\n+\t\treturn update_refs(oid, switch_to_branch, reflog_head,\n+\t\t\t\t   reflog_orig_head, default_reflog_action,\n+\t\t\t\t   flags);\n \n \taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n@@ -90,49 +141,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\tgoto leave_reset_head;\n \t}\n \n-reset_head_refs:\n-\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n-\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n-\tprefix_len = msg.len;\n-\n-\tif (update_orig_head) {\n-\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n-\t\t\told_orig = &oid_old_orig;\n-\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n-\t\t\torig = &oid_orig;\n-\t\t\tif (!reflog_orig_head) {\n-\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n-\t\t\t\treflog_orig_head = msg.buf;\n-\t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n-\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n-\t\t} else if (old_orig)\n-\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n-\t}\n-\n-\tif (!reflog_head) {\n-\t\tstrbuf_setlen(&msg, prefix_len);\n-\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n-\t\treflog_head = msg.buf;\n-\t}\n-\tif (!switch_to_branch)\n-\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n-\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n-\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n-\telse {\n-\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n-\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n-\t\tif (!ret)\n-\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n-\t\t\t\t\t    reflog_head);\n-\t}\n-\tif (!ret && run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n-\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n-\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n+\t\t\t  default_reflog_action, flags);\n \n leave_reset_head:\n-\tstrbuf_release(&msg);\n \trollback_lock_file(&lock);\n \tclear_unpack_trees_porcelain(&unpack_tree_opts);\n \twhile (nr)\n-- \ngitgitgadget\n\n"},{"id":"437660","messageId":"4503defe5912aecc4e6a50ac82a31aa7b230456b.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 06/11] reset_head(): make default_reflog_action optional","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:57Z","receivedAt":"2021-10-01T10:05:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis parameter is only needed when a ref is going to be updated and\nthe caller does not pass an explicit reflog message. Callers that are\njust discarding changes in the working tree like create_autostash() do\nnot update any refs so should not have to worry about passing this\nparameter.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/merge.c  |  6 ++----\n builtin/rebase.c | 14 ++++++--------\n reset.c          | 16 ++++++++++++----\n sequencer.c      |  6 +++---\n sequencer.h      |  3 +--\n 5 files changed, 24 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d2c52b6e971..35833a963fc 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1561,8 +1561,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \t\tif (autostash)\n \t\t\tcreate_autostash(the_repository,\n-\t\t\t\t\t git_path_merge_autostash(the_repository),\n-\t\t\t\t\t \"merge\");\n+\t\t\t\t\t git_path_merge_autostash(the_repository));\n \t\tif (checkout_fast_forward(the_repository,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n@@ -1632,8 +1631,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\n-\t\t\t\t git_path_merge_autostash(the_repository),\n-\t\t\t\t \"merge\");\n+\t\t\t\t git_path_merge_autostash(the_repository));\n \n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 26f53c55cc4..1a6af508f49 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -791,8 +791,7 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name);\n \tret = reset_head(the_repository, NULL, opts->head_name,\n \t\t\t RESET_HEAD_REFS_ONLY,\n-\t\t\t orig_head_reflog.buf, head_reflog.buf,\n-\t\t\t DEFAULT_REFLOG_ACTION);\n+\t\t\t orig_head_reflog.buf, head_reflog.buf, NULL);\n \n \tstrbuf_release(&orig_head_reflog);\n \tstrbuf_release(&head_reflog);\n@@ -1109,7 +1108,7 @@ static int checkout_up_to_date(struct rebase_options *options)\n \t\t    options->switch_to);\n \tif (reset_head(the_repository, &options->orig_head,\n \t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n+\t\t       NULL, buf.buf, NULL) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n \tstrbuf_release(&buf);\n \n@@ -1558,7 +1557,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstring_list_clear(&merge_rr, 1);\n \n \t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n+\t\t\t       NULL, NULL, NULL) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n \t\tif (read_basic_state(&options))\n@@ -1964,8 +1963,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"could not read index\"));\n \n \tif (options.autostash) {\n-\t\tcreate_autostash(the_repository, state_dir_path(\"autostash\", &options),\n-\t\t\t\t DEFAULT_REFLOG_ACTION);\n+\t\tcreate_autostash(the_repository,\n+\t\t\t\t state_dir_path(\"autostash\", &options));\n \t}\n \n \tif (require_clean_work_tree(the_repository, \"rebase\",\n@@ -2083,8 +2082,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n \t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf,\n-\t\t\t   DEFAULT_REFLOG_ACTION);\n+\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\ndiff --git a/reset.c b/reset.c\nindex 668c7639127..be4e18eed98 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -21,8 +21,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch\n \tsize_t prefix_len;\n \tint ret;\n \n-\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n-\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n+\tif ((update_orig_head && !reflog_orig_head) || !reflog_head) {\n+\t\tif (!default_reflog_action)\n+\t\t\tBUG(\"default_reflog_action must be given when reflog messages are omitted\");\n+\t\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n+\t\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action :\n+\t\t\t\t\t\t\t  default_reflog_action);\n+\t}\n \tprefix_len = msg.len;\n \n \tif (update_orig_head) {\n@@ -71,6 +76,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n {\n \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n+\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n \tstruct object_id head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n@@ -141,8 +147,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\tgoto leave_reset_head;\n \t}\n \n-\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n-\t\t\t  default_reflog_action, flags);\n+\tif (oid != &head_oid || update_orig_head || switch_to_branch)\n+\t\tret = update_refs(oid, switch_to_branch, reflog_head,\n+\t\t\t\t  reflog_orig_head, default_reflog_action,\n+\t\t\t\t  flags);\n \n leave_reset_head:\n \trollback_lock_file(&lock);\ndiff --git a/sequencer.c b/sequencer.c\nindex 07d2a582d39..56a204f18c5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4093,8 +4093,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \treturn -1;\n }\n \n-void create_autostash(struct repository *r, const char *path,\n-\t\t      const char *default_reflog_action)\n+void create_autostash(struct repository *r, const char *path)\n+\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4131,7 +4131,7 @@ void create_autostash(struct repository *r, const char *path,\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n-\t\t\t       default_reflog_action) < 0)\n+\t\t\t       NULL) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \n \t\tif (discard_index(r->index) < 0 ||\ndiff --git a/sequencer.h b/sequencer.h\nindex 2088344cb37..92357536ff4 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -198,8 +198,7 @@ void commit_post_rewrite(struct repository *r,\n \t\t\t const struct commit *current_head,\n \t\t\t const struct object_id *new_head);\n \n-void create_autostash(struct repository *r, const char *path,\n-\t\t      const char *default_reflog_action);\n+void create_autostash(struct repository *r, const char *path);\n int save_autostash(const char *path);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n-- \ngitgitgadget\n\n"},{"id":"437661","messageId":"cdb0de221d56d7087146a43a393cffd66c06a5e8.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 09/11] rebase --apply: fix reflog","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:05:00Z","receivedAt":"2021-10-01T10:05:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nmove_to_original_branch() passes the message intended for the branch\nreflog as `orig_head_msg`. Fix this by adding a `branch_msg` member to\nstruct reset_head_opts and add a regression test.  Note that these\nreflog messages do not respect GIT_REFLOG_ACTION. They are not alone\nin that and will be fixed in a future series.\n\nThe \"merge\" backend already has tests that check both the branch and\nHEAD reflogs.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c          |  8 ++++----\n reset.c                   | 12 ++++++++++--\n reset.h                   |  2 ++\n t/t3406-rebase-message.sh | 23 +++++++++++++++++++++++\n 4 files changed, 39 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ae5de271e9a..c40a8b843f4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -776,7 +776,7 @@ static void add_var(struct strbuf *buf, const char *name, const char *value)\n \n static int move_to_original_branch(struct rebase_options *opts)\n {\n-\tstruct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n+\tstruct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n \tstruct reset_head_opts ropts = { 0 };\n \tint ret;\n \n@@ -786,17 +786,17 @@ static int move_to_original_branch(struct rebase_options *opts)\n \tif (!opts->onto)\n \t\tBUG(\"move_to_original_branch without onto\");\n \n-\tstrbuf_addf(&orig_head_reflog, \"rebase finished: %s onto %s\",\n+\tstrbuf_addf(&branch_reflog, \"rebase finished: %s onto %s\",\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n \tropts.branch = opts->head_name;\n \tropts.flags = RESET_HEAD_REFS_ONLY;\n-\tropts.orig_head_msg = orig_head_reflog.buf;\n+\tropts.branch_msg = branch_reflog.buf;\n \tropts.head_msg = head_reflog.buf;\n \tret = reset_head(the_repository, &ropts);\n \n-\tstrbuf_release(&orig_head_reflog);\n+\tstrbuf_release(&branch_reflog);\n \tstrbuf_release(&head_reflog);\n \treturn ret;\n }\ndiff --git a/reset.c b/reset.c\nindex e77a8ac423d..2c32600234d 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -15,6 +15,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n \tconst char *switch_to_branch = opts->branch;\n+\tconst char *reflog_branch = opts->branch_msg;\n \tconst char *reflog_head = opts->head_msg;\n \tconst char *reflog_orig_head = opts->orig_head_msg;\n \tconst char *default_reflog_action = opts->default_reflog_action;\n@@ -58,8 +59,9 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\t\t\t detach_head ? REF_NO_DEREF : 0,\n \t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \telse {\n-\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n-\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tret = update_ref(reflog_branch ? reflog_branch : reflog_head,\n+\t\t\t\t switch_to_branch, oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \t\tif (!ret)\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n@@ -90,6 +92,12 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)\n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n \t\tBUG(\"Not a fully qualified branch: '%s'\", switch_to_branch);\n \n+\tif (opts->orig_head_msg && !update_orig_head)\n+\t\tBUG(\"ORIG_HEAD reflog message given without updating ORIG_HEAD\");\n+\n+\tif (opts->branch_msg && !opts->branch)\n+\t\tBUG(\"branch reflog message given without a branch\");\n+\n \tif (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {\n \t\tret = -1;\n \t\tgoto leave_reset_head;\ndiff --git a/reset.h b/reset.h\nindex 996d7f569b6..c0f4e99a3be 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -19,6 +19,8 @@ struct reset_head_opts {\n \tconst char *branch;\n \t/* Flags defined above */\n \tunsigned flags;\n+\t /* Optional reflog message for branch, defaults to head_msg. */\n+\tconst char *branch_msg;\n \t /*\n \t  * Optional reflog message for HEAD, if this is not set then\n \t  * default_reflog_action must be.\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 77a313f62eb..d17b450e811 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase --apply reflog' '\n+\tgit checkout -b reflog-apply start &&\n+\told_head_reflog=\"$(git log -g --format=%gs -1 HEAD)\" &&\n+\n+\tgit rebase --apply Y &&\n+\n+\tgit log -g --format=%gs -4 HEAD >actual &&\n+\tcat >expect <<-EOF &&\n+\trebase finished: returning to refs/heads/reflog-apply\n+\trebase: Z\n+\trebase: checkout Y\n+\t$old_head_reflog\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit log -g --format=%gs -2 reflog-apply >actual &&\n+\tcat >expect <<-EOF &&\n+\trebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)\n+\tbranch: Created from start\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'rebase -i onto unrelated history' '\n \tgit init unrelated &&\n \ttest_commit -C unrelated 1 &&\n-- \ngitgitgadget\n\n"},{"id":"437662","messageId":"267e074e6dbd3c7cf733cd5ba4fa476d2d5e6fd2.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 08/11] reset_head(): take struct rebase_head_opts","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:59Z","receivedAt":"2021-10-01T10:05:18Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis function already takes a confusingly large number of parameters\nsome of which are optional or not always required. The following\ncommits will add a couple more parameters so change it to take a\nstruct of options first.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 57 ++++++++++++++++++++++++++++++------------------\n reset.c          | 36 +++++++++++++++---------------\n reset.h          | 29 ++++++++++++++++++++----\n sequencer.c      |  5 ++---\n 4 files changed, 80 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 89b348ffa75..ae5de271e9a 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -777,6 +777,7 @@ static void add_var(struct strbuf *buf, const char *name, const char *value)\n static int move_to_original_branch(struct rebase_options *opts)\n {\n \tstruct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n+\tstruct reset_head_opts ropts = { 0 };\n \tint ret;\n \n \tif (!opts->head_name)\n@@ -789,9 +790,11 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n-\tret = reset_head(the_repository, NULL, opts->head_name,\n-\t\t\t RESET_HEAD_REFS_ONLY,\n-\t\t\t orig_head_reflog.buf, head_reflog.buf, NULL);\n+\tropts.branch = opts->head_name;\n+\tropts.flags = RESET_HEAD_REFS_ONLY;\n+\tropts.orig_head_msg = orig_head_reflog.buf;\n+\tropts.head_msg = head_reflog.buf;\n+\tret = reset_head(the_repository, &ropts);\n \n \tstrbuf_release(&orig_head_reflog);\n \tstrbuf_release(&head_reflog);\n@@ -875,13 +878,15 @@ static int run_am(struct rebase_options *opts)\n \n \tstatus = run_command(&format_patch);\n \tif (status) {\n+\t\tstruct reset_head_opts ropts = { 0 };\n \t\tunlink(rebased_patches);\n \t\tfree(rebased_patches);\n \t\tstrvec_clear(&am.args);\n \n-\t\treset_head(the_repository, &opts->orig_head,\n-\t\t\t   opts->head_name, 0,\n-\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n+\t\tropts.oid = &opts->orig_head;\n+\t\tropts.branch = opts->head_name;\n+\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\t\treset_head(the_repository, &ropts);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n \t\t\t\"patches to replay\\n\"\n \t\t\t\"these revisions:\\n\"\n@@ -1101,14 +1106,17 @@ static int rebase_config(const char *var, const char *value, void *data)\n static int checkout_up_to_date(struct rebase_options *options)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reset_head_opts ropts = { 0 };\n \tint ret = 0;\n \n \tstrbuf_addf(&buf, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n \t\t    options->switch_to);\n-\tif (reset_head(the_repository, &options->orig_head,\n-\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, buf.buf, NULL) < 0)\n+\tropts.oid = &options->orig_head;\n+\tropts.branch = options->head_name;\n+\tropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tropts.head_msg = buf.buf;\n+\tif (reset_head(the_repository, &ropts) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n \tstrbuf_release(&buf);\n \n@@ -1319,6 +1327,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n \tint allow_preemptive_ff = 1;\n+\tstruct reset_head_opts ropts = { 0 };\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1555,9 +1564,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\trerere_clear(the_repository, &merge_rr);\n \t\tstring_list_clear(&merge_rr, 1);\n-\n-\t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, NULL) < 0)\n+\t\tropts.flags = RESET_HEAD_HARD;\n+\t\tif (reset_head(the_repository, &ropts) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n \t\tif (read_basic_state(&options))\n@@ -1574,9 +1582,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\tif (read_basic_state(&options))\n \t\t\texit(1);\n-\t\tif (reset_head(the_repository, &options.orig_head,\n-\t\t\t       options.head_name, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n+\t\tropts.oid = &options.orig_head;\n+\t\tropts.branch = options.head_name;\n+\t\tropts.flags = RESET_HEAD_HARD;\n+\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\t\tif (reset_head(the_repository, &ropts) < 0)\n \t\t\tdie(_(\"could not move back to %s\"),\n \t\t\t    oid_to_hex(&options.orig_head));\n \t\tremove_branch_state(the_repository, 0);\n@@ -2063,10 +2073,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n-\tif (reset_head(the_repository, &options.onto->object.oid, NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n-\t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, msg.buf, DEFAULT_REFLOG_ACTION))\n+\tropts.oid = &options.onto->object.oid;\n+\tropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n+\t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tropts.head_msg = msg.buf;\n+\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\tif (reset_head(the_repository, &ropts))\n \t\tdie(_(\"Could not detach HEAD\"));\n \tstrbuf_release(&msg);\n \n@@ -2081,8 +2093,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&msg, \"rebase finished: %s onto %s\",\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n-\t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n+\t\tmemset(&ropts, 0, sizeof(ropts));\n+\t\tropts.branch = options.head_name;\n+\t\tropts.flags = RESET_HEAD_REFS_ONLY;\n+\t\tropts.head_msg = msg.buf;\n+\t\treset_head(the_repository, &ropts);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\ndiff --git a/reset.c b/reset.c\nindex be4e18eed98..e77a8ac423d 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,13 +8,16 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n-static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n-\t\t       const char *reflog_head, const char *reflog_orig_head,\n-\t\t       const char *default_reflog_action, unsigned flags)\n+static int update_refs(const struct reset_head_opts *opts,\n+\t\t       const struct object_id *oid)\n {\n-\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n-\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tunsigned detach_head = opts->flags & RESET_HEAD_DETACH;\n+\tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n+\tconst char *switch_to_branch = opts->branch;\n+\tconst char *reflog_head = opts->head_msg;\n+\tconst char *reflog_orig_head = opts->orig_head_msg;\n+\tconst char *default_reflog_action = opts->default_reflog_action;\n \tstruct object_id *orig = NULL, oid_orig, *old_orig = NULL, oid_old_orig;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *reflog_action;\n@@ -69,14 +72,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch\n \treturn ret;\n }\n \n-int reset_head(struct repository *r, struct object_id *oid,\n-\t       const char *switch_to_branch, unsigned flags,\n-\t       const char *reflog_orig_head, const char *reflog_head,\n-\t       const char *default_reflog_action)\n+int reset_head(struct repository *r, const struct reset_head_opts *opts)\n {\n-\tunsigned reset_hard = flags & RESET_HEAD_HARD;\n-\tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tconst struct object_id *oid = opts->oid;\n+\tconst char *switch_to_branch = opts->branch;\n+\tunsigned reset_hard = opts->flags & RESET_HEAD_HARD;\n+\tunsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;\n+\tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n \tstruct object_id head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n@@ -102,9 +104,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\toid = &head_oid;\n \n \tif (refs_only)\n-\t\treturn update_refs(oid, switch_to_branch, reflog_head,\n-\t\t\t\t   reflog_orig_head, default_reflog_action,\n-\t\t\t\t   flags);\n+\t\treturn update_refs(opts, oid);\n \n \taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n@@ -148,9 +148,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t}\n \n \tif (oid != &head_oid || update_orig_head || switch_to_branch)\n-\t\tret = update_refs(oid, switch_to_branch, reflog_head,\n-\t\t\t\t  reflog_orig_head, default_reflog_action,\n-\t\t\t\t  flags);\n+\t\tret = update_refs(opts, oid);\n \n leave_reset_head:\n \trollback_lock_file(&lock);\ndiff --git a/reset.h b/reset.h\nindex 2daec804259..996d7f569b6 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -12,9 +12,30 @@\n #define RESET_HEAD_REFS_ONLY (1<<3)\n #define RESET_ORIG_HEAD (1<<4)\n \n-int reset_head(struct repository *r, struct object_id *oid,\n-\t       const char *switch_to_branch, unsigned flags,\n-\t       const char *reflog_orig_head, const char *reflog_head,\n-\t       const char *default_reflog_action);\n+struct reset_head_opts {\n+\t/* The oid of the commit to checkout/reset to. Defaults to HEAD */\n+\tconst struct object_id *oid;\n+\t /* Optional branch to switch to */\n+\tconst char *branch;\n+\t/* Flags defined above */\n+\tunsigned flags;\n+\t /*\n+\t  * Optional reflog message for HEAD, if this is not set then\n+\t  * default_reflog_action must be.\n+\t  */\n+\tconst char *head_msg;\n+\t/*\n+\t * Optional reflog message for ORIG_HEAD, if this is not set and flags\n+\t * contains RESET_ORIG_HEAD then default_reflog_action must be set.\n+\t */\n+\tconst char *orig_head_msg;\n+\t/*\n+\t * Action to use in default reflog messages, only required if a ref is\n+\t * being updated and the reflog messages above are omitted.\n+\t */\n+\tconst char *default_reflog_action;\n+};\n+\n+int reset_head(struct repository *r, const struct reset_head_opts *opts);\n \n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex 56a204f18c5..36ab6854c6b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4109,6 +4109,7 @@ void create_autostash(struct repository *r, const char *path)\n \tif (has_unstaged_changes(r, 1) ||\n \t    has_uncommitted_changes(r, 1)) {\n \t\tstruct child_process stash = CHILD_PROCESS_INIT;\n+\t\tstruct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n@@ -4130,10 +4131,8 @@ void create_autostash(struct repository *r, const char *path)\n \t\t\t    path);\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n-\t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n-\t\t\t       NULL) < 0)\n+\t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n-\n \t\tif (discard_index(r->index) < 0 ||\n \t\t\trepo_read_index(r) < 0)\n \t\t\tdie(_(\"could not read index\"));\n-- \ngitgitgadget\n\n"},{"id":"437663","messageId":"e8884efcc83960fc0ea5d5aee72bb9839b4be397.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 10/11] rebase --apply: set ORIG_HEAD correctly","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:05:01Z","receivedAt":"2021-10-01T10:05:20Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAt the start of a rebase ORIG_HEAD is updated to tip of the branch\nbeing rebased. Unfortunately reset_head() always uses the current\nvalue of HEAD for this which is incorrect if the rebase is started\nwith 'git rebase <upstream> <branch>' as in that case ORIG_HEAD should\nbe updated to <branch>. This only affects the \"apply\" backend as the\n\"merge\" backend does not yet use reset_head() for the initial\ncheckout. Fix this by passing in orig_head when calling reset_head()\nand add some regression tests.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c           |  1 +\n reset.c                    |  4 +++-\n reset.h                    |  2 ++\n t/t3418-rebase-continue.sh | 26 ++++++++++++++++++++++++++\n 4 files changed, 32 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex c40a8b843f4..c36a8a10e9b 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -2074,6 +2074,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tropts.oid = &options.onto->object.oid;\n+\tropts.orig_head = &options.orig_head,\n \tropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tropts.head_msg = msg.buf;\ndiff --git a/reset.c b/reset.c\nindex 2c32600234d..82bcf2dcb8b 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -14,6 +14,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \tunsigned detach_head = opts->flags & RESET_HEAD_DETACH;\n \tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n+\tconst struct object_id *orig_head = opts->orig_head;\n \tconst char *switch_to_branch = opts->branch;\n \tconst char *reflog_branch = opts->branch_msg;\n \tconst char *reflog_head = opts->head_msg;\n@@ -43,7 +44,8 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n \t\t\t\treflog_orig_head = msg.buf;\n \t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\",\n+\t\t\t\t   orig_head ? orig_head : orig,\n \t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n \t\t} else if (old_orig)\n \t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\ndiff --git a/reset.h b/reset.h\nindex c0f4e99a3be..91a868c5ba8 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -15,6 +15,8 @@\n struct reset_head_opts {\n \t/* The oid of the commit to checkout/reset to. Defaults to HEAD */\n \tconst struct object_id *oid;\n+\t/* Optional commit when setting ORIG_HEAD. Defaults to HEAD */\n+\tconst struct object_id *orig_head;\n \t /* Optional branch to switch to */\n \tconst char *branch;\n \t/* Flags defined above */\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 738fbae9b29..be63456c5b9 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -323,4 +323,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n '\n \n+test_orig_head_helper() {\n+\ttest_when_finished 'git rebase --abort &&\n+\t\tgit checkout topic &&\n+\t\tgit reset --hard commit-new-file-F2-on-topic-branch' &&\n+\tgit update-ref -d ORIG_HEAD &&\n+\ttest_must_fail git rebase \"$@\" &&\n+\ttest_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch\n+}\n+\n+test_orig_head() {\n+\ttype=$1\n+\ttest_expect_success \"rebase $type sets ORIG_HEAD correctly\" '\n+\t\tgit checkout topic &&\n+\t\tgit reset --hard commit-new-file-F2-on-topic-branch &&\n+\t\ttest_orig_head_helper $type main\n+\t'\n+\n+\ttest_expect_success \"rebase $type <upstream> <branch> sets ORIG_HEAD correctly\" '\n+\t\tgit checkout main &&\n+\t\ttest_orig_head_helper $type main topic\n+\t'\n+}\n+\n+test_orig_head --apply\n+test_orig_head --merge\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"437664","messageId":"c8f641132160d6bbd72a5e4921f1c9f0b3d40242.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 02/11] reset_head(): fix checkout","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:53Z","receivedAt":"2021-10-01T10:05:21Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe reset bit should only be set if flags contains RESET_HEAD_HARD.\nThe test for `!deatch_head` dates back to the original implementation\nof reset_head() in ac7f467fef (\"builtin/rebase: support running \"git\nrebase <upstream>\"\", 2018-08-07) and was correct until e65123a71d\n(\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n2018-09-04) started using reset_head() to checkout <switch-to> when\nfast-forwarding.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/reset.c b/reset.c\nindex 79310ae071b..fc4dae3fd2d 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -57,7 +57,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tunpack_tree_opts.update = 1;\n \tunpack_tree_opts.merge = 1;\n \tinit_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);\n-\tif (!detach_head)\n+\tif (reset_hard)\n \t\tunpack_tree_opts.reset = 1;\n \n \tif (repo_read_index_unmerged(r) < 0) {\n-- \ngitgitgadget\n\n"},{"id":"437665","messageId":"2c8c60c3f310a490217b9b1c62b13fca83c171be.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 11/11] rebase -m: don't fork git checkout","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:05:02Z","receivedAt":"2021-10-01T10:05:26Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nNow that reset_head() can handle the initial checkout of onto\ncorrectly use it in the \"merge\" backend instead of forking 'git\ncheckout'.  This opens the way for us to stop calling the\npost-checkout hook in the future. Not running 'git checkout' means\nthat 'rebase -i/m' no longer recurse submodules when checking out\n'onto' (thanks to Philippe Blain for pointing this out). As the rest\nof rebase does not know what to do with submodules this is probably a\ngood thing. When using merge-ort rebase ought be able to handle\nsubmodules correctly if it parsed the submodule config, such a change\nis left for a future patch series.\n\nThe \"apply\" based rebase has avoided forking git checkout\nsince ac7f467fef (\"builtin/rebase: support running \"git rebase\n<upstream>\"\", 2018-08-07). The code that handles the checkout was\nmoved into libgit by b309a97108 (\"reset: extract reset_head() from\nrebase\", 2020-04-07).\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 38 +++++++++++---------------------------\n 1 file changed, 11 insertions(+), 27 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 36ab6854c6b..9011dd84afd 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4217,42 +4217,26 @@ int apply_autostash_oid(const char *stash_oid)\n \treturn apply_save_autostash_oid(stash_oid, 1);\n }\n \n-static int run_git_checkout(struct repository *r, struct replay_opts *opts,\n-\t\t\t    const char *commit, const char *action)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tint ret;\n-\n-\tcmd.git_cmd = 1;\n-\n-\tstrvec_push(&cmd.args, \"checkout\");\n-\tstrvec_push(&cmd.args, commit);\n-\tstrvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION \"=%s\", action);\n-\n-\tif (opts->verbose)\n-\t\tret = run_command(&cmd);\n-\telse\n-\t\tret = run_command_silent_on_success(&cmd);\n-\n-\tif (!ret)\n-\t\tdiscard_index(r->index);\n-\n-\treturn ret;\n-}\n-\n static int checkout_onto(struct repository *r, struct replay_opts *opts,\n \t\t\t const char *onto_name, const struct object_id *onto,\n \t\t\t const struct object_id *orig_head)\n {\n-\tconst char *action = reflog_message(opts, \"start\", \"checkout %s\", onto_name);\n-\n-\tif (run_git_checkout(r, opts, oid_to_hex(onto), action)) {\n+\tstruct reset_head_opts ropts = {\n+\t\t.oid = onto,\n+\t\t.orig_head = orig_head,\n+\t\t.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n+\t\t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n+\t\t.head_msg = reflog_message(opts, \"start\", \"checkout %s\",\n+\t\t\t\t\t   onto_name),\n+\t\t.default_reflog_action = \"rebase\"\n+\t};\n+\tif (reset_head(r, &ropts)) {\n \t\tapply_autostash(rebase_path_autostash());\n \t\tsequencer_remove_state(opts);\n \t\treturn error(_(\"could not detach HEAD\"));\n \t}\n \n-\treturn update_ref(NULL, \"ORIG_HEAD\", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\treturn 0;\n }\n \n static int stopped_at_head(struct repository *r)\n-- \ngitgitgadget\n"},{"id":"437666","messageId":"5ffc7e64ff1cb306dc96abf3b0fa65c0662994d8.1633082702.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH 07/11] rebase: cleanup reset_head() calls","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-01T10:04:58Z","receivedAt":"2021-10-01T10:07:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf ORIG_HEAD is not set by passing RESET_ORIG_HEAD then there is no\nneed to pass anything for reflog_orig_head. In addition to the callers\nfixed in this commit move_to_original_branch() also passes\nreflog_orig_head without setting ORIG_HEAD. That caller is mistakenly\npassing the message it wants to put in the branch reflog which is not\ncurrently possible so we delay fixing that caller until we can pass\nthe message as the branch reflog.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 1a6af508f49..89b348ffa75 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -881,7 +881,7 @@ static int run_am(struct rebase_options *opts)\n \n \t\treset_head(the_repository, &opts->orig_head,\n \t\t\t   opts->head_name, 0,\n-\t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n+\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n \t\t\t\"patches to replay\\n\"\n \t\t\t\"these revisions:\\n\"\n@@ -2082,7 +2082,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n \t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n+\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\n-- \ngitgitgadget\n\n"},{"id":"437722","messageId":"xmqqee947aqn.fsf@gitster.g","threadId":"56620","inReplyTo":"c8f641132160d6bbd72a5e4921f1c9f0b3d40242.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/11] reset_head(): fix checkout","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T20:26:08Z","receivedAt":"2021-10-01T20:26:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> The reset bit should only be set if flags contains RESET_HEAD_HARD.\n> The test for `!deatch_head` dates back to the original implementation\n> of reset_head() in ac7f467fef (\"builtin/rebase: support running \"git\n> rebase <upstream>\"\", 2018-08-07) and was correct until e65123a71d\n> (\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n> 2018-09-04) started using reset_head() to checkout <switch-to> when\n> fast-forwarding.\n\nSorry, but it is not quite clear what exactly is \"fix checkout\" in\nthe context of this change, even with the above paragraph that\ndescribes the internals but not any end-user visible effect.\n\nCan this step come with its own addition to t/ to demonstrate the\nbreakage that is fixed?\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  reset.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/reset.c b/reset.c\n> index 79310ae071b..fc4dae3fd2d 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -57,7 +57,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n>  \tunpack_tree_opts.update = 1;\n>  \tunpack_tree_opts.merge = 1;\n>  \tinit_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);\n> -\tif (!detach_head)\n> +\tif (reset_hard)\n>  \t\tunpack_tree_opts.reset = 1;\n>  \n>  \tif (repo_read_index_unmerged(r) < 0) {\n"},{"id":"437725","messageId":"xmqqtui05uxr.fsf@gitster.g","threadId":"56620","inReplyTo":"28872cbca687b057663a4e3408cb94d69fb60f94.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/11] reset_head(): don't run checkout hook if there is an error","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T20:52:48Z","receivedAt":"2021-10-01T20:52:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> The hook should only be run if the worktree and refs were successfully\n> updated.\n\nOK.  This is a behaviour change visible to end-users, and deserves a\nmention in the release notes.\n\n - When \"git rebase\" attempted to check out a branch (or detached\n   the HEAD) to work on, we used to always call the \"post-checkout\"\n   hook, even if the checkout failed to update the ref.  The hook is\n   no longer called if the checkout fails.\n\nor something.\n\nAgain, can the bug this step fixes be protected with a new test in\nt/ please?\n\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  reset.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/reset.c b/reset.c\n> index fc4dae3fd2d..5abb1a5683e 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -125,7 +125,7 @@ reset_head_refs:\n>  \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n>  \t\t\t\t\t    reflog_head);\n>  \t}\n> -\tif (run_hook)\n> +\tif (!ret && run_hook)\n>  \t\trun_hook_le(NULL, \"post-checkout\",\n>  \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n>  \t\t\t    oid_to_hex(oid), \"1\", NULL);\n"},{"id":"437726","messageId":"xmqqpmso5uno.fsf@gitster.g","threadId":"56620","inReplyTo":"fbaf64d6b282730f91b16ea7d5844fb0e8d779da.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 04/11] reset_head(): remove action parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T20:58:51Z","receivedAt":"2021-10-01T20:58:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> The action parameter is passed as the command name to\n> setup_unpack_trees_porcelain(). All but two cases pass either\n> \"checkout\" or \"reset\". The case that passes \"reset --hard\" should be\n> passing \"reset\" instead.\n\nDescribe how the parameter is meant to be used (presumably \"this is\nto record in the reflog\", perhaps?); without such explanation, it is\nhard to either agree or disagree with the claim that \"reset --hard\"\nshould be \"reset\".\n\nAlso state if this change is supposed to have any externally\nobservable effect.\n\nPerhaps this improves what is shown in an error message by affecting\nwhat setup_unpack_trees_porcelain() does?  I am just guessing,\nbecause the proposed log message is not telling.  Please do not make\nme (or other readers of \"git log\") guess.\n\nThanks.\n\n"},{"id":"437727","messageId":"xmqqlf3c5ul1.fsf@gitster.g","threadId":"56620","inReplyTo":"0744c3d143b5c2020267c5f9ad7da9303d7a7835.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/11] reset_head(): factor out ref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T21:00:26Z","receivedAt":"2021-10-01T21:00:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> In the next commit we will stop trying to update HEAD when we are just\n> clearing changes from the working tree. Move the code that updates the\n\n\"clearing changes\" meaning...?  Like \"rebase --abort\"?\n\n> refs to its own function in preparation for that.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  reset.c | 112 +++++++++++++++++++++++++++++++-------------------------\n>  1 file changed, 62 insertions(+), 50 deletions(-)\n>\n> diff --git a/reset.c b/reset.c\n> index 9ab007c0c34..668c7639127 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -8,26 +8,75 @@\n>  #include \"tree.h\"\n>  #include \"unpack-trees.h\"\n>  \n> +static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n> +\t\t       const char *reflog_head, const char *reflog_orig_head,\n> +\t\t       const char *default_reflog_action, unsigned flags)\n> +{\n> +\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n> +\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n> +\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n> +\tstruct object_id *orig = NULL, oid_orig, *old_orig = NULL, oid_old_orig;\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\tconst char *reflog_action;\n> +\tsize_t prefix_len;\n> +\tint ret;\n> +\n> +\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n> +\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n> +\tprefix_len = msg.len;\n> +\n> +\tif (update_orig_head) {\n> +\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n> +\t\t\told_orig = &oid_old_orig;\n> +\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n> +\t\t\torig = &oid_orig;\n> +\t\t\tif (!reflog_orig_head) {\n> +\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n> +\t\t\t\treflog_orig_head = msg.buf;\n> +\t\t\t}\n> +\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n> +\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n> +\t\t} else if (old_orig)\n> +\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n> +\t}\n> +\n> +\tif (!reflog_head) {\n> +\t\tstrbuf_setlen(&msg, prefix_len);\n> +\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n> +\t\treflog_head = msg.buf;\n> +\t}\n> +\tif (!switch_to_branch)\n> +\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n> +\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n> +\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n> +\telse {\n> +\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n> +\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n> +\t\tif (!ret)\n> +\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n> +\t\t\t\t\t    reflog_head);\n> +\t}\n> +\tif (!ret && run_hook)\n> +\t\trun_hook_le(NULL, \"post-checkout\",\n> +\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n> +\t\t\t    oid_to_hex(oid), \"1\", NULL);\n> +\tstrbuf_release(&msg);\n> +\treturn ret;\n> +}\n> +\n>  int reset_head(struct repository *r, struct object_id *oid,\n>  \t       const char *switch_to_branch, unsigned flags,\n>  \t       const char *reflog_orig_head, const char *reflog_head,\n>  \t       const char *default_reflog_action)\n>  {\n> -\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n>  \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n> -\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n>  \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n> -\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n>  \tstruct object_id head_oid;\n>  \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n>  \tstruct lock_file lock = LOCK_INIT;\n>  \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n>  \tstruct tree *tree;\n> -\tconst char *action, *reflog_action;\n> -\tstruct strbuf msg = STRBUF_INIT;\n> -\tsize_t prefix_len;\n> -\tstruct object_id *orig = NULL, oid_orig,\n> -\t\t*old_orig = NULL, oid_old_orig;\n> +\tconst char *action;\n>  \tint ret = 0, nr = 0;\n>  \n>  \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n> @@ -47,7 +96,9 @@ int reset_head(struct repository *r, struct object_id *oid,\n>  \t\toid = &head_oid;\n>  \n>  \tif (refs_only)\n> -\t\tgoto reset_head_refs;\n> +\t\treturn update_refs(oid, switch_to_branch, reflog_head,\n> +\t\t\t\t   reflog_orig_head, default_reflog_action,\n> +\t\t\t\t   flags);\n>  \n>  \taction = reset_hard ? \"reset\" : \"checkout\";\n>  \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n> @@ -90,49 +141,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n>  \t\tgoto leave_reset_head;\n>  \t}\n>  \n> -reset_head_refs:\n> -\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n> -\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n> -\tprefix_len = msg.len;\n> -\n> -\tif (update_orig_head) {\n> -\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n> -\t\t\told_orig = &oid_old_orig;\n> -\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n> -\t\t\torig = &oid_orig;\n> -\t\t\tif (!reflog_orig_head) {\n> -\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n> -\t\t\t\treflog_orig_head = msg.buf;\n> -\t\t\t}\n> -\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n> -\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n> -\t\t} else if (old_orig)\n> -\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n> -\t}\n> -\n> -\tif (!reflog_head) {\n> -\t\tstrbuf_setlen(&msg, prefix_len);\n> -\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n> -\t\treflog_head = msg.buf;\n> -\t}\n> -\tif (!switch_to_branch)\n> -\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n> -\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n> -\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n> -\telse {\n> -\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n> -\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n> -\t\tif (!ret)\n> -\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n> -\t\t\t\t\t    reflog_head);\n> -\t}\n> -\tif (!ret && run_hook)\n> -\t\trun_hook_le(NULL, \"post-checkout\",\n> -\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n> -\t\t\t    oid_to_hex(oid), \"1\", NULL);\n> +\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n> +\t\t\t  default_reflog_action, flags);\n>  \n>  leave_reset_head:\n> -\tstrbuf_release(&msg);\n>  \trollback_lock_file(&lock);\n>  \tclear_unpack_trees_porcelain(&unpack_tree_opts);\n>  \twhile (nr)\n"},{"id":"437728","messageId":"xmqqh7e05ugl.fsf@gitster.g","threadId":"56620","inReplyTo":"4503defe5912aecc4e6a50ac82a31aa7b230456b.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/11] reset_head(): make default_reflog_action optional","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T21:03:06Z","receivedAt":"2021-10-01T21:03:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This parameter is only needed when a ref is going to be updated and\n> the caller does not pass an explicit reflog message. Callers that are\n> just discarding changes in the working tree like create_autostash() do\n> not update any refs so should not have to worry about passing this\n> parameter.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  builtin/merge.c  |  6 ++----\n>  builtin/rebase.c | 14 ++++++--------\n>  reset.c          | 16 ++++++++++++----\n>  sequencer.c      |  6 +++---\n>  sequencer.h      |  3 +--\n>  5 files changed, 24 insertions(+), 21 deletions(-)\n>\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index d2c52b6e971..35833a963fc 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -1561,8 +1561,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n>  \n>  \t\tif (autostash)\n>  \t\t\tcreate_autostash(the_repository,\n> -\t\t\t\t\t git_path_merge_autostash(the_repository),\n> -\t\t\t\t\t \"merge\");\n> +\t\t\t\t\t git_path_merge_autostash(the_repository));\n>  \t\tif (checkout_fast_forward(the_repository,\n>  \t\t\t\t\t  &head_commit->object.oid,\n>  \t\t\t\t\t  &commit->object.oid,\n\nThe title talks about an optional change to reset_head(); perhaps a\nchange to create_autostash() should be a separate preliminary step.\n"},{"id":"437729","messageId":"xmqqczoo5u7m.fsf@gitster.g","threadId":"56620","inReplyTo":"4503defe5912aecc4e6a50ac82a31aa7b230456b.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/11] reset_head(): make default_reflog_action optional","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T21:08:29Z","receivedAt":"2021-10-01T21:08:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This parameter is only needed when a ref is going to be updated and\n> the caller does not pass an explicit reflog message. Callers that are\n> just discarding changes in the working tree like create_autostash() do\n> not update any refs so should not have to worry about passing this\n> parameter.\n\nThis only talks about internal implementation details of passing a\nparameter that may not be used, but ...\n\n> -\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n> -\t\t\t  default_reflog_action, flags);\n> +\tif (oid != &head_oid || update_orig_head || switch_to_branch)\n> +\t\tret = update_refs(oid, switch_to_branch, reflog_head,\n> +\t\t\t\t  reflog_orig_head, default_reflog_action,\n> +\t\t\t\t  flags);\n\n... doesn't this have a more significant behaviour change?\n\nI am not sure if comparison between oid and head_oid can safely\ncheat like the patch does, or if it is necessary to do a proper oid\ncomparison, but either way, this would stop calling update_refs(),\nwhich in turn means it would have an externally visible effect, like\na hook no longer getting called, doesn't it?\n\nIt would be a change for the good---calling post-checkout hook when\nyou did \"git checkout (no other arguments)\" feels wasteful, but it\ndeserves to be told to end users, I would think.\n\nAgain, a new test to protect the change would go well in the same\npatch.\n\nThanks.\n\n\n"},{"id":"437730","messageId":"xmqq8rzc5u2j.fsf@gitster.g","threadId":"56620","inReplyTo":"267e074e6dbd3c7cf733cd5ba4fa476d2d5e6fd2.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 08/11] reset_head(): take struct rebase_head_opts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T21:11:32Z","receivedAt":"2021-10-01T21:11:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This function already takes a confusingly large number of parameters\n> some of which are optional or not always required. The following\n> commits will add a couple more parameters so change it to take a\n> struct of options first.\n\nWould all the members in the struct always be used by the function?\nIf not, such a change will *not* solve the \"confusingly ... required\"\nproblem at all.\n\nI am not necessarily against a change to consolidate a bag of\nparameters into a pointer to a struct.  I am against a change that\nis justified with a benefit that the change does not bring to us.\n\nThanks.\n"},{"id":"437731","messageId":"xmqq4ka05u05.fsf@gitster.g","threadId":"56620","inReplyTo":"cdb0de221d56d7087146a43a393cffd66c06a5e8.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 09/11] rebase --apply: fix reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T21:12:58Z","receivedAt":"2021-10-01T21:13:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> move_to_original_branch() passes the message intended for the branch\n> reflog as `orig_head_msg`. Fix this by adding a `branch_msg` member to\n> struct reset_head_opts and add a regression test.\n> ...\n> The \"merge\" backend already has tests that check both the branch and\n> HEAD reflogs.\n\nOK.  This sounds like a good idea.\n\n> +test_expect_success 'rebase --apply reflog' '\n> +\tgit checkout -b reflog-apply start &&\n> +\told_head_reflog=\"$(git log -g --format=%gs -1 HEAD)\" &&\n> +\n> +\tgit rebase --apply Y &&\n> +\n> +\tgit log -g --format=%gs -4 HEAD >actual &&\n> +\tcat >expect <<-EOF &&\n> +\trebase finished: returning to refs/heads/reflog-apply\n> +\trebase: Z\n> +\trebase: checkout Y\n> +\t$old_head_reflog\n> +\tEOF\n> +\ttest_cmp expect actual &&\n> +\n> +\tgit log -g --format=%gs -2 reflog-apply >actual &&\n> +\tcat >expect <<-EOF &&\n> +\trebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)\n> +\tbranch: Created from start\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n\nThanks.\n"},{"id":"437732","messageId":"xmqqwnmw4f5q.fsf@gitster.g","threadId":"56620","inReplyTo":"e8884efcc83960fc0ea5d5aee72bb9839b4be397.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 10/11] rebase --apply: set ORIG_HEAD correctly","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-01T21:18:57Z","receivedAt":"2021-10-01T21:19:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> At the start of a rebase ORIG_HEAD is updated to tip of the branch\n> being rebased. Unfortunately reset_head() always uses the current\n> value of HEAD for this which is incorrect if the rebase is started\n> with 'git rebase <upstream> <branch>' as in that case ORIG_HEAD should\n> be updated to <branch>. This only affects the \"apply\" backend as the\n> \"merge\" backend does not yet use reset_head() for the initial\n> checkout. Fix this by passing in orig_head when calling reset_head()\n> and add some regression tests.\n\nAll correct.  It is somewhat surprising that this wasn't caught and\nreported as an issue for this long X-<.\n\n> +\tconst struct object_id *orig_head;\n>  \t /* Optional branch to switch to */\n>  \tconst char *branch;\n>  \t/* Flags defined above */\n> diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n> index 738fbae9b29..be63456c5b9 100755\n> --- a/t/t3418-rebase-continue.sh\n> +++ b/t/t3418-rebase-continue.sh\n> @@ -323,4 +323,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n>  \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n>  '\n>  \n> +test_orig_head_helper() {\n\nNeed SP before \"()\" in addition to after it.  Ditto for the other one.\n\n> +\ttest_when_finished 'git rebase --abort &&\n> +\t\tgit checkout topic &&\n> +\t\tgit reset --hard commit-new-file-F2-on-topic-branch' &&\n> +\tgit update-ref -d ORIG_HEAD &&\n> +\ttest_must_fail git rebase \"$@\" &&\n> +\ttest_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch\n> +}\n> +\n> +test_orig_head() {\n> +\ttype=$1\n> +\ttest_expect_success \"rebase $type sets ORIG_HEAD correctly\" '\n> +\t\tgit checkout topic &&\n> +\t\tgit reset --hard commit-new-file-F2-on-topic-branch &&\n> +\t\ttest_orig_head_helper $type main\n> +\t'\n> +\n> +\ttest_expect_success \"rebase $type <upstream> <branch> sets ORIG_HEAD correctly\" '\n> +\t\tgit checkout main &&\n> +\t\ttest_orig_head_helper $type main topic\n> +\t'\n> +}\n> +\n> +test_orig_head --apply\n> +test_orig_head --merge\n> +\n>  test_done\n"},{"id":"437745","messageId":"CAPig+cTVODZX1tS1FJe16OH0pTgW4dagau3eAHrwDmnBpYNZ7Q@mail.gmail.com","threadId":"56620","inReplyTo":"c8f641132160d6bbd72a5e4921f1c9f0b3d40242.1633082702.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/11] reset_head(): fix checkout","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-10-01T22:47:06Z","receivedAt":"2021-10-01T22:47:35Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Oct 1, 2021 at 6:06 AM Phillip Wood via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> The reset bit should only be set if flags contains RESET_HEAD_HARD.\n> The test for `!deatch_head` dates back to the original implementation\n\ns/deatch_head/detach_head/\n\n> of reset_head() in ac7f467fef (\"builtin/rebase: support running \"git\n> rebase <upstream>\"\", 2018-08-07) and was correct until e65123a71d\n> (\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n> 2018-09-04) started using reset_head() to checkout <switch-to> when\n> fast-forwarding.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n"},{"id":"437748","messageId":"xmqqbl482rby.fsf@gitster.g","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/11] rebase: reset_head() related fixes and improvements","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-02T00:38:57Z","receivedAt":"2021-10-02T00:39:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Fix some issues with the implementation and use of reset_head(). The last\n> patch was previously posted as [1], I have updated the commit message and\n> rebased it onto the fixes in this series. There are a couple of small\n> conflicts merging this into seen, I think they should be easy to resolve (in\n> rebase.c take both sides in reset.c take the changed lines from each side).\n> These patches are based on pw/rebase-of-a-tag-fix\n\nWhen merged with other topics in flight in 'seen', this seems to\nfail the t1092 test (most likely, ds/add-rm-with-sparse-index is\nwhat this interacts badly with).\n\nThanks.\n"},{"id":"437750","messageId":"xmqq4ka02fc4.fsf@gitster.g","threadId":"56620","inReplyTo":"xmqqbl482rby.fsf@gitster.g","subject":"Re: [PATCH 00/11] rebase: reset_head() related fixes and improvements","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-02T04:58:03Z","receivedAt":"2021-10-02T04:58:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> When merged with other topics in flight in 'seen', this seems to\n> fail the t1092 test (most likely, ds/add-rm-with-sparse-index is\n> what this interacts badly with).\n\nThere are Two CI runs at GitHub:\n\n - https://github.com/git/git/actions/runs/1297117791 (d3a1c4e)\n - https://github.com/git/git/actions/runs/1297232973 (a1108c2)\n\nThe difference between the former (which passes all the tests) and\nhe latter (which fails) are only two topics:\n\n    $ git log --first-parent --oneline d3a1c4e..a1108c2\n    a1108c2b1b Merge branch 'hn/reftable' into seen\n    e575f29006 Merge branch 'pw/fix-some-issues-in-reset-head' into seen\n\nI think the following is the same failure I saw earlier\n\n   https://github.com/git/git/runs/3773780195?check_suite_focus=true#step:6:5033\n\nthat the write-tree codepath hits assertion failure by detecting a\ncorruption in the cache-tree data structure.\n\ne575f29006 (i.e. without the reftable topic) fails t1092.  If you\nrevert e575f29006^2 (i.e. the \"do not fork 'git checkout'\") from\nthat merge, all tests pass including t1092.\n\nThe reftable topic is queued near the tip of 'seen' not necessarily\nbecause it _breaks_ CI (I do not think it does), but it needed a\nhandful of fixup commits on top.  The topic needs rerolling with the\nfixes squashed in.\n\nThanks.\n\n"},{"id":"437763","messageId":"ddf5e9c2-3211-cec2-cd18-2a083e279ccb@gmail.com","threadId":"56620","inReplyTo":"xmqq4ka02fc4.fsf@gitster.g","subject":"Re: [PATCH 00/11] rebase: reset_head() related fixes and improvements","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-02T12:27:28Z","receivedAt":"2021-10-02T12:27:33Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 02/10/2021 05:58, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n>> When merged with other topics in flight in 'seen', this seems to\n>> fail the t1092 test (most likely, ds/add-rm-with-sparse-index is\n>> what this interacts badly with).\n\nOh dear, thanks for letting me know.\n\n> There are Two CI runs at GitHub:\n> \n>   - https://github.com/git/git/actions/runs/1297117791 (d3a1c4e)\n>   - https://github.com/git/git/actions/runs/1297232973 (a1108c2)\n> \n> The difference between the former (which passes all the tests) and\n> he latter (which fails) are only two topics:\n> \n>      $ git log --first-parent --oneline d3a1c4e..a1108c2\n>      a1108c2b1b Merge branch 'hn/reftable' into seen\n>      e575f29006 Merge branch 'pw/fix-some-issues-in-reset-head' into seen\n> \n> I think the following is the same failure I saw earlier\n> \n>     https://github.com/git/git/runs/3773780195?check_suite_focus=true#step:6:5033\n> \n> that the write-tree codepath hits assertion failure by detecting a\n> corruption in the cache-tree data structure.\n\nThe test that fails (t1092-sparse-checkout-compatibility.sh:'merge, \ncherry-pick, and rebase') was introduced by c0b99303db (\"t1092: add \ncherry-pick, rebase tests\", 2021-09-08) and is in v2.33.0. It does not \ntest the \"apply\" backend of rebase, changing it to do so makes it fail \non the current master as that backend already uses reset_head() for the \ninitial checkout so it is an existing bug that is exposed by the changes \nin this series. It seems to be a use-after-free (see below) I'll try and \ninvestigate but I've got no idea what is going on at the moment - the \nindex is not my area of expertise.\n\nBest Wishes\n\nPhillip\n\n==74345==ERROR: AddressSanitizer: heap-use-after-free on address \n0x606000001b20 at pc 0x557cbe82d3a2 bp 0x7ffdfee08090 sp 0x7ffdfee08080\nREAD of size 4 at 0x606000001b20 thread T0\n     #0 0x557cbe82d3a1 in verify_one /home/phil/src/git/cache-tree.c:863\n     #1 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n     #2 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n     #3 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n     #4 0x557cbe830a2b in cache_tree_verify \n/home/phil/src/git/cache-tree.c:910\n     #5 0x557cbea53741 in write_locked_index \n/home/phil/src/git/read-cache.c:3250\n     #6 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n     #7 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n     #8 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n     #9 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n     #10 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n     #11 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n     #12 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n     #13 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n     #14 0x557cbe5bcb8d in _start (/home/phil/src/git/git+0x1b9b8d)\n\n0x606000001b20 is located 0 bytes inside of 56-byte region \n[0x606000001b20,0x606000001b58)\nfreed by thread T0 here:\n     #0 0x7fdd4bacff19 in __interceptor_free \n/build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:127\n     #1 0x557cbe82af60 in cache_tree_free /home/phil/src/git/cache-tree.c:35\n     #2 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n     #3 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n     #4 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n     #5 0x557cbeb2557a in ensure_full_index \n/home/phil/src/git/sparse-index.c:310\n     #6 0x557cbea45c4a in index_name_stage_pos \n/home/phil/src/git/read-cache.c:588\n     #7 0x557cbe82ce37 in verify_one /home/phil/src/git/cache-tree.c:850\n     #8 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n     #9 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n     #10 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n     #11 0x557cbe830a2b in cache_tree_verify \n/home/phil/src/git/cache-tree.c:910\n     #12 0x557cbea53741 in write_locked_index \n/home/phil/src/git/read-cache.c:3250\n     #13 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n     #14 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n     #15 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n     #16 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n     #17 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n     #18 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n     #19 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n     #20 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n\npreviously allocated by thread T0 here:\n     #0 0x7fdd4bad0459 in __interceptor_calloc \n/build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:154\n     #1 0x557cbebc1807 in xcalloc /home/phil/src/git/wrapper.c:140\n     #2 0x557cbe82b7d8 in cache_tree /home/phil/src/git/cache-tree.c:17\n     #3 0x557cbe82b7d8 in prime_cache_tree_rec \n/home/phil/src/git/cache-tree.c:763\n     #4 0x557cbe82b837 in prime_cache_tree_rec \n/home/phil/src/git/cache-tree.c:764\n     #5 0x557cbe82b837 in prime_cache_tree_rec \n/home/phil/src/git/cache-tree.c:764\n     #6 0x557cbe8304e1 in prime_cache_tree \n/home/phil/src/git/cache-tree.c:779\n     #7 0x557cbeab7fa7 in reset_head /home/phil/src/git/reset.c:85\n     #8 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n     #9 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n     #10 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n     #11 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n     #12 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n     #13 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n     #14 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n\nSUMMARY: AddressSanitizer: heap-use-after-free \n/home/phil/src/git/cache-tree.c:863 in verify_one\nShadow bytes around the buggy address:\n   0x0c0c7fff8310: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n   0x0c0c7fff8320: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fa\n   0x0c0c7fff8330: fa fa fa fa 00 00 00 00 00 00 00 02 fa fa fa fa\n   0x0c0c7fff8340: fd fd fd fd fd fd fd fa fa fa fa fa 00 00 00 00\n   0x0c0c7fff8350: 00 00 00 02 fa fa fa fa fd fd fd fd fd fd fd fa\n=>0x0c0c7fff8360: fa fa fa fa[fd]fd fd fd fd fd fd fa fa fa fa fa\n   0x0c0c7fff8370: 00 00 00 00 00 00 00 02 fa fa fa fa fd fd fd fd\n   0x0c0c7fff8380: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 02\n   0x0c0c7fff8390: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa\n   0x0c0c7fff83a0: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n   0x0c0c7fff83b0: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 fa\nShadow byte legend (one shadow byte represents 8 application bytes):\n   Addressable:           00\n   Partially addressable: 01 02 03 04 05 06 07\n   Heap left redzone:       fa\n   Freed heap region:       fd\n   Stack left redzone:      f1\n   Stack mid redzone:       f2\n   Stack right redzone:     f3\n   Stack after return:      f5\n   Stack use after scope:   f8\n   Global redzone:          f9\n   Global init order:       f6\n   Poisoned by user:        f7\n   Container overflow:      fc\n   Array cookie:            ac\n   Intra object redzone:    bb\n   ASan internal:           fe\n   Left alloca redzone:     ca\n   Right alloca redzone:    cb\n   Shadow gap:              cc\n==74345==ABORTING\n\n\n> e575f29006 (i.e. without the reftable topic) fails t1092.  If you\n> revert e575f29006^2 (i.e. the \"do not fork 'git checkout'\") from\n> that merge, all tests pass including t1092.\n> \n> The reftable topic is queued near the tip of 'seen' not necessarily\n> because it _breaks_ CI (I do not think it does), but it needed a\n> handful of fixup commits on top.  The topic needs rerolling with the\n> fixes squashed in.\n> \n> Thanks.\n> \n\n"},{"id":"437764","messageId":"9e5cdff3-a71f-640a-8964-22a7d6b12276@gmail.com","threadId":"56620","inReplyTo":"ddf5e9c2-3211-cec2-cd18-2a083e279ccb@gmail.com","subject":"Re: [PATCH 00/11] rebase: reset_head() related fixes and improvements","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-02T13:12:18Z","receivedAt":"2021-10-02T13:12:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/10/2021 13:27, Phillip Wood wrote:\n> Hi Junio\n> \n> On 02/10/2021 05:58, Junio C Hamano wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> When merged with other topics in flight in 'seen', this seems to\n>>> fail the t1092 test (most likely, ds/add-rm-with-sparse-index is\n>>> what this interacts badly with).\n> \n> Oh dear, thanks for letting me know.\n> \n>> There are Two CI runs at GitHub:\n>>\n>>   - https://github.com/git/git/actions/runs/1297117791 (d3a1c4e)\n>>   - https://github.com/git/git/actions/runs/1297232973 (a1108c2)\n>>\n>> The difference between the former (which passes all the tests) and\n>> he latter (which fails) are only two topics:\n>>\n>>      $ git log --first-parent --oneline d3a1c4e..a1108c2\n>>      a1108c2b1b Merge branch 'hn/reftable' into seen\n>>      e575f29006 Merge branch 'pw/fix-some-issues-in-reset-head' into seen\n>>\n>> I think the following is the same failure I saw earlier\n>>\n>>     \n>> https://github.com/git/git/runs/3773780195?check_suite_focus=true#step:6:5033 \n>>\n>>\n>> that the write-tree codepath hits assertion failure by detecting a\n>> corruption in the cache-tree data structure.\n> \n> The test that fails (t1092-sparse-checkout-compatibility.sh:'merge, \n> cherry-pick, and rebase') was introduced by c0b99303db (\"t1092: add \n> cherry-pick, rebase tests\", 2021-09-08) and is in v2.33.0. It does not \n> test the \"apply\" backend of rebase, changing it to do so makes it fail \n> on the current master as that backend already uses reset_head() for the \n> initial checkout so it is an existing bug that is exposed by the changes \n> in this series. It seems to be a use-after-free (see below) I'll try and \n> investigate but I've got no idea what is going on at the moment - the \n> index is not my area of expertise.\n\nremoving the call to prime_cache_tree() from reset_head() fixes the \ncrash. It is called after unpack_trees() and before \nwrite_locked_index(), I'm not sure what the implications of removing it \nare - why it was there it is needed?\n\nBest Wishes\n\nPhillip\n\n> Best Wishes\n> \n> Phillip\n> \n> ==74345==ERROR: AddressSanitizer: heap-use-after-free on address \n> 0x606000001b20 at pc 0x557cbe82d3a2 bp 0x7ffdfee08090 sp 0x7ffdfee08080\n> READ of size 4 at 0x606000001b20 thread T0\n>      #0 0x557cbe82d3a1 in verify_one /home/phil/src/git/cache-tree.c:863\n>      #1 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>      #2 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>      #3 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>      #4 0x557cbe830a2b in cache_tree_verify \n> /home/phil/src/git/cache-tree.c:910\n>      #5 0x557cbea53741 in write_locked_index \n> /home/phil/src/git/read-cache.c:3250\n>      #6 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n>      #7 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>      #8 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>      #9 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>      #10 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>      #11 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>      #12 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>      #13 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n>      #14 0x557cbe5bcb8d in _start (/home/phil/src/git/git+0x1b9b8d)\n> \n> 0x606000001b20 is located 0 bytes inside of 56-byte region \n> [0x606000001b20,0x606000001b58)\n> freed by thread T0 here:\n>      #0 0x7fdd4bacff19 in __interceptor_free \n> /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:127\n>      #1 0x557cbe82af60 in cache_tree_free \n> /home/phil/src/git/cache-tree.c:35\n>      #2 0x557cbe82aee5 in cache_tree_free \n> /home/phil/src/git/cache-tree.c:31\n>      #3 0x557cbe82aee5 in cache_tree_free \n> /home/phil/src/git/cache-tree.c:31\n>      #4 0x557cbe82aee5 in cache_tree_free \n> /home/phil/src/git/cache-tree.c:31\n>      #5 0x557cbeb2557a in ensure_full_index \n> /home/phil/src/git/sparse-index.c:310\n>      #6 0x557cbea45c4a in index_name_stage_pos \n> /home/phil/src/git/read-cache.c:588\n>      #7 0x557cbe82ce37 in verify_one /home/phil/src/git/cache-tree.c:850\n>      #8 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>      #9 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>      #10 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>      #11 0x557cbe830a2b in cache_tree_verify \n> /home/phil/src/git/cache-tree.c:910\n>      #12 0x557cbea53741 in write_locked_index \n> /home/phil/src/git/read-cache.c:3250\n>      #13 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n>      #14 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>      #15 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>      #16 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>      #17 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>      #18 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>      #19 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>      #20 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n> \n> previously allocated by thread T0 here:\n>      #0 0x7fdd4bad0459 in __interceptor_calloc \n> /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:154\n>      #1 0x557cbebc1807 in xcalloc /home/phil/src/git/wrapper.c:140\n>      #2 0x557cbe82b7d8 in cache_tree /home/phil/src/git/cache-tree.c:17\n>      #3 0x557cbe82b7d8 in prime_cache_tree_rec \n> /home/phil/src/git/cache-tree.c:763\n>      #4 0x557cbe82b837 in prime_cache_tree_rec \n> /home/phil/src/git/cache-tree.c:764\n>      #5 0x557cbe82b837 in prime_cache_tree_rec \n> /home/phil/src/git/cache-tree.c:764\n>      #6 0x557cbe8304e1 in prime_cache_tree \n> /home/phil/src/git/cache-tree.c:779\n>      #7 0x557cbeab7fa7 in reset_head /home/phil/src/git/reset.c:85\n>      #8 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>      #9 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>      #10 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>      #11 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>      #12 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>      #13 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>      #14 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n> \n> SUMMARY: AddressSanitizer: heap-use-after-free \n> /home/phil/src/git/cache-tree.c:863 in verify_one\n> Shadow bytes around the buggy address:\n>    0x0c0c7fff8310: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n>    0x0c0c7fff8320: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fa\n>    0x0c0c7fff8330: fa fa fa fa 00 00 00 00 00 00 00 02 fa fa fa fa\n>    0x0c0c7fff8340: fd fd fd fd fd fd fd fa fa fa fa fa 00 00 00 00\n>    0x0c0c7fff8350: 00 00 00 02 fa fa fa fa fd fd fd fd fd fd fd fa\n> =>0x0c0c7fff8360: fa fa fa fa[fd]fd fd fd fd fd fd fa fa fa fa fa\n>    0x0c0c7fff8370: 00 00 00 00 00 00 00 02 fa fa fa fa fd fd fd fd\n>    0x0c0c7fff8380: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 02\n>    0x0c0c7fff8390: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa\n>    0x0c0c7fff83a0: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n>    0x0c0c7fff83b0: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 fa\n> Shadow byte legend (one shadow byte represents 8 application bytes):\n>    Addressable:           00\n>    Partially addressable: 01 02 03 04 05 06 07\n>    Heap left redzone:       fa\n>    Freed heap region:       fd\n>    Stack left redzone:      f1\n>    Stack mid redzone:       f2\n>    Stack right redzone:     f3\n>    Stack after return:      f5\n>    Stack use after scope:   f8\n>    Global redzone:          f9\n>    Global init order:       f6\n>    Poisoned by user:        f7\n>    Container overflow:      fc\n>    Array cookie:            ac\n>    Intra object redzone:    bb\n>    ASan internal:           fe\n>    Left alloca redzone:     ca\n>    Right alloca redzone:    cb\n>    Shadow gap:              cc\n> ==74345==ABORTING\n> \n> \n>> e575f29006 (i.e. without the reftable topic) fails t1092.  If you\n>> revert e575f29006^2 (i.e. the \"do not fork 'git checkout'\") from\n>> that merge, all tests pass including t1092.\n>>\n>> The reftable topic is queued near the tip of 'seen' not necessarily\n>> because it _breaks_ CI (I do not think it does), but it needed a\n>> handful of fixup commits on top.  The topic needs rerolling with the\n>> fixes squashed in.\n>>\n>> Thanks.\n>>\n> \n\n"},{"id":"437765","messageId":"5af2ddb1-0330-a987-9887-14331f6ea006@web.de","threadId":"56620","inReplyTo":"ddf5e9c2-3211-cec2-cd18-2a083e279ccb@gmail.com","subject":"Re: [PATCH 00/11] rebase: reset_head() related fixes and improvements","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2021-10-02T13:38:50Z","receivedAt":"2021-10-02T13:43:13Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 02.10.21 um 14:27 schrieb Phillip Wood:\n> Hi Junio\n>\n> On 02/10/2021 05:58, Junio C Hamano wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> When merged with other topics in flight in 'seen', this seems to\n>>> fail the t1092 test (most likely, ds/add-rm-with-sparse-index is\n>>> what this interacts badly with).\n>\n> Oh dear, thanks for letting me know.\n>\n>> There are Two CI runs at GitHub:\n>>\n>>   - https://github.com/git/git/actions/runs/1297117791 (d3a1c4e)\n>>   - https://github.com/git/git/actions/runs/1297232973 (a1108c2)\n>>\n>> The difference between the former (which passes all the tests) and\n>> he latter (which fails) are only two topics:\n>>\n>>      $ git log --first-parent --oneline d3a1c4e..a1108c2\n>>      a1108c2b1b Merge branch 'hn/reftable' into seen\n>>      e575f29006 Merge branch 'pw/fix-some-issues-in-reset-head' into seen\n>>\n>> I think the following is the same failure I saw earlier\n>>\n>>     https://github.com/git/git/runs/3773780195?check_suite_focus=true#step:6:5033\n>>\n>> that the write-tree codepath hits assertion failure by detecting a\n>> corruption in the cache-tree data structure.\n>\n> The test that fails (t1092-sparse-checkout-compatibility.sh:'merge, cherry-pick, and rebase') was introduced by c0b99303db (\"t1092: add cherry-pick, rebase tests\", 2021-09-08) and is in v2.33.0. It does not test the \"apply\" backend of rebase, changing it to do so makes it fail on the current master as that backend already uses reset_head() for the initial checkout so it is an existing bug that is exposed by the changes in this series. It seems to be a use-after-free (see below) I'll try and investigate but I've got no idea what is going on at the moment - the index is not my area of expertise.\n>\n> Best Wishes\n>\n> Phillip\n>\n> ==74345==ERROR: AddressSanitizer: heap-use-after-free on address 0x606000001b20 at pc 0x557cbe82d3a2 bp 0x7ffdfee08090 sp 0x7ffdfee08080\n> READ of size 4 at 0x606000001b20 thread T0\n>     #0 0x557cbe82d3a1 in verify_one /home/phil/src/git/cache-tree.c:863\n>     #1 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>     #2 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>     #3 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>     #4 0x557cbe830a2b in cache_tree_verify /home/phil/src/git/cache-tree.c:910\n>     #5 0x557cbea53741 in write_locked_index /home/phil/src/git/read-cache.c:3250\n>     #6 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n>     #7 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>     #8 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>     #9 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>     #10 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>     #11 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>     #12 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>     #13 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n>     #14 0x557cbe5bcb8d in _start (/home/phil/src/git/git+0x1b9b8d)\n>\n> 0x606000001b20 is located 0 bytes inside of 56-byte region [0x606000001b20,0x606000001b58)\n> freed by thread T0 here:\n>     #0 0x7fdd4bacff19 in __interceptor_free /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:127\n>     #1 0x557cbe82af60 in cache_tree_free /home/phil/src/git/cache-tree.c:35\n>     #2 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n>     #3 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n>     #4 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n>     #5 0x557cbeb2557a in ensure_full_index /home/phil/src/git/sparse-index.c:310\n>     #6 0x557cbea45c4a in index_name_stage_pos /home/phil/src/git/read-cache.c:588\n>     #7 0x557cbe82ce37 in verify_one /home/phil/src/git/cache-tree.c:850\n>     #8 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>     #9 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>     #10 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>     #11 0x557cbe830a2b in cache_tree_verify /home/phil/src/git/cache-tree.c:910\n\nLooks like cache_tree_verify() indirectly triggered ensure_full_index(),\nwhich throws the cache_tree away, then kept going using stale pointers.\nIt should instead stop and start over in such a case, no?\n\n>     #12 0x557cbea53741 in write_locked_index /home/phil/src/git/read-cache.c:3250\n>     #13 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n>     #14 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>     #15 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>     #16 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>     #17 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>     #18 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>     #19 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>     #20 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n>\n> previously allocated by thread T0 here:\n>     #0 0x7fdd4bad0459 in __interceptor_calloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:154\n>     #1 0x557cbebc1807 in xcalloc /home/phil/src/git/wrapper.c:140\n>     #2 0x557cbe82b7d8 in cache_tree /home/phil/src/git/cache-tree.c:17\n>     #3 0x557cbe82b7d8 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:763\n>     #4 0x557cbe82b837 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:764\n>     #5 0x557cbe82b837 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:764\n>     #6 0x557cbe8304e1 in prime_cache_tree /home/phil/src/git/cache-tree.c:779\n>     #7 0x557cbeab7fa7 in reset_head /home/phil/src/git/reset.c:85\n>     #8 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>     #9 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>     #10 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>     #11 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>     #12 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>     #13 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>     #14 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n>\n> SUMMARY: AddressSanitizer: heap-use-after-free /home/phil/src/git/cache-tree.c:863 in verify_one\n> Shadow bytes around the buggy address:\n>   0x0c0c7fff8310: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n>   0x0c0c7fff8320: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fa\n>   0x0c0c7fff8330: fa fa fa fa 00 00 00 00 00 00 00 02 fa fa fa fa\n>   0x0c0c7fff8340: fd fd fd fd fd fd fd fa fa fa fa fa 00 00 00 00\n>   0x0c0c7fff8350: 00 00 00 02 fa fa fa fa fd fd fd fd fd fd fd fa\n> =>0x0c0c7fff8360: fa fa fa fa[fd]fd fd fd fd fd fd fa fa fa fa fa\n>   0x0c0c7fff8370: 00 00 00 00 00 00 00 02 fa fa fa fa fd fd fd fd\n>   0x0c0c7fff8380: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 02\n>   0x0c0c7fff8390: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa\n>   0x0c0c7fff83a0: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n>   0x0c0c7fff83b0: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 fa\n> Shadow byte legend (one shadow byte represents 8 application bytes):\n>   Addressable:           00\n>   Partially addressable: 01 02 03 04 05 06 07\n>   Heap left redzone:       fa\n>   Freed heap region:       fd\n>   Stack left redzone:      f1\n>   Stack mid redzone:       f2\n>   Stack right redzone:     f3\n>   Stack after return:      f5\n>   Stack use after scope:   f8\n>   Global redzone:          f9\n>   Global init order:       f6\n>   Poisoned by user:        f7\n>   Container overflow:      fc\n>   Array cookie:            ac\n>   Intra object redzone:    bb\n>   ASan internal:           fe\n>   Left alloca redzone:     ca\n>   Right alloca redzone:    cb\n>   Shadow gap:              cc\n> ==74345==ABORTING\n>\n>\n>> e575f29006 (i.e. without the reftable topic) fails t1092.  If you\n>> revert e575f29006^2 (i.e. the \"do not fork 'git checkout'\") from\n>> that merge, all tests pass including t1092.\n>>\n>> The reftable topic is queued near the tip of 'seen' not necessarily\n>> because it _breaks_ CI (I do not think it does), but it needed a\n>> handful of fixup commits on top.  The topic needs rerolling with the\n>> fixes squashed in.\n>>\n>> Thanks.\n>>\n>\n\n"},{"id":"437867","messageId":"648396f6-56ce-b727-4567-2b6eba2f4f46@gmail.com","threadId":"56620","inReplyTo":"xmqqee947aqn.fsf@gitster.g","subject":"Re: [PATCH 02/11] reset_head(): fix checkout","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-04T09:58:59Z","receivedAt":"2021-10-04T09:59:05Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 01/10/2021 21:26, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> The reset bit should only be set if flags contains RESET_HEAD_HARD.\n>> The test for `!deatch_head` dates back to the original implementation\n>> of reset_head() in ac7f467fef (\"builtin/rebase: support running \"git\n>> rebase <upstream>\"\", 2018-08-07) and was correct until e65123a71d\n>> (\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n>> 2018-09-04) started using reset_head() to checkout <switch-to> when\n>> fast-forwarding.\n> \n> Sorry, but it is not quite clear what exactly is \"fix checkout\" in\n> the context of this change, even with the above paragraph that\n> describes the internals but not any end-user visible effect.\n\n\"git checkout\" refuses to overwrite untracked files but reset_head() \ndoes when checking out a branch.\n\n> Can this step come with its own addition to t/ to demonstrate the\n> breakage that is fixed?\n\nI can add a test to check that a checkout does not remove untracked \nfiles. However such a test would pass on top of \nen/remaving-untracked-fixes without the fix in this patch. I cannot \nthink of a way to specifically test that unpack_tree_opts.reset == 0 \nunless RESET_HEAD_HARD is given after en/removing-untracked-fixes is \nmerged. Elijah's fixes will stop the \"reset\" mode of reset_head() from \nwiping untracked files which is good. The reset flag to unpack trees \nalso affects unmerged entries but rebase does not try to checkout \nanything if the index contains unmerged entries.\n\nBest Wishes\n\nPhillip\n\n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   reset.c | 2 +-\n>>   1 file changed, 1 insertion(+), 1 deletion(-)\n>>\n>> diff --git a/reset.c b/reset.c\n>> index 79310ae071b..fc4dae3fd2d 100644\n>> --- a/reset.c\n>> +++ b/reset.c\n>> @@ -57,7 +57,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n>>   \tunpack_tree_opts.update = 1;\n>>   \tunpack_tree_opts.merge = 1;\n>>   \tinit_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);\n>> -\tif (!detach_head)\n>> +\tif (reset_hard)\n>>   \t\tunpack_tree_opts.reset = 1;\n>>   \n>>   \tif (repo_read_index_unmerged(r) < 0) {\n"},{"id":"437868","messageId":"b298f8cb-f55f-86f0-52fc-8d9db0047525@gmail.com","threadId":"56620","inReplyTo":"xmqqtui05uxr.fsf@gitster.g","subject":"Re: [PATCH 03/11] reset_head(): don't run checkout hook if there is an error","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-04T10:00:07Z","receivedAt":"2021-10-04T10:00:12Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 01/10/2021 21:52, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> The hook should only be run if the worktree and refs were successfully\n>> updated.\n> \n> OK.  This is a behaviour change visible to end-users, and deserves a\n> mention in the release notes.\n> \n>   - When \"git rebase\" attempted to check out a branch (or detached\n\nIt only affects \"git rebase --apply\" as the \"merge\" backend forks \"git \ncheckout\" which does not run the hook if it cannot update the worktree \nor refs.\n\n>     the HEAD) to work on, we used to always call the \"post-checkout\"\n>     hook, even if the checkout failed to update the ref.  The hook is\n>     no longer called if the checkout fails.\n> \n> or something.\n> \n> Again, can the bug this step fixes be protected with a new test in\n> t/ please?\n\nI'll try and come up with something - it should be possible to arrange \nan untracked file to make unpack_trees() to fail, I'm not sure how we'd \nmake update_ref() fail\n\nBest Wishes\n\nPhillip\n\n>>\n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   reset.c | 2 +-\n>>   1 file changed, 1 insertion(+), 1 deletion(-)\n>>\n>> diff --git a/reset.c b/reset.c\n>> index fc4dae3fd2d..5abb1a5683e 100644\n>> --- a/reset.c\n>> +++ b/reset.c\n>> @@ -125,7 +125,7 @@ reset_head_refs:\n>>   \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n>>   \t\t\t\t\t    reflog_head);\n>>   \t}\n>> -\tif (run_hook)\n>> +\tif (!ret && run_hook)\n>>   \t\trun_hook_le(NULL, \"post-checkout\",\n>>   \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n>>   \t\t\t    oid_to_hex(oid), \"1\", NULL);\n"},{"id":"437869","messageId":"97b8cbdb-60f9-cba5-2db0-6ae77a9afd60@gmail.com","threadId":"56620","inReplyTo":"xmqqpmso5uno.fsf@gitster.g","subject":"Re: [PATCH 04/11] reset_head(): remove action parameter","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-04T10:00:22Z","receivedAt":"2021-10-04T10:00:29Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 01/10/2021 21:58, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> The action parameter is passed as the command name to\n>> setup_unpack_trees_porcelain(). All but two cases pass either\n>> \"checkout\" or \"reset\". The case that passes \"reset --hard\" should be\n>> passing \"reset\" instead.\n> \n> Describe how the parameter is meant to be used (presumably \"this is\n> to record in the reflog\", perhaps?); without such explanation, it is\n> hard to either agree or disagree with the claim that \"reset --hard\"\n> should be \"reset\".\n\nHow about\n\nThe only use of the action parameter is to setup the error messages for \nunpack_trees(). All but two cases pass either \"checkout\" or \"reset\". The \ncase that passes \"reset --hard\" would be better passing \"reset\" so that \nthe error messages match the builtin reset command like all the other \ncallers that are doing a reset. The case that passes \"Fast-forwarded\" is \nonly updating HEAD and so the parameter is unused in that case as it \ndoes not call unpack_trees(). The value to pass to \nsetup_unpack_trees_porcelain() can be determined by checking whether \nflags contains RESET_HEAD_HARD without the caller having to specify it.\n\n> Also state if this change is supposed to have any externally\n> observable effect.\n\nIt'll change the error message if we cannot clear the stashed changes \nform the working tree from saying \"reset --hard\" to \"reset\".\n\n\nBest Wishes\n\nPhillip\n\n> Perhaps this improves what is shown in an error message by affecting\n> what setup_unpack_trees_porcelain() does?  I am just guessing,\n> because the proposed log message is not telling.  Please do not make\n> me (or other readers of \"git log\") guess.\n> \n> Thanks.\n> \n"},{"id":"437870","messageId":"27b8539e-f097-3027-ffee-f54de09a6361@gmail.com","threadId":"56620","inReplyTo":"xmqqlf3c5ul1.fsf@gitster.g","subject":"Re: [PATCH 05/11] reset_head(): factor out ref updates","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-04T10:03:04Z","receivedAt":"2021-10-04T10:03:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 01/10/2021 22:00, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> In the next commit we will stop trying to update HEAD when we are just\n>> clearing changes from the working tree. Move the code that updates the\n> \n> \"clearing changes\" meaning...?  Like \"rebase --abort\"?\n\n\"rebase --abort\" will update HEAD, \"rebase --skip\" would be a better \nexample, I'll expand the commit message.\n\nBest Wishes\n\nPhillip\n\n>> refs to its own function in preparation for that.\n>>\n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   reset.c | 112 +++++++++++++++++++++++++++++++-------------------------\n>>   1 file changed, 62 insertions(+), 50 deletions(-)\n>>\n>> diff --git a/reset.c b/reset.c\n>> index 9ab007c0c34..668c7639127 100644\n>> --- a/reset.c\n>> +++ b/reset.c\n>> @@ -8,26 +8,75 @@\n>>   #include \"tree.h\"\n>>   #include \"unpack-trees.h\"\n>>   \n>> +static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n>> +\t\t       const char *reflog_head, const char *reflog_orig_head,\n>> +\t\t       const char *default_reflog_action, unsigned flags)\n>> +{\n>> +\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n>> +\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n>> +\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n>> +\tstruct object_id *orig = NULL, oid_orig, *old_orig = NULL, oid_old_orig;\n>> +\tstruct strbuf msg = STRBUF_INIT;\n>> +\tconst char *reflog_action;\n>> +\tsize_t prefix_len;\n>> +\tint ret;\n>> +\n>> +\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n>> +\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n>> +\tprefix_len = msg.len;\n>> +\n>> +\tif (update_orig_head) {\n>> +\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n>> +\t\t\told_orig = &oid_old_orig;\n>> +\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n>> +\t\t\torig = &oid_orig;\n>> +\t\t\tif (!reflog_orig_head) {\n>> +\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n>> +\t\t\t\treflog_orig_head = msg.buf;\n>> +\t\t\t}\n>> +\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n>> +\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n>> +\t\t} else if (old_orig)\n>> +\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n>> +\t}\n>> +\n>> +\tif (!reflog_head) {\n>> +\t\tstrbuf_setlen(&msg, prefix_len);\n>> +\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n>> +\t\treflog_head = msg.buf;\n>> +\t}\n>> +\tif (!switch_to_branch)\n>> +\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n>> +\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n>> +\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n>> +\telse {\n>> +\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n>> +\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n>> +\t\tif (!ret)\n>> +\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n>> +\t\t\t\t\t    reflog_head);\n>> +\t}\n>> +\tif (!ret && run_hook)\n>> +\t\trun_hook_le(NULL, \"post-checkout\",\n>> +\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n>> +\t\t\t    oid_to_hex(oid), \"1\", NULL);\n>> +\tstrbuf_release(&msg);\n>> +\treturn ret;\n>> +}\n>> +\n>>   int reset_head(struct repository *r, struct object_id *oid,\n>>   \t       const char *switch_to_branch, unsigned flags,\n>>   \t       const char *reflog_orig_head, const char *reflog_head,\n>>   \t       const char *default_reflog_action)\n>>   {\n>> -\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n>>   \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n>> -\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n>>   \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n>> -\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n>>   \tstruct object_id head_oid;\n>>   \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n>>   \tstruct lock_file lock = LOCK_INIT;\n>>   \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n>>   \tstruct tree *tree;\n>> -\tconst char *action, *reflog_action;\n>> -\tstruct strbuf msg = STRBUF_INIT;\n>> -\tsize_t prefix_len;\n>> -\tstruct object_id *orig = NULL, oid_orig,\n>> -\t\t*old_orig = NULL, oid_old_orig;\n>> +\tconst char *action;\n>>   \tint ret = 0, nr = 0;\n>>   \n>>   \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n>> @@ -47,7 +96,9 @@ int reset_head(struct repository *r, struct object_id *oid,\n>>   \t\toid = &head_oid;\n>>   \n>>   \tif (refs_only)\n>> -\t\tgoto reset_head_refs;\n>> +\t\treturn update_refs(oid, switch_to_branch, reflog_head,\n>> +\t\t\t\t   reflog_orig_head, default_reflog_action,\n>> +\t\t\t\t   flags);\n>>   \n>>   \taction = reset_hard ? \"reset\" : \"checkout\";\n>>   \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n>> @@ -90,49 +141,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n>>   \t\tgoto leave_reset_head;\n>>   \t}\n>>   \n>> -reset_head_refs:\n>> -\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n>> -\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n>> -\tprefix_len = msg.len;\n>> -\n>> -\tif (update_orig_head) {\n>> -\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n>> -\t\t\told_orig = &oid_old_orig;\n>> -\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n>> -\t\t\torig = &oid_orig;\n>> -\t\t\tif (!reflog_orig_head) {\n>> -\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n>> -\t\t\t\treflog_orig_head = msg.buf;\n>> -\t\t\t}\n>> -\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n>> -\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n>> -\t\t} else if (old_orig)\n>> -\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n>> -\t}\n>> -\n>> -\tif (!reflog_head) {\n>> -\t\tstrbuf_setlen(&msg, prefix_len);\n>> -\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n>> -\t\treflog_head = msg.buf;\n>> -\t}\n>> -\tif (!switch_to_branch)\n>> -\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n>> -\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n>> -\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n>> -\telse {\n>> -\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n>> -\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n>> -\t\tif (!ret)\n>> -\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n>> -\t\t\t\t\t    reflog_head);\n>> -\t}\n>> -\tif (!ret && run_hook)\n>> -\t\trun_hook_le(NULL, \"post-checkout\",\n>> -\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n>> -\t\t\t    oid_to_hex(oid), \"1\", NULL);\n>> +\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n>> +\t\t\t  default_reflog_action, flags);\n>>   \n>>   leave_reset_head:\n>> -\tstrbuf_release(&msg);\n>>   \trollback_lock_file(&lock);\n>>   \tclear_unpack_trees_porcelain(&unpack_tree_opts);\n>>   \twhile (nr)\n"},{"id":"437871","messageId":"0a580f49-4366-526b-d601-f8ce5f99711c@gmail.com","threadId":"56620","inReplyTo":"xmqqczoo5u7m.fsf@gitster.g","subject":"Re: [PATCH 06/11] reset_head(): make default_reflog_action optional","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-04T10:03:27Z","receivedAt":"2021-10-04T10:03:33Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 01/10/2021 22:08, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> This parameter is only needed when a ref is going to be updated and\n>> the caller does not pass an explicit reflog message. Callers that are\n>> just discarding changes in the working tree like create_autostash() do\n>> not update any refs so should not have to worry about passing this\n>> parameter.\n> \n> This only talks about internal implementation details of passing a\n> parameter that may not be used, but ...\n> \n>> -\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n>> -\t\t\t  default_reflog_action, flags);\n>> +\tif (oid != &head_oid || update_orig_head || switch_to_branch)\n>> +\t\tret = update_refs(oid, switch_to_branch, reflog_head,\n>> +\t\t\t\t  reflog_orig_head, default_reflog_action,\n>> +\t\t\t\t  flags);\n> \n> ... doesn't this have a more significant behaviour change?\n> \n> I am not sure if comparison between oid and head_oid can safely\n> cheat like the patch does, or if it is necessary to do a proper oid > comparison, but either way, this would stop calling update_refs(),\n> which in turn means it would have an externally visible effect, like\n> a hook no longer getting called, doesn't it?\n> \n> It would be a change for the good---calling post-checkout hook when\n> you did \"git checkout (no other arguments)\" feels wasteful, but it\n> deserves to be told to end users, I would think.\n\nThat would be a change for the good, but it is not what this patch does.\nIf oid == &head_oid then it means that the caller did not give us a tree \nto checkout so HEAD will not change unless we are switching branches. \nUsing a pointer comparison rather than calling oidcmp() was a deliberate \nchoice to avoid any user visible changes as there are no callers that \ntry to run the checkout-hook without passing an oid or branch. The aim \nhere is to avoid having to pass default_reflog when it is not needed \nwithout making any user visible changes.\n\n> Again, a new test to protect the change would go well in the same\n> patch.\n\nI'm not sure what we could test\n\nBest Wishes\n\nPhillip\n\n> Thanks.\n> \n> \n"},{"id":"437872","messageId":"e423b101-77e2-02df-ee36-03c692c44e6c@gmail.com","threadId":"56620","inReplyTo":"xmqq8rzc5u2j.fsf@gitster.g","subject":"Re: [PATCH 08/11] reset_head(): take struct rebase_head_opts","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-04T10:09:24Z","receivedAt":"2021-10-04T10:09:29Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 01/10/2021 22:11, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> This function already takes a confusingly large number of parameters\n>> some of which are optional or not always required. The following\n>> commits will add a couple more parameters so change it to take a\n>> struct of options first.\n> \n> Would all the members in the struct always be used by the function?\n> If not, such a change will *not* solve the \"confusingly ... required\"\n> problem at all.\n\nI'll rephrase to emphasize that it solves the problem of remembering \nwhich order to pass things in and makes it clear what is being passed\n\n> I am not necessarily against a change to consolidate a bag of\n> parameters into a pointer to a struct.  I am against a change that\n> is justified with a benefit that the change does not bring to us.\n\nI found the existing api hard to use because it is easy to lose track of \nwhich position each parameter should be in. With the struct the order \ndoes not matter and having to set opts.parameter = value makes it clear \nwhich parameters are being passed.\n\nBest Wishes\n\nPhillip\n\n> Thanks.\n> \n"},{"id":"437907","messageId":"xmqqfstgydiq.fsf@gitster.g","threadId":"56620","inReplyTo":"648396f6-56ce-b727-4567-2b6eba2f4f46@gmail.com","subject":"Re: [PATCH 02/11] reset_head(): fix checkout","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-04T16:13:01Z","receivedAt":"2021-10-04T16:13:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> Sorry, but it is not quite clear what exactly is \"fix checkout\" in\n>> the context of this change, even with the above paragraph that\n>> describes the internals but not any end-user visible effect.\n>\n> \"git checkout\" refuses to overwrite untracked files but reset_head()\n> does when checking out a branch.\n\nBy the name of the function, I would have expected that would be the\nintended behaviour, though.  Isn't it emulating \"reset --hard\" that\nwas used when the calling commands were implemented as scripts?\n\nWhat I am wondering is if the overwriting behaviour is annoying for\nsome callers but is essential for other callers, and unless we tell\nreaders that we vetted all the callers and everybody wants to keep\nthe paths overwritten from the tree-ish in the working tree that are\nnot in the index, I feel uneasy to add a label \"fix\" to such a change\nto a callee used from multiple code paths.\n\n> files. However such a test would pass on top of \n> en/remaving-untracked-fixes without the fix in this patch. I cannot\n> think of a way to specifically test that unpack_tree_opts.reset == 0 \n> unless RESET_HEAD_HARD is given after en/removing-untracked-fixes is\n> merged.\n\nMeaning that this fix is redundant, as the other topic has already\nbeen cooking and well in the process of graduating?\n\nThanks.\n"},{"id":"438066","messageId":"04cdeb7c-4469-7036-40f4-87543e729026@gmail.com","threadId":"56620","inReplyTo":"5af2ddb1-0330-a987-9887-14331f6ea006@web.de","subject":"Re: [PATCH 00/11] rebase: reset_head() related fixes and improvements","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-10-06T14:03:14Z","receivedAt":"2021-10-06T14:03:21Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 02/10/2021 14:38, René Scharfe wrote:\n>[..]\n>> ==74345==ERROR: AddressSanitizer: heap-use-after-free on address 0x606000001b20 at pc 0x557cbe82d3a2 bp 0x7ffdfee08090 sp 0x7ffdfee08080\n>> READ of size 4 at 0x606000001b20 thread T0\n>>      #0 0x557cbe82d3a1 in verify_one /home/phil/src/git/cache-tree.c:863\n>>      #1 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>>      #2 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>>      #3 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>>      #4 0x557cbe830a2b in cache_tree_verify /home/phil/src/git/cache-tree.c:910\n>>      #5 0x557cbea53741 in write_locked_index /home/phil/src/git/read-cache.c:3250\n>>      #6 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n>>      #7 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>>      #8 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>>      #9 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>>      #10 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>>      #11 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>>      #12 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>>      #13 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n>>      #14 0x557cbe5bcb8d in _start (/home/phil/src/git/git+0x1b9b8d)\n>>\n>> 0x606000001b20 is located 0 bytes inside of 56-byte region [0x606000001b20,0x606000001b58)\n>> freed by thread T0 here:\n>>      #0 0x7fdd4bacff19 in __interceptor_free /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:127\n>>      #1 0x557cbe82af60 in cache_tree_free /home/phil/src/git/cache-tree.c:35\n>>      #2 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n>>      #3 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n>>      #4 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31\n>>      #5 0x557cbeb2557a in ensure_full_index /home/phil/src/git/sparse-index.c:310\n>>      #6 0x557cbea45c4a in index_name_stage_pos /home/phil/src/git/read-cache.c:588\n>>      #7 0x557cbe82ce37 in verify_one /home/phil/src/git/cache-tree.c:850\n>>      #8 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>>      #9 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>>      #10 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840\n>>      #11 0x557cbe830a2b in cache_tree_verify /home/phil/src/git/cache-tree.c:910\n> \n> Looks like cache_tree_verify() indirectly triggered ensure_full_index(),\n> which throws the cache_tree away, then kept going using stale pointers.\n> It should instead stop and start over in such a case, no?\n\nThanks for that, I found it really helpful\n\nBest Wishes\n\nPhillip\n\n>>      #12 0x557cbea53741 in write_locked_index /home/phil/src/git/read-cache.c:3250\n>>      #13 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87\n>>      #14 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>>      #15 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>>      #16 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>>      #17 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>>      #18 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>>      #19 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>>      #20 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n>>\n>> previously allocated by thread T0 here:\n>>      #0 0x7fdd4bad0459 in __interceptor_calloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:154\n>>      #1 0x557cbebc1807 in xcalloc /home/phil/src/git/wrapper.c:140\n>>      #2 0x557cbe82b7d8 in cache_tree /home/phil/src/git/cache-tree.c:17\n>>      #3 0x557cbe82b7d8 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:763\n>>      #4 0x557cbe82b837 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:764\n>>      #5 0x557cbe82b837 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:764\n>>      #6 0x557cbe8304e1 in prime_cache_tree /home/phil/src/git/cache-tree.c:779\n>>      #7 0x557cbeab7fa7 in reset_head /home/phil/src/git/reset.c:85\n>>      #8 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074\n>>      #9 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461\n>>      #10 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714\n>>      #11 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781\n>>      #12 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912\n>>      #13 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52\n>>      #14 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)\n>>\n>> SUMMARY: AddressSanitizer: heap-use-after-free /home/phil/src/git/cache-tree.c:863 in verify_one\n>> Shadow bytes around the buggy address:\n>>    0x0c0c7fff8310: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n>>    0x0c0c7fff8320: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fa\n>>    0x0c0c7fff8330: fa fa fa fa 00 00 00 00 00 00 00 02 fa fa fa fa\n>>    0x0c0c7fff8340: fd fd fd fd fd fd fd fa fa fa fa fa 00 00 00 00\n>>    0x0c0c7fff8350: 00 00 00 02 fa fa fa fa fd fd fd fd fd fd fd fa\n>> =>0x0c0c7fff8360: fa fa fa fa[fd]fd fd fd fd fd fd fa fa fa fa fa\n>>    0x0c0c7fff8370: 00 00 00 00 00 00 00 02 fa fa fa fa fd fd fd fd\n>>    0x0c0c7fff8380: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 02\n>>    0x0c0c7fff8390: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa\n>>    0x0c0c7fff83a0: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd\n>>    0x0c0c7fff83b0: fd fd fd fa fa fa fa fa 00 00 00 00 00 00 00 fa\n>> Shadow byte legend (one shadow byte represents 8 application bytes):\n>>    Addressable:           00\n>>    Partially addressable: 01 02 03 04 05 06 07\n>>    Heap left redzone:       fa\n>>    Freed heap region:       fd\n>>    Stack left redzone:      f1\n>>    Stack mid redzone:       f2\n>>    Stack right redzone:     f3\n>>    Stack after return:      f5\n>>    Stack use after scope:   f8\n>>    Global redzone:          f9\n>>    Global init order:       f6\n>>    Poisoned by user:        f7\n>>    Container overflow:      fc\n>>    Array cookie:            ac\n>>    Intra object redzone:    bb\n>>    ASan internal:           fe\n>>    Left alloca redzone:     ca\n>>    Right alloca redzone:    cb\n>>    Shadow gap:              cc\n>> ==74345==ABORTING\n>>\n>>\n>>> e575f29006 (i.e. without the reftable topic) fails t1092.  If you\n>>> revert e575f29006^2 (i.e. the \"do not fork 'git checkout'\") from\n>>> that merge, all tests pass including t1092.\n>>>\n>>> The reftable topic is queued near the tip of 'seen' not necessarily\n>>> because it _breaks_ CI (I do not think it does), but it needed a\n>>> handful of fixup commits on top.  The topic needs rerolling with the\n>>> fixes squashed in.\n>>>\n>>> Thanks.\n>>>\n>>\n> \n"},{"id":"438520","messageId":"877deilixj.fsf@evledraar.gmail.com","threadId":"56620","inReplyTo":"b298f8cb-f55f-86f0-52fc-8d9db0047525@gmail.com","subject":"Re: [PATCH 03/11] reset_head(): don't run checkout hook if there is an error","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-12T08:48:19Z","receivedAt":"2021-10-12T08:57:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Oct 04 2021, Phillip Wood wrote:\n\n> Hi Junio\n>\n> On 01/10/2021 21:52, Junio C Hamano wrote:\n>> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> \n>>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>>\n>>> The hook should only be run if the worktree and refs were successfully\n>>> updated.\n>> OK.  This is a behaviour change visible to end-users, and deserves a\n>> mention in the release notes.\n>>   - When \"git rebase\" attempted to check out a branch (or detached\n>\n> It only affects \"git rebase --apply\" as the \"merge\" backend forks \"git\n> checkout\" which does not run the hook if it cannot update the worktree \n> or refs.\n>\n>>     the HEAD) to work on, we used to always call the \"post-checkout\"\n>>     hook, even if the checkout failed to update the ref.  The hook is\n>>     no longer called if the checkout fails.\n>> or something.\n>> Again, can the bug this step fixes be protected with a new test in\n>> t/ please?\n>\n> I'll try and come up with something - it should be possible to arrange\n> an untracked file to make unpack_trees() to fail, I'm not sure how\n> we'd make update_ref() fail\n\nIf all else fails I think it's perfectly OK to just have that failure be\ntriggered by a:\n\n    git_env_bool(\"GIT_TEST_FAIL_UPDATE_REF\", 1) or\n    git_env_bool(\"GIT_TEST_FAIL_UPDATE_REF_FOR_POST_CHECKOUT\", 1)\n\n*tests it out a bit*\n\nI tried this and it \"worked\", as in it'll fail :)\n\ndiff --git a/refs.c b/refs.c\nindex 97a9501c06f..25e2b0ab49a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2016,6 +2016,9 @@ int refs_create_symref(struct ref_store *refs,\n        char *msg;\n        int retval;\n \n+       if (git_env_bool(\"GIT_TEST_FAIL_REFS_CREATE_SYMREF\", 0))\n+               return -1;\n+\n        msg = normalize_reflog_message(logmsg);\n        retval = refs->be->create_symref(refs, ref_target, refs_heads_master,\n                                         msg);\n\nThat seems to me to also be a worthwhile thing to do/test for other\nreasons, i.e. my HEAD won't update, but now my index is in some state\nwhere it points to the new branch, but presumably we'd want to revert\nthat in case of the failure (probably a much more general issue than\nyou're poking at here...).\n\n>>>\n>>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>> ---\n>>>   reset.c | 2 +-\n>>>   1 file changed, 1 insertion(+), 1 deletion(-)\n>>>\n>>> diff --git a/reset.c b/reset.c\n>>> index fc4dae3fd2d..5abb1a5683e 100644\n>>> --- a/reset.c\n>>> +++ b/reset.c\n>>> @@ -125,7 +125,7 @@ reset_head_refs:\n>>>   \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n>>>   \t\t\t\t\t    reflog_head);\n>>>   \t}\n>>> -\tif (run_hook)\n>>> +\tif (!ret && run_hook)\n>>>   \t\trun_hook_le(NULL, \"post-checkout\",\n>>>   \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n>>>   \t\t\t    oid_to_hex(oid), \"1\", NULL);\n\n"},{"id":"443451","messageId":"0e84d00572ed9a99d68ec856b779f550b2a934de.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 01/14] rebase: factor out checkout for up to date branch","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:48Z","receivedAt":"2021-12-08T14:58:07Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis code is heavily indented and it will be convenient later in the\nseries to have it in its own function.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 33 +++++++++++++++++++--------------\n 1 file changed, 19 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 34b4744e5f3..f5c37b7d4a5 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -812,6 +812,23 @@ static int rebase_config(const char *var, const char *value, void *data)\n \treturn git_default_config(var, value, data);\n }\n \n+static int checkout_up_to_date(struct rebase_options *options)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tstrbuf_addf(&buf, \"%s: checkout %s\",\n+\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n+\t\t    options->switch_to);\n+\tif (reset_head(the_repository, &options->orig_head, \"checkout\",\n+\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n+\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n+\t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n /*\n  * Determines whether the commits in from..to are linear, i.e. contain\n  * no merge commits. This function *expects* `from` to be an ancestor of\n@@ -1673,21 +1690,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tif (!(options.flags & REBASE_FORCE)) {\n \t\t\t/* Lazily switch to the target branch if needed... */\n \t\t\tif (options.switch_to) {\n-\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\tstrbuf_addf(&buf, \"%s: checkout %s\",\n-\t\t\t\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n-\t\t\t\t\t    options.switch_to);\n-\t\t\t\tif (reset_head(the_repository,\n-\t\t\t\t\t       &options.orig_head, \"checkout\",\n-\t\t\t\t\t       options.head_name,\n-\t\t\t\t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t\t\t\t       NULL, buf.buf,\n-\t\t\t\t\t       DEFAULT_REFLOG_ACTION) < 0) {\n-\t\t\t\t\tret = error(_(\"could not switch to \"\n-\t\t\t\t\t\t\t\"%s\"),\n-\t\t\t\t\t\t      options.switch_to);\n+\t\t\t\tret = checkout_up_to_date(&options);\n+\t\t\t\tif (ret)\n \t\t\t\t\tgoto cleanup;\n-\t\t\t\t}\n \t\t\t}\n \n \t\t\tif (!(options.flags & REBASE_NO_QUIET))\n-- \ngitgitgadget\n\n"},{"id":"443452","messageId":"a67a5a03b94ccbaec85da2c93ce5bbffcd5cfac9.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 02/14] t5403: refactor rebase post-checkout hook tests","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:49Z","receivedAt":"2021-12-08T14:58:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThese tests only test the default backend and do not check that the\narguments passed to the hook are correct. Fix this by running the\ntests with both backends and adding checks for the hook arguments.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n t/t5403-post-checkout-hook.sh | 42 ++++++++++++++++++++++-------------\n 1 file changed, 26 insertions(+), 16 deletions(-)\n\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex 1ec9e23be75..272b02687ba 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -49,23 +49,33 @@ test_expect_success 'post-checkout receives the right args when not switching br\n \ttest $old = $new && test $flag = 0\n '\n \n-test_expect_success 'post-checkout is triggered on rebase' '\n-\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n-\tgit checkout -b rebase-test main &&\n-\trm -f .git/post-checkout.args &&\n-\tgit rebase rebase-on-me &&\n-\tread old new flag <.git/post-checkout.args &&\n-\ttest $old != $new && test $flag = 1\n-'\n+test_rebase () {\n+\targs=\"$*\" &&\n+\ttest_expect_success \"post-checkout is triggered on rebase $args\" '\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n+\t\tgit checkout -B rebase-test main &&\n+\t\trm -f .git/post-checkout.args &&\n+\t\tgit rebase $args rebase-on-me &&\n+\t\tread old new flag <.git/post-checkout.args &&\n+\t\ttest_cmp_rev main $old &&\n+\t\ttest_cmp_rev rebase-on-me $new &&\n+\t\ttest $flag = 1\n+\t'\n \n-test_expect_success 'post-checkout is triggered on rebase with fast-forward' '\n-\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n-\tgit checkout -b ff-rebase-test rebase-on-me^ &&\n-\trm -f .git/post-checkout.args &&\n-\tgit rebase rebase-on-me &&\n-\tread old new flag <.git/post-checkout.args &&\n-\ttest $old != $new && test $flag = 1\n-'\n+\ttest_expect_success \"post-checkout is triggered on rebase $args with fast-forward\" '\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n+\t\tgit checkout -B ff-rebase-test rebase-on-me^ &&\n+\t\trm -f .git/post-checkout.args &&\n+\t\tgit rebase $args rebase-on-me &&\n+\t\tread old new flag <.git/post-checkout.args &&\n+\t\ttest_cmp_rev rebase-on-me^ $old &&\n+\t\ttest_cmp_rev rebase-on-me $new &&\n+\t\ttest $flag = 1\n+\t'\n+}\n+\n+test_rebase --apply &&\n+test_rebase --merge\n \n test_expect_success 'post-checkout hook is triggered by clone' '\n \tmkdir -p templates/hooks &&\n-- \ngitgitgadget\n\n"},{"id":"443453","messageId":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.git.1633082702.gitgitgadget@gmail.com","subject":"[PATCH v2 00/14] rebase: reset_head() related fixes and improvements","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:47Z","receivedAt":"2021-12-08T14:58:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Thanks for the comments on V1. I have tried to improve the commit messages\nto explain better the motivation and implications of the changes in this\nseries and I have added some more tests. I have rebased onto v2.34.0 to\navoid some merges conflicts.\n\nChanges since V1:\n\n * Patch 1 - unchanged.\n * Patches 2, 3 - these are new and fix an bug I noticed while adding a test\n   to patch 4.\n * Patches 4, 5 - improved commit messages and added tests.\n * Patch 6 - reworded commit message.\n * Patch 7 - split out some changes that used to be in patch 9.\n * Patch 8 - in principle the same but the range-diff is noisy due to the\n   addition of patch 3.\n * Patch 9 - reworded commit message.\n * Patch 10 - unchanged.\n * Patch 11 - reworded commit message and a couple of comments.\n * Patch 12 - minor changes to comments.\n * Patch 13 - cosmetic changes to commit message and tests.\n * Patch 14 - cosmetic changes to commit message.\n\nCover letter for V1: Fix some issues with the implementation and use of\nreset_head(). The last patch was previously posted as [1], I have updated\nthe commit message and rebased it onto the fixes in this series. There are a\ncouple of small conflicts merging this into seen, I think they should be\neasy to resolve (in rebase.c take both sides in reset.c take the changed\nlines from each side). These patches are based on pw/rebase-of-a-tag-fix\n\n[1]\nhttps://lore.kernel.org/git/39ad40c9297531a2d42b7263a1d41b1ecbc23c0a.1631108472.git.gitgitgadget@gmail.com/\n\nPhillip Wood (14):\n  rebase: factor out checkout for up to date branch\n  t5403: refactor rebase post-checkout hook tests\n  rebase: pass correct arguments to post-checkout hook\n  rebase: do not remove untracked files on checkout\n  rebase --apply: don't run post-checkout hook if there is an error\n  reset_head(): remove action parameter\n  create_autostash(): remove unneeded parameter\n  reset_head(): factor out ref updates\n  reset_head(): make default_reflog_action optional\n  rebase: cleanup reset_head() calls\n  reset_head(): take struct rebase_head_opts\n  rebase --apply: fix reflog\n  rebase --apply: set ORIG_HEAD correctly\n  rebase -m: don't fork git checkout\n\n builtin/merge.c               |   6 +-\n builtin/rebase.c              | 101 +++++++++++++----------\n reset.c                       | 149 ++++++++++++++++++++--------------\n reset.h                       |  48 ++++++++++-\n sequencer.c                   |  47 ++++-------\n sequencer.h                   |   3 +-\n t/t3406-rebase-message.sh     |  23 ++++++\n t/t3418-rebase-continue.sh    |  26 ++++++\n t/t5403-post-checkout-hook.sh |  67 +++++++++++----\n 9 files changed, 312 insertions(+), 158 deletions(-)\n\n\nbase-commit: cd3e606211bb1cf8bc57f7d76bab98cc17a150bc\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1049%2Fphillipwood%2Fwip%2Frebase-reset-head-fixes-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1049/phillipwood/wip/rebase-reset-head-fixes-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1049\n\nRange-diff vs v1:\n\n  1:  4d3441c2b25 =  1:  0e84d00572e rebase: factor out checkout for up to date branch\n  -:  ----------- >  2:  a67a5a03b94 t5403: refactor rebase post-checkout hook tests\n  -:  ----------- >  3:  07867760e68 rebase: pass correct arguments to post-checkout hook\n  2:  c8f64113216 !  4:  2b499704c8f reset_head(): fix checkout\n     @@ Metadata\n      Author: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## Commit message ##\n     -    reset_head(): fix checkout\n     +    rebase: do not remove untracked files on checkout\n      \n     -    The reset bit should only be set if flags contains RESET_HEAD_HARD.\n     -    The test for `!deatch_head` dates back to the original implementation\n     -    of reset_head() in ac7f467fef (\"builtin/rebase: support running \"git\n     -    rebase <upstream>\"\", 2018-08-07) and was correct until e65123a71d\n     +    If \"git rebase [--apply|--merge] <upstream> <branch>\" detects that\n     +    <upstream> is an ancestor of <branch> then it will fast-forward and\n     +    checkout <branch>. Normally a checkout or picking a commit during a\n     +    rebase will refuse to overwrite untracked files, however rebase does\n     +    overwrite untracked files when checking <branch>.\n     +\n     +    The fix is to only set reset in `unpack_tree_opts` if flags contains\n     +    `RESET_HEAD_HARD`. t5403 may seem like an odd home for the new test\n     +    but it will be extended in the next commit to check that the\n     +    post-checkout hook is not run when the checkout fails.\n     +\n     +    The test for `!deatch_head` dates back to the\n     +    original implementation of reset_head() in\n     +    ac7f467fef (\"builtin/rebase: support running \"git rebase <upstream>\"\",\n     +    2018-08-07) and was correct until e65123a71d\n          (\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n          2018-09-04) started using reset_head() to checkout <switch-to> when\n          fast-forwarding.\n      \n     +    Note that 480d3d6bf9 (\"Change unpack_trees' 'reset' flag into an\n     +    enum\", 2021-09-27) also fixes this bug as it changes reset_head() to\n     +    never remove untracked files. I think this fix is still worthwhile as\n     +    it makes it clear that the same settings are used for detached and\n     +    non-detached checkouts.\n     +\n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## reset.c ##\n      @@ reset.c: int reset_head(struct repository *r, struct object_id *oid, const char *action,\n     - \tunpack_tree_opts.update = 1;\n       \tunpack_tree_opts.merge = 1;\n     + \tunpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */\n       \tinit_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);\n      -\tif (!detach_head)\n      +\tif (reset_hard)\n     - \t\tunpack_tree_opts.reset = 1;\n     + \t\tunpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;\n       \n       \tif (repo_read_index_unmerged(r) < 0) {\n     +\n     + ## t/t5403-post-checkout-hook.sh ##\n     +@@ t/t5403-post-checkout-hook.sh: test_rebase () {\n     + \t\ttest_cmp_rev three $new &&\n     + \t\ttest $flag = 1\n     + \t'\n     ++\n     ++\ttest_expect_success \"rebase $args checkout does not remove untracked files\" '\n     ++\t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n     ++\t\tgit update-ref refs/heads/rebase-fast-forward three &&\n     ++\t\tgit checkout two &&\n     ++\t\techo untracked >three.t &&\n     ++\t\ttest_when_finished \"rm three.t\" &&\n     ++\t\ttest_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&\n     ++\t\tgrep \"untracked working tree files would be overwritten by checkout\" err\n     ++'\n     + }\n     + \n     + test_rebase --apply &&\n  3:  28872cbca68 !  5:  04e7340a7e7 reset_head(): don't run checkout hook if there is an error\n     @@ Metadata\n      Author: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## Commit message ##\n     -    reset_head(): don't run checkout hook if there is an error\n     +    rebase --apply: don't run post-checkout hook if there is an error\n      \n          The hook should only be run if the worktree and refs were successfully\n     -    updated.\n     +    updated. This primarily affects \"rebase --apply\" but also \"rebase\n     +    --merge\" when it fast-forwards.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n     @@ reset.c: reset_head_refs:\n      -\tif (run_hook)\n      +\tif (!ret && run_hook)\n       \t\trun_hook_le(NULL, \"post-checkout\",\n     - \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n     + \t\t\t    oid_to_hex(head ? head : null_oid()),\n       \t\t\t    oid_to_hex(oid), \"1\", NULL);\n     +\n     + ## t/t5403-post-checkout-hook.sh ##\n     +@@ t/t5403-post-checkout-hook.sh: test_rebase () {\n     + \n     + \ttest_expect_success \"rebase $args checkout does not remove untracked files\" '\n     + \t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n     ++\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n     + \t\tgit update-ref refs/heads/rebase-fast-forward three &&\n     + \t\tgit checkout two &&\n     ++\t\trm -f .git/post-checkout.args &&\n     + \t\techo untracked >three.t &&\n     + \t\ttest_when_finished \"rm three.t\" &&\n     + \t\ttest_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&\n     +-\t\tgrep \"untracked working tree files would be overwritten by checkout\" err\n     ++\t\tgrep \"untracked working tree files would be overwritten by checkout\" err &&\n     ++\t\ttest_path_is_missing .git/post-checkout.args\n     ++\n     + '\n     + }\n     + \n  4:  fbaf64d6b28 !  6:  32ffa98c1bc reset_head(): remove action parameter\n     @@ Metadata\n       ## Commit message ##\n          reset_head(): remove action parameter\n      \n     -    The action parameter is passed as the command name to\n     -    setup_unpack_trees_porcelain(). All but two cases pass either\n     -    \"checkout\" or \"reset\". The case that passes \"reset --hard\" should be\n     -    passing \"reset\" instead. The case that passes \"Fast-forwarded\" is only\n     -    updating HEAD and so does not call unpack_trees(). The value can be\n     -    determined by checking whether flags contains RESET_HEAD_HARD so it\n     -    does not need to be specified by the caller.\n     +    The only use of the action parameter is to setup the error messages\n     +    for unpack_trees(). All but two cases pass either \"checkout\" or\n     +    \"reset\". The case that passes \"reset --hard\" would be better passing\n     +    \"reset\" so that the error messages match the builtin reset command\n     +    like all the other callers that are doing a reset. The case that\n     +    passes \"Fast-forwarded\" is only updating HEAD and so the parameter is\n     +    unused in that case as it does not call unpack_trees(). The value to\n     +    pass to setup_unpack_trees_porcelain() can be determined by checking\n     +    whether flags contains RESET_HEAD_HARD without the caller having to\n     +    specify it.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid, const char\n      +\tconst char *action, *reflog_action;\n       \tstruct strbuf msg = STRBUF_INIT;\n       \tsize_t prefix_len;\n     - \tstruct object_id *orig = NULL, oid_orig,\n     + \tstruct object_id *old_orig = NULL, oid_old_orig;\n      @@ reset.c: int reset_head(struct repository *r, struct object_id *oid, const char *action,\n       \tif (refs_only)\n       \t\tgoto reset_head_refs;\n  -:  ----------- >  7:  341fe183c18 create_autostash(): remove unneeded parameter\n  5:  0744c3d143b !  8:  29e06e7d36d reset_head(): factor out ref updates\n     @@ Metadata\n       ## Commit message ##\n          reset_head(): factor out ref updates\n      \n     -    In the next commit we will stop trying to update HEAD when we are just\n     -    clearing changes from the working tree. Move the code that updates the\n     -    refs to its own function in preparation for that.\n     +    In the next commit we will stop trying to update HEAD when we are\n     +    removing uncommitted changes from the working tree. Move the code that\n     +    updates the refs to its own function in preparation for that.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n     @@ reset.c\n       #include \"unpack-trees.h\"\n       \n      +static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n     -+\t\t       const char *reflog_head, const char *reflog_orig_head,\n     ++\t\t       const struct object_id *head, const char *reflog_head,\n     ++\t\t       const char *reflog_orig_head,\n      +\t\t       const char *default_reflog_action, unsigned flags)\n      +{\n      +\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n      +\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n      +\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n     -+\tstruct object_id *orig = NULL, oid_orig, *old_orig = NULL, oid_old_orig;\n     ++\tstruct object_id *old_orig = NULL, oid_old_orig;\n      +\tstruct strbuf msg = STRBUF_INIT;\n      +\tconst char *reflog_action;\n      +\tsize_t prefix_len;\n     @@ reset.c\n      +\tif (update_orig_head) {\n      +\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n      +\t\t\told_orig = &oid_old_orig;\n     -+\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n     -+\t\t\torig = &oid_orig;\n     ++\t\tif (head) {\n      +\t\t\tif (!reflog_orig_head) {\n      +\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n      +\t\t\t\treflog_orig_head = msg.buf;\n      +\t\t\t}\n     -+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n     ++\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n      +\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n      +\t\t} else if (old_orig)\n      +\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n     @@ reset.c\n      +\t\treflog_head = msg.buf;\n      +\t}\n      +\tif (!switch_to_branch)\n     -+\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n     ++\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n      +\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n      +\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n      +\telse {\n     @@ reset.c\n      +\t}\n      +\tif (!ret && run_hook)\n      +\t\trun_hook_le(NULL, \"post-checkout\",\n     -+\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n     ++\t\t\t    oid_to_hex(head ? head : null_oid()),\n      +\t\t\t    oid_to_hex(oid), \"1\", NULL);\n      +\tstrbuf_release(&msg);\n      +\treturn ret;\n     @@ reset.c\n      -\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n       \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n      -\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n     - \tstruct object_id head_oid;\n     + \tstruct object_id *head = NULL, head_oid;\n       \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n       \tstruct lock_file lock = LOCK_INIT;\n       \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n     @@ reset.c\n      -\tconst char *action, *reflog_action;\n      -\tstruct strbuf msg = STRBUF_INIT;\n      -\tsize_t prefix_len;\n     --\tstruct object_id *orig = NULL, oid_orig,\n     --\t\t*old_orig = NULL, oid_old_orig;\n     +-\tstruct object_id *old_orig = NULL, oid_old_orig;\n      +\tconst char *action;\n       \tint ret = 0, nr = 0;\n       \n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n       \n       \tif (refs_only)\n      -\t\tgoto reset_head_refs;\n     -+\t\treturn update_refs(oid, switch_to_branch, reflog_head,\n     ++\t\treturn update_refs(oid, switch_to_branch, head, reflog_head,\n      +\t\t\t\t   reflog_orig_head, default_reflog_action,\n      +\t\t\t\t   flags);\n       \n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n      -\tif (update_orig_head) {\n      -\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n      -\t\t\told_orig = &oid_old_orig;\n     --\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n     --\t\t\torig = &oid_orig;\n     +-\t\tif (head) {\n      -\t\t\tif (!reflog_orig_head) {\n      -\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n      -\t\t\t\treflog_orig_head = msg.buf;\n      -\t\t\t}\n     --\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n     +-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n      -\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n      -\t\t} else if (old_orig)\n      -\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n      -\t\treflog_head = msg.buf;\n      -\t}\n      -\tif (!switch_to_branch)\n     --\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n     +-\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n      -\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n      -\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n      -\telse {\n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n      -\t}\n      -\tif (!ret && run_hook)\n      -\t\trun_hook_le(NULL, \"post-checkout\",\n     --\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n     +-\t\t\t    oid_to_hex(head ? head : null_oid()),\n      -\t\t\t    oid_to_hex(oid), \"1\", NULL);\n     -+\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n     -+\t\t\t  default_reflog_action, flags);\n     ++\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n     ++\t\t\t  reflog_orig_head, default_reflog_action, flags);\n       \n       leave_reset_head:\n      -\tstrbuf_release(&msg);\n  6:  4503defe591 !  9:  9d00a218daf reset_head(): make default_reflog_action optional\n     @@ Commit message\n      \n          This parameter is only needed when a ref is going to be updated and\n          the caller does not pass an explicit reflog message. Callers that are\n     -    just discarding changes in the working tree like create_autostash() do\n     -    not update any refs so should not have to worry about passing this\n     -    parameter.\n     +    only discarding uncommitted changes in the working tree such as such\n     +    as \"rebase --skip\" or create_autostash() do not update any refs so\n     +    should not have to worry about passing this parameter.\n      \n     -    Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n     +    This change is not intended to have any user visible changes. The\n     +    pointer comparison between `oid` and `&head_oid` checks that the\n     +    caller did not pass an oid to be checked out. As no callers pass\n     +    RESET_HEAD_RUN_POST_CHECKOUT_HOOK without passing an oid there are\n     +    no changes to when the post-checkout hook is run. As update_ref() only\n     +    updates the ref if the oid passed to it differs from the current ref\n     +    there are no changes to when HEAD is updated.\n      \n     - ## builtin/merge.c ##\n     -@@ builtin/merge.c: int cmd_merge(int argc, const char **argv, const char *prefix)\n     - \n     - \t\tif (autostash)\n     - \t\t\tcreate_autostash(the_repository,\n     --\t\t\t\t\t git_path_merge_autostash(the_repository),\n     --\t\t\t\t\t \"merge\");\n     -+\t\t\t\t\t git_path_merge_autostash(the_repository));\n     - \t\tif (checkout_fast_forward(the_repository,\n     - \t\t\t\t\t  &head_commit->object.oid,\n     - \t\t\t\t\t  &commit->object.oid,\n     -@@ builtin/merge.c: int cmd_merge(int argc, const char **argv, const char *prefix)\n     - \n     - \tif (autostash)\n     - \t\tcreate_autostash(the_repository,\n     --\t\t\t\t git_path_merge_autostash(the_repository),\n     --\t\t\t\t \"merge\");\n     -+\t\t\t\t git_path_merge_autostash(the_repository));\n     - \n     - \t/* We are going to make a new commit. */\n     - \tgit_committer_info(IDENT_STRICT);\n     +    Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## builtin/rebase.c ##\n      @@ builtin/rebase.c: static int move_to_original_branch(struct rebase_options *opts)\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n       \t\t\tdie(_(\"could not discard worktree changes\"));\n       \t\tremove_branch_state(the_repository, 0);\n       \t\tif (read_basic_state(&options))\n     -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n     - \t\tdie(_(\"could not read index\"));\n     - \n     - \tif (options.autostash) {\n     --\t\tcreate_autostash(the_repository, state_dir_path(\"autostash\", &options),\n     --\t\t\t\t DEFAULT_REFLOG_ACTION);\n     -+\t\tcreate_autostash(the_repository,\n     -+\t\t\t\t state_dir_path(\"autostash\", &options));\n     - \t}\n     - \n     - \tif (require_clean_work_tree(the_repository, \"rebase\",\n      @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n       \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n       \t\t\toid_to_hex(&options.onto->object.oid));\n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n       \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n       \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n      +\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n     - \tstruct object_id head_oid;\n     + \tstruct object_id *head = NULL, head_oid;\n       \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n       \tstruct lock_file lock = LOCK_INIT;\n      @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n       \t\tgoto leave_reset_head;\n       \t}\n       \n     --\tret = update_refs(oid, switch_to_branch, reflog_head, reflog_orig_head,\n     --\t\t\t  default_reflog_action, flags);\n     +-\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n     +-\t\t\t  reflog_orig_head, default_reflog_action, flags);\n      +\tif (oid != &head_oid || update_orig_head || switch_to_branch)\n     -+\t\tret = update_refs(oid, switch_to_branch, reflog_head,\n     ++\t\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n      +\t\t\t\t  reflog_orig_head, default_reflog_action,\n      +\t\t\t\t  flags);\n       \n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n       \trollback_lock_file(&lock);\n      \n       ## sequencer.c ##\n     -@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n     - \treturn -1;\n     - }\n     - \n     --void create_autostash(struct repository *r, const char *path,\n     --\t\t      const char *default_reflog_action)\n     -+void create_autostash(struct repository *r, const char *path)\n     -+\n     - {\n     - \tstruct strbuf buf = STRBUF_INIT;\n     - \tstruct lock_file lock_file = LOCK_INIT;\n     -@@ sequencer.c: void create_autostash(struct repository *r, const char *path,\n     +@@ sequencer.c: void create_autostash(struct repository *r, const char *path)\n       \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n       \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n       \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n     --\t\t\t       default_reflog_action) < 0)\n     +-\t\t\t       \"\") < 0)\n      +\t\t\t       NULL) < 0)\n       \t\t\tdie(_(\"could not reset --hard\"));\n       \n       \t\tif (discard_index(r->index) < 0 ||\n     -\n     - ## sequencer.h ##\n     -@@ sequencer.h: void commit_post_rewrite(struct repository *r,\n     - \t\t\t const struct commit *current_head,\n     - \t\t\t const struct object_id *new_head);\n     - \n     --void create_autostash(struct repository *r, const char *path,\n     --\t\t      const char *default_reflog_action);\n     -+void create_autostash(struct repository *r, const char *path);\n     - int save_autostash(const char *path);\n     - int apply_autostash(const char *path);\n     - int apply_autostash_oid(const char *stash_oid);\n  7:  5ffc7e64ff1 = 10:  5ea636009e7 rebase: cleanup reset_head() calls\n  8:  267e074e6db ! 11:  24b0566aba5 reset_head(): take struct rebase_head_opts\n     @@ Metadata\n       ## Commit message ##\n          reset_head(): take struct rebase_head_opts\n      \n     -    This function already takes a confusingly large number of parameters\n     -    some of which are optional or not always required. The following\n     -    commits will add a couple more parameters so change it to take a\n     -    struct of options first.\n     +    This function takes a confusingly large number of parameters which\n     +    makes it difficult to remember which order to pass them in. The\n     +    following commits will add a couple more parameters which makes the\n     +    problem worse. To address this change the function to take a struct of\n     +    options. Using a struct means that it is no longer necessary to\n     +    remember which order to pass the parameters in and anyone reading the\n     +    code can easily see which value is passed to each parameter.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## builtin/rebase.c ##\n     -@@ builtin/rebase.c: static void add_var(struct strbuf *buf, const char *name, const char *value)\n     +@@ builtin/rebase.c: static int finish_rebase(struct rebase_options *opts)\n       static int move_to_original_branch(struct rebase_options *opts)\n       {\n       \tstruct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n     @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v\n       \tstrbuf_release(&buf);\n       \n      @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n     - \tchar *squash_onto_name = NULL;\n       \tint reschedule_failed_exec = -1;\n       \tint allow_preemptive_ff = 1;\n     + \tint preserve_merges_selected = 0;\n      +\tstruct reset_head_opts ropts = { 0 };\n       \tstruct option builtin_rebase_options[] = {\n       \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n     @@ reset.c\n       #include \"unpack-trees.h\"\n       \n      -static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n     --\t\t       const char *reflog_head, const char *reflog_orig_head,\n     +-\t\t       const struct object_id *head, const char *reflog_head,\n     +-\t\t       const char *reflog_orig_head,\n      -\t\t       const char *default_reflog_action, unsigned flags)\n      +static int update_refs(const struct reset_head_opts *opts,\n     -+\t\t       const struct object_id *oid)\n     ++\t\t       const struct object_id *oid,\n     ++\t\t       const struct object_id *head)\n       {\n      -\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n      -\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n     @@ reset.c\n      +\tconst char *reflog_head = opts->head_msg;\n      +\tconst char *reflog_orig_head = opts->orig_head_msg;\n      +\tconst char *default_reflog_action = opts->default_reflog_action;\n     - \tstruct object_id *orig = NULL, oid_orig, *old_orig = NULL, oid_old_orig;\n     + \tstruct object_id *old_orig = NULL, oid_old_orig;\n       \tstruct strbuf msg = STRBUF_INIT;\n       \tconst char *reflog_action;\n      @@ reset.c: static int update_refs(const struct object_id *oid, const char *switch_to_branch\n     @@ reset.c: static int update_refs(const struct object_id *oid, const char *switch_\n      +\tunsigned reset_hard = opts->flags & RESET_HEAD_HARD;\n      +\tunsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;\n      +\tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n     - \tstruct object_id head_oid;\n     + \tstruct object_id *head = NULL, head_oid;\n       \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n       \tstruct lock_file lock = LOCK_INIT;\n      @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n       \t\toid = &head_oid;\n       \n       \tif (refs_only)\n     --\t\treturn update_refs(oid, switch_to_branch, reflog_head,\n     +-\t\treturn update_refs(oid, switch_to_branch, head, reflog_head,\n      -\t\t\t\t   reflog_orig_head, default_reflog_action,\n      -\t\t\t\t   flags);\n     -+\t\treturn update_refs(opts, oid);\n     ++\t\treturn update_refs(opts, oid, head);\n       \n       \taction = reset_hard ? \"reset\" : \"checkout\";\n       \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n       \t}\n       \n       \tif (oid != &head_oid || update_orig_head || switch_to_branch)\n     --\t\tret = update_refs(oid, switch_to_branch, reflog_head,\n     +-\t\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n      -\t\t\t\t  reflog_orig_head, default_reflog_action,\n      -\t\t\t\t  flags);\n     -+\t\tret = update_refs(opts, oid);\n     ++\t\tret = update_refs(opts, oid, head);\n       \n       leave_reset_head:\n       \trollback_lock_file(&lock);\n      \n       ## reset.h ##\n      @@\n     + \n     + #define GIT_REFLOG_ACTION_ENVIRONMENT \"GIT_REFLOG_ACTION\"\n     + \n     ++/* Request a detached checkout */\n     + #define RESET_HEAD_DETACH (1<<0)\n     ++/* Request a reset rather than a checkout */\n     + #define RESET_HEAD_HARD (1<<1)\n     ++/* Run the post-checkout hook */\n     + #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)\n     ++/* Only update refs, do not touch the worktree */\n       #define RESET_HEAD_REFS_ONLY (1<<3)\n     ++/* Update ORIG_HEAD as well as HEAD */\n       #define RESET_ORIG_HEAD (1<<4)\n       \n      -int reset_head(struct repository *r, struct object_id *oid,\n     @@ reset.h\n      -\t       const char *reflog_orig_head, const char *reflog_head,\n      -\t       const char *default_reflog_action);\n      +struct reset_head_opts {\n     -+\t/* The oid of the commit to checkout/reset to. Defaults to HEAD */\n     ++\t/*\n     ++\t * The commit to checkout/reset to. Defaults to HEAD.\n     ++\t */\n      +\tconst struct object_id *oid;\n     -+\t /* Optional branch to switch to */\n     ++\t/*\n     ++\t * Optional branch to switch to.\n     ++\t */\n      +\tconst char *branch;\n     -+\t/* Flags defined above */\n     ++\t/*\n     ++\t * Flags defined above.\n     ++\t */\n      +\tunsigned flags;\n     -+\t /*\n     -+\t  * Optional reflog message for HEAD, if this is not set then\n     -+\t  * default_reflog_action must be.\n     -+\t  */\n     ++\t/*\n     ++\t * Optional reflog message for HEAD, if this omitted but oid or branch\n     ++\t * are given then default_reflog_action must be given.\n     ++\t */\n      +\tconst char *head_msg;\n      +\t/*\n     -+\t * Optional reflog message for ORIG_HEAD, if this is not set and flags\n     -+\t * contains RESET_ORIG_HEAD then default_reflog_action must be set.\n     ++\t * Optional reflog message for ORIG_HEAD, if this omitted and flags\n     ++\t * contains RESET_ORIG_HEAD then default_reflog_action must be given.\n      +\t */\n      +\tconst char *orig_head_msg;\n      +\t/*\n  9:  cdb0de221d5 ! 12:  dc5d11291e7 rebase --apply: fix reflog\n     @@ Commit message\n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## builtin/rebase.c ##\n     -@@ builtin/rebase.c: static void add_var(struct strbuf *buf, const char *name, const char *value)\n     +@@ builtin/rebase.c: static int finish_rebase(struct rebase_options *opts)\n       \n       static int move_to_original_branch(struct rebase_options *opts)\n       {\n     @@ reset.c: int reset_head(struct repository *r, const struct reset_head_opts *opts\n      \n       ## reset.h ##\n      @@ reset.h: struct reset_head_opts {\n     - \tconst char *branch;\n     - \t/* Flags defined above */\n     + \t * Flags defined above.\n     + \t */\n       \tunsigned flags;\n     -+\t /* Optional reflog message for branch, defaults to head_msg. */\n     ++\t/*\n     ++\t * Optional reflog message for branch, defaults to head_msg.\n     ++\t */\n      +\tconst char *branch_msg;\n     - \t /*\n     - \t  * Optional reflog message for HEAD, if this is not set then\n     - \t  * default_reflog_action must be.\n     + \t/*\n     + \t * Optional reflog message for HEAD, if this omitted but oid or branch\n     + \t * are given then default_reflog_action must be given.\n      \n       ## t/t3406-rebase-message.sh ##\n      @@ t/t3406-rebase-message.sh: test_expect_success 'GIT_REFLOG_ACTION' '\n 10:  e8884efcc83 ! 13:  45a5b5e9818 rebase --apply: set ORIG_HEAD correctly\n     @@ Commit message\n          At the start of a rebase ORIG_HEAD is updated to tip of the branch\n          being rebased. Unfortunately reset_head() always uses the current\n          value of HEAD for this which is incorrect if the rebase is started\n     -    with 'git rebase <upstream> <branch>' as in that case ORIG_HEAD should\n     +    with \"git rebase <upstream> <branch>\" as in that case ORIG_HEAD should\n          be updated to <branch>. This only affects the \"apply\" backend as the\n          \"merge\" backend does not yet use reset_head() for the initial\n          checkout. Fix this by passing in orig_head when calling reset_head()\n     @@ reset.c: static int update_refs(const struct reset_head_opts *opts,\n       \t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n       \t\t\t\treflog_orig_head = msg.buf;\n       \t\t\t}\n     --\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n     +-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n      +\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\",\n     -+\t\t\t\t   orig_head ? orig_head : orig,\n     ++\t\t\t\t   orig_head ? orig_head : head,\n       \t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n       \t\t} else if (old_orig)\n       \t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n      \n       ## reset.h ##\n     -@@\n     - struct reset_head_opts {\n     - \t/* The oid of the commit to checkout/reset to. Defaults to HEAD */\n     +@@ reset.h: struct reset_head_opts {\n     + \t * The commit to checkout/reset to. Defaults to HEAD.\n     + \t */\n       \tconst struct object_id *oid;\n     -+\t/* Optional commit when setting ORIG_HEAD. Defaults to HEAD */\n     ++\t/*\n     ++\t * Optional value to set ORIG_HEAD. Defaults to HEAD.\n     ++\t */\n      +\tconst struct object_id *orig_head;\n     - \t /* Optional branch to switch to */\n     - \tconst char *branch;\n     - \t/* Flags defined above */\n     + \t/*\n     + \t * Optional branch to switch to.\n     + \t */\n      \n       ## t/t3418-rebase-continue.sh ##\n      @@ t/t3418-rebase-continue.sh: test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n       \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n       '\n       \n     -+test_orig_head_helper() {\n     ++test_orig_head_helper () {\n      +\ttest_when_finished 'git rebase --abort &&\n      +\t\tgit checkout topic &&\n      +\t\tgit reset --hard commit-new-file-F2-on-topic-branch' &&\n     @@ t/t3418-rebase-continue.sh: test_expect_success 'there is no --no-reschedule-fai\n      +\ttest_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch\n      +}\n      +\n     -+test_orig_head() {\n     ++test_orig_head () {\n      +\ttype=$1\n      +\ttest_expect_success \"rebase $type sets ORIG_HEAD correctly\" '\n      +\t\tgit checkout topic &&\n 11:  2c8c60c3f31 ! 14:  3f64b9274b5 rebase -m: don't fork git checkout\n     @@ Commit message\n          rebase -m: don't fork git checkout\n      \n          Now that reset_head() can handle the initial checkout of onto\n     -    correctly use it in the \"merge\" backend instead of forking 'git\n     -    checkout'.  This opens the way for us to stop calling the\n     -    post-checkout hook in the future. Not running 'git checkout' means\n     -    that 'rebase -i/m' no longer recurse submodules when checking out\n     -    'onto' (thanks to Philippe Blain for pointing this out). As the rest\n     +    correctly use it in the \"merge\" backend instead of forking \"git\n     +    checkout\".  This opens the way for us to stop calling the\n     +    post-checkout hook in the future. Not running \"git checkout\" means\n     +    that \"rebase -i/m\" no longer recurse submodules when checking out\n     +    \"onto\" (thanks to Philippe Blain for pointing this out). As the rest\n          of rebase does not know what to do with submodules this is probably a\n          good thing. When using merge-ort rebase ought be able to handle\n          submodules correctly if it parsed the submodule config, such a change\n\n-- \ngitgitgadget\n"},{"id":"443454","messageId":"07867760e68a6b28f718bdcb33094461592affaa.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 03/14] rebase: pass correct arguments to post-checkout hook","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:50Z","receivedAt":"2021-12-08T14:58:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf a rebase started with \"rebase [--apply|--merge] <upstream> <branch>\"\ndetects that <upstream> is an ancestor of <branch> then it fast-forwards\nand checks out <branch>. Unfortunately in that case it passed the null\noid as the first argument to the post-checkout hook rather than the oid\nof HEAD.\n\nA side effect of this change is that the call to update_ref() which\nupdates HEAD now always receives the old value of HEAD. This provides\nprotection against another process updating HEAD during the checkout.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c                       | 18 +++++++++---------\n t/t5403-post-checkout-hook.sh | 13 +++++++++++++\n 2 files changed, 22 insertions(+), 9 deletions(-)\n\ndiff --git a/reset.c b/reset.c\nindex f214df3d96c..315fef91d33 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -18,7 +18,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n \tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n-\tstruct object_id head_oid;\n+\tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n@@ -26,8 +26,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tconst char *reflog_action;\n \tstruct strbuf msg = STRBUF_INIT;\n \tsize_t prefix_len;\n-\tstruct object_id *orig = NULL, oid_orig,\n-\t\t*old_orig = NULL, oid_old_orig;\n+\tstruct object_id *old_orig = NULL, oid_old_orig;\n \tint ret = 0, nr = 0;\n \n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n@@ -38,7 +37,9 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t\tgoto leave_reset_head;\n \t}\n \n-\tif ((!oid || !reset_hard) && get_oid(\"HEAD\", &head_oid)) {\n+\tif (!get_oid(\"HEAD\", &head_oid)) {\n+\t\thead = &head_oid;\n+\t} else if (!oid || !reset_hard) {\n \t\tret = error(_(\"could not determine HEAD revision\"));\n \t\tgoto leave_reset_head;\n \t}\n@@ -98,13 +99,12 @@ reset_head_refs:\n \tif (update_orig_head) {\n \t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n \t\t\told_orig = &oid_old_orig;\n-\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n-\t\t\torig = &oid_orig;\n+\t\tif (head) {\n \t\t\tif (!reflog_orig_head) {\n \t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n \t\t\t\treflog_orig_head = msg.buf;\n \t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n \t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n \t\t} else if (old_orig)\n \t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n@@ -116,7 +116,7 @@ reset_head_refs:\n \t\treflog_head = msg.buf;\n \t}\n \tif (!switch_to_branch)\n-\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n+\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n \t\t\t\t detach_head ? REF_NO_DEREF : 0,\n \t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \telse {\n@@ -128,7 +128,7 @@ reset_head_refs:\n \t}\n \tif (run_hook)\n \t\trun_hook_le(NULL, \"post-checkout\",\n-\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n+\t\t\t    oid_to_hex(head ? head : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\n \n leave_reset_head:\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex 272b02687ba..17ab518f268 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -72,6 +72,19 @@ test_rebase () {\n \t\ttest_cmp_rev rebase-on-me $new &&\n \t\ttest $flag = 1\n \t'\n+\n+\ttest_expect_success \"rebase $args fast-forward branch checkout runs post-checkout hook\" '\n+\t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n+\t\tgit update-ref refs/heads/rebase-fast-forward three &&\n+\t\tgit checkout two  &&\n+\t\trm -f .git/post-checkout.args &&\n+\t\tgit rebase $args HEAD rebase-fast-forward  &&\n+\t\tread old new flag <.git/post-checkout.args &&\n+\t\ttest_cmp_rev two $old &&\n+\t\ttest_cmp_rev three $new &&\n+\t\ttest $flag = 1\n+\t'\n }\n \n test_rebase --apply &&\n-- \ngitgitgadget\n\n"},{"id":"443455","messageId":"2b499704c8f132f173c7328d5f020330481b24dc.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 04/14] rebase: do not remove untracked files on checkout","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:51Z","receivedAt":"2021-12-08T14:58:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf \"git rebase [--apply|--merge] <upstream> <branch>\" detects that\n<upstream> is an ancestor of <branch> then it will fast-forward and\ncheckout <branch>. Normally a checkout or picking a commit during a\nrebase will refuse to overwrite untracked files, however rebase does\noverwrite untracked files when checking <branch>.\n\nThe fix is to only set reset in `unpack_tree_opts` if flags contains\n`RESET_HEAD_HARD`. t5403 may seem like an odd home for the new test\nbut it will be extended in the next commit to check that the\npost-checkout hook is not run when the checkout fails.\n\nThe test for `!deatch_head` dates back to the\noriginal implementation of reset_head() in\nac7f467fef (\"builtin/rebase: support running \"git rebase <upstream>\"\",\n2018-08-07) and was correct until e65123a71d\n(\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n2018-09-04) started using reset_head() to checkout <switch-to> when\nfast-forwarding.\n\nNote that 480d3d6bf9 (\"Change unpack_trees' 'reset' flag into an\nenum\", 2021-09-27) also fixes this bug as it changes reset_head() to\nnever remove untracked files. I think this fix is still worthwhile as\nit makes it clear that the same settings are used for detached and\nnon-detached checkouts.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c                       |  2 +-\n t/t5403-post-checkout-hook.sh | 10 ++++++++++\n 2 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/reset.c b/reset.c\nindex 315fef91d33..3e7b9e2e131 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -59,7 +59,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tunpack_tree_opts.merge = 1;\n \tunpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */\n \tinit_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);\n-\tif (!detach_head)\n+\tif (reset_hard)\n \t\tunpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;\n \n \tif (repo_read_index_unmerged(r) < 0) {\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex 17ab518f268..fd2817b4068 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -85,6 +85,16 @@ test_rebase () {\n \t\ttest_cmp_rev three $new &&\n \t\ttest $flag = 1\n \t'\n+\n+\ttest_expect_success \"rebase $args checkout does not remove untracked files\" '\n+\t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\t\tgit update-ref refs/heads/rebase-fast-forward three &&\n+\t\tgit checkout two &&\n+\t\techo untracked >three.t &&\n+\t\ttest_when_finished \"rm three.t\" &&\n+\t\ttest_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&\n+\t\tgrep \"untracked working tree files would be overwritten by checkout\" err\n+'\n }\n \n test_rebase --apply &&\n-- \ngitgitgadget\n\n"},{"id":"443456","messageId":"04e7340a7e7b9fe0193070e9b9c9fb99b60503fc.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 05/14] rebase --apply: don't run post-checkout hook if there is an error","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:52Z","receivedAt":"2021-12-08T14:58:14Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe hook should only be run if the worktree and refs were successfully\nupdated. This primarily affects \"rebase --apply\" but also \"rebase\n--merge\" when it fast-forwards.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c                       | 2 +-\n t/t5403-post-checkout-hook.sh | 6 +++++-\n 2 files changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/reset.c b/reset.c\nindex 3e7b9e2e131..3537de91f65 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -126,7 +126,7 @@ reset_head_refs:\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n \t}\n-\tif (run_hook)\n+\tif (!ret && run_hook)\n \t\trun_hook_le(NULL, \"post-checkout\",\n \t\t\t    oid_to_hex(head ? head : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex fd2817b4068..d1181816906 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -88,12 +88,16 @@ test_rebase () {\n \n \ttest_expect_success \"rebase $args checkout does not remove untracked files\" '\n \t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n \t\tgit update-ref refs/heads/rebase-fast-forward three &&\n \t\tgit checkout two &&\n+\t\trm -f .git/post-checkout.args &&\n \t\techo untracked >three.t &&\n \t\ttest_when_finished \"rm three.t\" &&\n \t\ttest_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&\n-\t\tgrep \"untracked working tree files would be overwritten by checkout\" err\n+\t\tgrep \"untracked working tree files would be overwritten by checkout\" err &&\n+\t\ttest_path_is_missing .git/post-checkout.args\n+\n '\n }\n \n-- \ngitgitgadget\n\n"},{"id":"443457","messageId":"32ffa98c1bcc0350fbbf6ce8c1bec5827bcb2594.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 06/14] reset_head(): remove action parameter","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:53Z","receivedAt":"2021-12-08T14:58:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe only use of the action parameter is to setup the error messages\nfor unpack_trees(). All but two cases pass either \"checkout\" or\n\"reset\". The case that passes \"reset --hard\" would be better passing\n\"reset\" so that the error messages match the builtin reset command\nlike all the other callers that are doing a reset. The case that\npasses \"Fast-forwarded\" is only updating HEAD and so the parameter is\nunused in that case as it does not call unpack_trees(). The value to\npass to setup_unpack_trees_porcelain() can be determined by checking\nwhether flags contains RESET_HEAD_HARD without the caller having to\nspecify it.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 14 +++++++-------\n reset.c          |  5 +++--\n reset.h          |  2 +-\n sequencer.c      |  3 +--\n 4 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f5c37b7d4a5..2e5a535b54e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -583,7 +583,7 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n-\tret = reset_head(the_repository, NULL, \"\", opts->head_name,\n+\tret = reset_head(the_repository, NULL, opts->head_name,\n \t\t\t RESET_HEAD_REFS_ONLY,\n \t\t\t orig_head_reflog.buf, head_reflog.buf,\n \t\t\t DEFAULT_REFLOG_ACTION);\n@@ -674,7 +674,7 @@ static int run_am(struct rebase_options *opts)\n \t\tfree(rebased_patches);\n \t\tstrvec_clear(&am.args);\n \n-\t\treset_head(the_repository, &opts->orig_head, \"checkout\",\n+\t\treset_head(the_repository, &opts->orig_head,\n \t\t\t   opts->head_name, 0,\n \t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n@@ -820,7 +820,7 @@ static int checkout_up_to_date(struct rebase_options *options)\n \tstrbuf_addf(&buf, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n \t\t    options->switch_to);\n-\tif (reset_head(the_repository, &options->orig_head, \"checkout\",\n+\tif (reset_head(the_repository, &options->orig_head,\n \t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n@@ -1272,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\trerere_clear(the_repository, &merge_rr);\n \t\tstring_list_clear(&merge_rr, 1);\n \n-\t\tif (reset_head(the_repository, NULL, \"reset\", NULL, RESET_HEAD_HARD,\n+\t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n \t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n@@ -1290,7 +1290,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\tif (read_basic_state(&options))\n \t\t\texit(1);\n-\t\tif (reset_head(the_repository, &options.orig_head, \"reset\",\n+\t\tif (reset_head(the_repository, &options.orig_head,\n \t\t\t       options.head_name, RESET_HEAD_HARD,\n \t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n \t\t\tdie(_(\"could not move back to %s\"),\n@@ -1759,7 +1759,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n-\tif (reset_head(the_repository, &options.onto->object.oid, \"checkout\", NULL,\n+\tif (reset_head(the_repository, &options.onto->object.oid, NULL,\n \t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf, DEFAULT_REFLOG_ACTION))\n@@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&msg, \"rebase finished: %s onto %s\",\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n-\t\treset_head(the_repository, NULL, \"Fast-forwarded\", options.head_name,\n+\t\treset_head(the_repository, NULL, options.head_name,\n \t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf,\n \t\t\t   DEFAULT_REFLOG_ACTION);\n \t\tstrbuf_release(&msg);\ndiff --git a/reset.c b/reset.c\nindex 3537de91f65..7841b2b2a02 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,7 +8,7 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n-int reset_head(struct repository *r, struct object_id *oid, const char *action,\n+int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action)\n@@ -23,7 +23,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n \tstruct tree *tree;\n-\tconst char *reflog_action;\n+\tconst char *action, *reflog_action;\n \tstruct strbuf msg = STRBUF_INIT;\n \tsize_t prefix_len;\n \tstruct object_id *old_orig = NULL, oid_old_orig;\n@@ -50,6 +50,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tif (refs_only)\n \t\tgoto reset_head_refs;\n \n+\taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n \tunpack_tree_opts.head_idx = 1;\n \tunpack_tree_opts.src_index = r->index;\ndiff --git a/reset.h b/reset.h\nindex 12f83c78e28..2daec804259 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -12,7 +12,7 @@\n #define RESET_HEAD_REFS_ONLY (1<<3)\n #define RESET_ORIG_HEAD (1<<4)\n \n-int reset_head(struct repository *r, struct object_id *oid, const char *action,\n+int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action);\ndiff --git a/sequencer.c b/sequencer.c\nindex ea96837cde3..3d3c3fbe305 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4131,8 +4131,7 @@ void create_autostash(struct repository *r, const char *path,\n \t\t\t    path);\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n-\t\tif (reset_head(r, NULL, \"reset --hard\",\n-\t\t\t       NULL, RESET_HEAD_HARD, NULL, NULL,\n+\t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n \t\t\t       default_reflog_action) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \n-- \ngitgitgadget\n\n"},{"id":"443458","messageId":"341fe183c18ee28b459ba26f2c8c369d9367c328.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 07/14] create_autostash(): remove unneeded parameter","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:54Z","receivedAt":"2021-12-08T14:58:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe default_reflog parameter of create_autostash() is passed to\nreset_head(). However as creating a stash does not involve updating\nany refs the parameter is not used by reset_head(). Removing the\nparameter from create_autostash() simplifies the callers.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/merge.c  | 6 ++----\n builtin/rebase.c | 8 ++++----\n sequencer.c      | 5 ++---\n sequencer.h      | 3 +--\n 4 files changed, 9 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ea3112e0c0b..cb0e4e22258 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1565,8 +1565,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \t\tif (autostash)\n \t\t\tcreate_autostash(the_repository,\n-\t\t\t\t\t git_path_merge_autostash(the_repository),\n-\t\t\t\t\t \"merge\");\n+\t\t\t\t\t git_path_merge_autostash(the_repository));\n \t\tif (checkout_fast_forward(the_repository,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n@@ -1637,8 +1636,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\n-\t\t\t\t git_path_merge_autostash(the_repository),\n-\t\t\t\t \"merge\");\n+\t\t\t\t git_path_merge_autostash(the_repository));\n \n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 2e5a535b54e..832e6954827 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1658,10 +1658,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (repo_read_index(the_repository) < 0)\n \t\tdie(_(\"could not read index\"));\n \n-\tif (options.autostash) {\n-\t\tcreate_autostash(the_repository, state_dir_path(\"autostash\", &options),\n-\t\t\t\t DEFAULT_REFLOG_ACTION);\n-\t}\n+\tif (options.autostash)\n+\t\tcreate_autostash(the_repository,\n+\t\t\t\t state_dir_path(\"autostash\", &options));\n+\n \n \tif (require_clean_work_tree(the_repository, \"rebase\",\n \t\t\t\t    _(\"Please commit or stash them.\"), 1, 1)) {\ndiff --git a/sequencer.c b/sequencer.c\nindex 3d3c3fbe305..5c65f5f1ac5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4094,8 +4094,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \treturn -1;\n }\n \n-void create_autostash(struct repository *r, const char *path,\n-\t\t      const char *default_reflog_action)\n+void create_autostash(struct repository *r, const char *path)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4132,7 +4131,7 @@ void create_autostash(struct repository *r, const char *path,\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n-\t\t\t       default_reflog_action) < 0)\n+\t\t\t       \"\") < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \n \t\tif (discard_index(r->index) < 0 ||\ndiff --git a/sequencer.h b/sequencer.h\nindex 05a7d2ba6b3..da64473636b 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -197,8 +197,7 @@ void commit_post_rewrite(struct repository *r,\n \t\t\t const struct commit *current_head,\n \t\t\t const struct object_id *new_head);\n \n-void create_autostash(struct repository *r, const char *path,\n-\t\t      const char *default_reflog_action);\n+void create_autostash(struct repository *r, const char *path);\n int save_autostash(const char *path);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n-- \ngitgitgadget\n\n"},{"id":"443459","messageId":"29e06e7d36de454da18de9fde871cbaf491cefc6.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 08/14] reset_head(): factor out ref updates","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:55Z","receivedAt":"2021-12-08T14:58:18Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIn the next commit we will stop trying to update HEAD when we are\nremoving uncommitted changes from the working tree. Move the code that\nupdates the refs to its own function in preparation for that.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c | 110 +++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 62 insertions(+), 48 deletions(-)\n\ndiff --git a/reset.c b/reset.c\nindex 7841b2b2a02..56d6e2a06d9 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,25 +8,75 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n+static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n+\t\t       const struct object_id *head, const char *reflog_head,\n+\t\t       const char *reflog_orig_head,\n+\t\t       const char *default_reflog_action, unsigned flags)\n+{\n+\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n+\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tstruct object_id *old_orig = NULL, oid_old_orig;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *reflog_action;\n+\tsize_t prefix_len;\n+\tint ret;\n+\n+\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n+\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n+\tprefix_len = msg.len;\n+\n+\tif (update_orig_head) {\n+\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n+\t\t\told_orig = &oid_old_orig;\n+\t\tif (head) {\n+\t\t\tif (!reflog_orig_head) {\n+\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n+\t\t\t\treflog_orig_head = msg.buf;\n+\t\t\t}\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n+\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\t} else if (old_orig)\n+\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n+\t}\n+\n+\tif (!reflog_head) {\n+\t\tstrbuf_setlen(&msg, prefix_len);\n+\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n+\t\treflog_head = msg.buf;\n+\t}\n+\tif (!switch_to_branch)\n+\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n+\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\telse {\n+\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n+\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tif (!ret)\n+\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n+\t\t\t\t\t    reflog_head);\n+\t}\n+\tif (!ret && run_hook)\n+\t\trun_hook_le(NULL, \"post-checkout\",\n+\t\t\t    oid_to_hex(head ? head : null_oid()),\n+\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tstrbuf_release(&msg);\n+\treturn ret;\n+}\n+\n int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action)\n {\n-\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n-\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n \tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n \tstruct tree *tree;\n-\tconst char *action, *reflog_action;\n-\tstruct strbuf msg = STRBUF_INIT;\n-\tsize_t prefix_len;\n-\tstruct object_id *old_orig = NULL, oid_old_orig;\n+\tconst char *action;\n \tint ret = 0, nr = 0;\n \n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n@@ -48,7 +98,9 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\toid = &head_oid;\n \n \tif (refs_only)\n-\t\tgoto reset_head_refs;\n+\t\treturn update_refs(oid, switch_to_branch, head, reflog_head,\n+\t\t\t\t   reflog_orig_head, default_reflog_action,\n+\t\t\t\t   flags);\n \n \taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n@@ -92,48 +144,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\tgoto leave_reset_head;\n \t}\n \n-reset_head_refs:\n-\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n-\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n-\tprefix_len = msg.len;\n-\n-\tif (update_orig_head) {\n-\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n-\t\t\told_orig = &oid_old_orig;\n-\t\tif (head) {\n-\t\t\tif (!reflog_orig_head) {\n-\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n-\t\t\t\treflog_orig_head = msg.buf;\n-\t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n-\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n-\t\t} else if (old_orig)\n-\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n-\t}\n-\n-\tif (!reflog_head) {\n-\t\tstrbuf_setlen(&msg, prefix_len);\n-\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n-\t\treflog_head = msg.buf;\n-\t}\n-\tif (!switch_to_branch)\n-\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n-\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n-\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n-\telse {\n-\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n-\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n-\t\tif (!ret)\n-\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n-\t\t\t\t\t    reflog_head);\n-\t}\n-\tif (!ret && run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n-\t\t\t    oid_to_hex(head ? head : null_oid()),\n-\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n+\t\t\t  reflog_orig_head, default_reflog_action, flags);\n \n leave_reset_head:\n-\tstrbuf_release(&msg);\n \trollback_lock_file(&lock);\n \tclear_unpack_trees_porcelain(&unpack_tree_opts);\n \twhile (nr)\n-- \ngitgitgadget\n\n"},{"id":"443460","messageId":"9d00a218daf68a86e5dfba0c0cd66d6aaa6706b3.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 09/14] reset_head(): make default_reflog_action optional","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:56Z","receivedAt":"2021-12-08T14:58:20Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis parameter is only needed when a ref is going to be updated and\nthe caller does not pass an explicit reflog message. Callers that are\nonly discarding uncommitted changes in the working tree such as such\nas \"rebase --skip\" or create_autostash() do not update any refs so\nshould not have to worry about passing this parameter.\n\nThis change is not intended to have any user visible changes. The\npointer comparison between `oid` and `&head_oid` checks that the\ncaller did not pass an oid to be checked out. As no callers pass\nRESET_HEAD_RUN_POST_CHECKOUT_HOOK without passing an oid there are\nno changes to when the post-checkout hook is run. As update_ref() only\nupdates the ref if the oid passed to it differs from the current ref\nthere are no changes to when HEAD is updated.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 10 ++++------\n reset.c          | 16 ++++++++++++----\n sequencer.c      |  2 +-\n 3 files changed, 17 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 832e6954827..3d78b5c8bef 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -585,8 +585,7 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name);\n \tret = reset_head(the_repository, NULL, opts->head_name,\n \t\t\t RESET_HEAD_REFS_ONLY,\n-\t\t\t orig_head_reflog.buf, head_reflog.buf,\n-\t\t\t DEFAULT_REFLOG_ACTION);\n+\t\t\t orig_head_reflog.buf, head_reflog.buf, NULL);\n \n \tstrbuf_release(&orig_head_reflog);\n \tstrbuf_release(&head_reflog);\n@@ -822,7 +821,7 @@ static int checkout_up_to_date(struct rebase_options *options)\n \t\t    options->switch_to);\n \tif (reset_head(the_repository, &options->orig_head,\n \t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n+\t\t       NULL, buf.buf, NULL) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n \tstrbuf_release(&buf);\n \n@@ -1273,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstring_list_clear(&merge_rr, 1);\n \n \t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n+\t\t\t       NULL, NULL, NULL) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n \t\tif (read_basic_state(&options))\n@@ -1778,8 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n \t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf,\n-\t\t\t   DEFAULT_REFLOG_ACTION);\n+\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\ndiff --git a/reset.c b/reset.c\nindex 56d6e2a06d9..4a92e4bc30b 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -22,8 +22,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch\n \tsize_t prefix_len;\n \tint ret;\n \n-\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n-\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n+\tif ((update_orig_head && !reflog_orig_head) || !reflog_head) {\n+\t\tif (!default_reflog_action)\n+\t\t\tBUG(\"default_reflog_action must be given when reflog messages are omitted\");\n+\t\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n+\t\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action :\n+\t\t\t\t\t\t\t  default_reflog_action);\n+\t}\n \tprefix_len = msg.len;\n \n \tif (update_orig_head) {\n@@ -71,6 +76,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n {\n \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n+\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n \tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n@@ -144,8 +150,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\tgoto leave_reset_head;\n \t}\n \n-\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n-\t\t\t  reflog_orig_head, default_reflog_action, flags);\n+\tif (oid != &head_oid || update_orig_head || switch_to_branch)\n+\t\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n+\t\t\t\t  reflog_orig_head, default_reflog_action,\n+\t\t\t\t  flags);\n \n leave_reset_head:\n \trollback_lock_file(&lock);\ndiff --git a/sequencer.c b/sequencer.c\nindex 5c65f5f1ac5..78d203dec47 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4131,7 +4131,7 @@ void create_autostash(struct repository *r, const char *path)\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n-\t\t\t       \"\") < 0)\n+\t\t\t       NULL) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \n \t\tif (discard_index(r->index) < 0 ||\n-- \ngitgitgadget\n\n"},{"id":"443461","messageId":"5ea636009e7858e50357f0f6f8d8fa42e056db60.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 10/14] rebase: cleanup reset_head() calls","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:57Z","receivedAt":"2021-12-08T14:58:21Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf ORIG_HEAD is not set by passing RESET_ORIG_HEAD then there is no\nneed to pass anything for reflog_orig_head. In addition to the callers\nfixed in this commit move_to_original_branch() also passes\nreflog_orig_head without setting ORIG_HEAD. That caller is mistakenly\npassing the message it wants to put in the branch reflog which is not\ncurrently possible so we delay fixing that caller until we can pass\nthe message as the branch reflog.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 3d78b5c8bef..fdd822c470f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -675,7 +675,7 @@ static int run_am(struct rebase_options *opts)\n \n \t\treset_head(the_repository, &opts->orig_head,\n \t\t\t   opts->head_name, 0,\n-\t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n+\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n \t\t\t\"patches to replay\\n\"\n \t\t\t\"these revisions:\\n\"\n@@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n \t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n+\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\n-- \ngitgitgadget\n\n"},{"id":"443462","messageId":"24b0566aba5d8800d209261a14aca4d872af8027.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 11/14] reset_head(): take struct rebase_head_opts","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:58Z","receivedAt":"2021-12-08T14:58:22Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis function takes a confusingly large number of parameters which\nmakes it difficult to remember which order to pass them in. The\nfollowing commits will add a couple more parameters which makes the\nproblem worse. To address this change the function to take a struct of\noptions. Using a struct means that it is no longer necessary to\nremember which order to pass the parameters in and anyone reading the\ncode can easily see which value is passed to each parameter.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 57 ++++++++++++++++++++++++++++++------------------\n reset.c          | 38 +++++++++++++++-----------------\n reset.h          | 40 +++++++++++++++++++++++++++++----\n sequencer.c      |  5 ++---\n 4 files changed, 92 insertions(+), 48 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex fdd822c470f..ecc368dd4f4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -571,6 +571,7 @@ static int finish_rebase(struct rebase_options *opts)\n static int move_to_original_branch(struct rebase_options *opts)\n {\n \tstruct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n+\tstruct reset_head_opts ropts = { 0 };\n \tint ret;\n \n \tif (!opts->head_name)\n@@ -583,9 +584,11 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n-\tret = reset_head(the_repository, NULL, opts->head_name,\n-\t\t\t RESET_HEAD_REFS_ONLY,\n-\t\t\t orig_head_reflog.buf, head_reflog.buf, NULL);\n+\tropts.branch = opts->head_name;\n+\tropts.flags = RESET_HEAD_REFS_ONLY;\n+\tropts.orig_head_msg = orig_head_reflog.buf;\n+\tropts.head_msg = head_reflog.buf;\n+\tret = reset_head(the_repository, &ropts);\n \n \tstrbuf_release(&orig_head_reflog);\n \tstrbuf_release(&head_reflog);\n@@ -669,13 +672,15 @@ static int run_am(struct rebase_options *opts)\n \n \tstatus = run_command(&format_patch);\n \tif (status) {\n+\t\tstruct reset_head_opts ropts = { 0 };\n \t\tunlink(rebased_patches);\n \t\tfree(rebased_patches);\n \t\tstrvec_clear(&am.args);\n \n-\t\treset_head(the_repository, &opts->orig_head,\n-\t\t\t   opts->head_name, 0,\n-\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n+\t\tropts.oid = &opts->orig_head;\n+\t\tropts.branch = opts->head_name;\n+\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\t\treset_head(the_repository, &ropts);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n \t\t\t\"patches to replay\\n\"\n \t\t\t\"these revisions:\\n\"\n@@ -814,14 +819,17 @@ static int rebase_config(const char *var, const char *value, void *data)\n static int checkout_up_to_date(struct rebase_options *options)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reset_head_opts ropts = { 0 };\n \tint ret = 0;\n \n \tstrbuf_addf(&buf, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n \t\t    options->switch_to);\n-\tif (reset_head(the_repository, &options->orig_head,\n-\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, buf.buf, NULL) < 0)\n+\tropts.oid = &options->orig_head;\n+\tropts.branch = options->head_name;\n+\tropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tropts.head_msg = buf.buf;\n+\tif (reset_head(the_repository, &ropts) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n \tstrbuf_release(&buf);\n \n@@ -1033,6 +1041,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tint reschedule_failed_exec = -1;\n \tint allow_preemptive_ff = 1;\n \tint preserve_merges_selected = 0;\n+\tstruct reset_head_opts ropts = { 0 };\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1270,9 +1279,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\trerere_clear(the_repository, &merge_rr);\n \t\tstring_list_clear(&merge_rr, 1);\n-\n-\t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, NULL) < 0)\n+\t\tropts.flags = RESET_HEAD_HARD;\n+\t\tif (reset_head(the_repository, &ropts) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n \t\tif (read_basic_state(&options))\n@@ -1289,9 +1297,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\tif (read_basic_state(&options))\n \t\t\texit(1);\n-\t\tif (reset_head(the_repository, &options.orig_head,\n-\t\t\t       options.head_name, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n+\t\tropts.oid = &options.orig_head;\n+\t\tropts.branch = options.head_name;\n+\t\tropts.flags = RESET_HEAD_HARD;\n+\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\t\tif (reset_head(the_repository, &ropts) < 0)\n \t\t\tdie(_(\"could not move back to %s\"),\n \t\t\t    oid_to_hex(&options.orig_head));\n \t\tremove_branch_state(the_repository, 0);\n@@ -1758,10 +1768,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n-\tif (reset_head(the_repository, &options.onto->object.oid, NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n-\t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, msg.buf, DEFAULT_REFLOG_ACTION))\n+\tropts.oid = &options.onto->object.oid;\n+\tropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n+\t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tropts.head_msg = msg.buf;\n+\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\tif (reset_head(the_repository, &ropts))\n \t\tdie(_(\"Could not detach HEAD\"));\n \tstrbuf_release(&msg);\n \n@@ -1776,8 +1788,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&msg, \"rebase finished: %s onto %s\",\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n-\t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n+\t\tmemset(&ropts, 0, sizeof(ropts));\n+\t\tropts.branch = options.head_name;\n+\t\tropts.flags = RESET_HEAD_REFS_ONLY;\n+\t\tropts.head_msg = msg.buf;\n+\t\treset_head(the_repository, &ropts);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\ndiff --git a/reset.c b/reset.c\nindex 4a92e4bc30b..78145d5c456 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,14 +8,17 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n-static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n-\t\t       const struct object_id *head, const char *reflog_head,\n-\t\t       const char *reflog_orig_head,\n-\t\t       const char *default_reflog_action, unsigned flags)\n+static int update_refs(const struct reset_head_opts *opts,\n+\t\t       const struct object_id *oid,\n+\t\t       const struct object_id *head)\n {\n-\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n-\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tunsigned detach_head = opts->flags & RESET_HEAD_DETACH;\n+\tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n+\tconst char *switch_to_branch = opts->branch;\n+\tconst char *reflog_head = opts->head_msg;\n+\tconst char *reflog_orig_head = opts->orig_head_msg;\n+\tconst char *default_reflog_action = opts->default_reflog_action;\n \tstruct object_id *old_orig = NULL, oid_old_orig;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *reflog_action;\n@@ -69,14 +72,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch\n \treturn ret;\n }\n \n-int reset_head(struct repository *r, struct object_id *oid,\n-\t       const char *switch_to_branch, unsigned flags,\n-\t       const char *reflog_orig_head, const char *reflog_head,\n-\t       const char *default_reflog_action)\n+int reset_head(struct repository *r, const struct reset_head_opts *opts)\n {\n-\tunsigned reset_hard = flags & RESET_HEAD_HARD;\n-\tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tconst struct object_id *oid = opts->oid;\n+\tconst char *switch_to_branch = opts->branch;\n+\tunsigned reset_hard = opts->flags & RESET_HEAD_HARD;\n+\tunsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;\n+\tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n \tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n@@ -104,9 +106,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\toid = &head_oid;\n \n \tif (refs_only)\n-\t\treturn update_refs(oid, switch_to_branch, head, reflog_head,\n-\t\t\t\t   reflog_orig_head, default_reflog_action,\n-\t\t\t\t   flags);\n+\t\treturn update_refs(opts, oid, head);\n \n \taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n@@ -151,9 +151,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t}\n \n \tif (oid != &head_oid || update_orig_head || switch_to_branch)\n-\t\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n-\t\t\t\t  reflog_orig_head, default_reflog_action,\n-\t\t\t\t  flags);\n+\t\tret = update_refs(opts, oid, head);\n \n leave_reset_head:\n \trollback_lock_file(&lock);\ndiff --git a/reset.h b/reset.h\nindex 2daec804259..a205be2fb85 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -6,15 +6,47 @@\n \n #define GIT_REFLOG_ACTION_ENVIRONMENT \"GIT_REFLOG_ACTION\"\n \n+/* Request a detached checkout */\n #define RESET_HEAD_DETACH (1<<0)\n+/* Request a reset rather than a checkout */\n #define RESET_HEAD_HARD (1<<1)\n+/* Run the post-checkout hook */\n #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)\n+/* Only update refs, do not touch the worktree */\n #define RESET_HEAD_REFS_ONLY (1<<3)\n+/* Update ORIG_HEAD as well as HEAD */\n #define RESET_ORIG_HEAD (1<<4)\n \n-int reset_head(struct repository *r, struct object_id *oid,\n-\t       const char *switch_to_branch, unsigned flags,\n-\t       const char *reflog_orig_head, const char *reflog_head,\n-\t       const char *default_reflog_action);\n+struct reset_head_opts {\n+\t/*\n+\t * The commit to checkout/reset to. Defaults to HEAD.\n+\t */\n+\tconst struct object_id *oid;\n+\t/*\n+\t * Optional branch to switch to.\n+\t */\n+\tconst char *branch;\n+\t/*\n+\t * Flags defined above.\n+\t */\n+\tunsigned flags;\n+\t/*\n+\t * Optional reflog message for HEAD, if this omitted but oid or branch\n+\t * are given then default_reflog_action must be given.\n+\t */\n+\tconst char *head_msg;\n+\t/*\n+\t * Optional reflog message for ORIG_HEAD, if this omitted and flags\n+\t * contains RESET_ORIG_HEAD then default_reflog_action must be given.\n+\t */\n+\tconst char *orig_head_msg;\n+\t/*\n+\t * Action to use in default reflog messages, only required if a ref is\n+\t * being updated and the reflog messages above are omitted.\n+\t */\n+\tconst char *default_reflog_action;\n+};\n+\n+int reset_head(struct repository *r, const struct reset_head_opts *opts);\n \n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex 78d203dec47..a62ea9d0e05 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4109,6 +4109,7 @@ void create_autostash(struct repository *r, const char *path)\n \tif (has_unstaged_changes(r, 1) ||\n \t    has_uncommitted_changes(r, 1)) {\n \t\tstruct child_process stash = CHILD_PROCESS_INIT;\n+\t\tstruct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n@@ -4130,10 +4131,8 @@ void create_autostash(struct repository *r, const char *path)\n \t\t\t    path);\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n-\t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n-\t\t\t       NULL) < 0)\n+\t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n-\n \t\tif (discard_index(r->index) < 0 ||\n \t\t\trepo_read_index(r) < 0)\n \t\t\tdie(_(\"could not read index\"));\n-- \ngitgitgadget\n\n"},{"id":"443463","messageId":"dc5d11291e78bddde448f6e273bc58e9f2f83c95.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 12/14] rebase --apply: fix reflog","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:57:59Z","receivedAt":"2021-12-08T14:58:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nmove_to_original_branch() passes the message intended for the branch\nreflog as `orig_head_msg`. Fix this by adding a `branch_msg` member to\nstruct reset_head_opts and add a regression test.  Note that these\nreflog messages do not respect GIT_REFLOG_ACTION. They are not alone\nin that and will be fixed in a future series.\n\nThe \"merge\" backend already has tests that check both the branch and\nHEAD reflogs.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c          |  8 ++++----\n reset.c                   | 12 ++++++++++--\n reset.h                   |  4 ++++\n t/t3406-rebase-message.sh | 23 +++++++++++++++++++++++\n 4 files changed, 41 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ecc368dd4f4..b55a9cff05d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -570,7 +570,7 @@ static int finish_rebase(struct rebase_options *opts)\n \n static int move_to_original_branch(struct rebase_options *opts)\n {\n-\tstruct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n+\tstruct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n \tstruct reset_head_opts ropts = { 0 };\n \tint ret;\n \n@@ -580,17 +580,17 @@ static int move_to_original_branch(struct rebase_options *opts)\n \tif (!opts->onto)\n \t\tBUG(\"move_to_original_branch without onto\");\n \n-\tstrbuf_addf(&orig_head_reflog, \"rebase finished: %s onto %s\",\n+\tstrbuf_addf(&branch_reflog, \"rebase finished: %s onto %s\",\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n \tropts.branch = opts->head_name;\n \tropts.flags = RESET_HEAD_REFS_ONLY;\n-\tropts.orig_head_msg = orig_head_reflog.buf;\n+\tropts.branch_msg = branch_reflog.buf;\n \tropts.head_msg = head_reflog.buf;\n \tret = reset_head(the_repository, &ropts);\n \n-\tstrbuf_release(&orig_head_reflog);\n+\tstrbuf_release(&branch_reflog);\n \tstrbuf_release(&head_reflog);\n \treturn ret;\n }\ndiff --git a/reset.c b/reset.c\nindex 78145d5c456..e02915c0f65 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -16,6 +16,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n \tconst char *switch_to_branch = opts->branch;\n+\tconst char *reflog_branch = opts->branch_msg;\n \tconst char *reflog_head = opts->head_msg;\n \tconst char *reflog_orig_head = opts->orig_head_msg;\n \tconst char *default_reflog_action = opts->default_reflog_action;\n@@ -58,8 +59,9 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\t\t\t detach_head ? REF_NO_DEREF : 0,\n \t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \telse {\n-\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n-\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tret = update_ref(reflog_branch ? reflog_branch : reflog_head,\n+\t\t\t\t switch_to_branch, oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \t\tif (!ret)\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n@@ -90,6 +92,12 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)\n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n \t\tBUG(\"Not a fully qualified branch: '%s'\", switch_to_branch);\n \n+\tif (opts->orig_head_msg && !update_orig_head)\n+\t\tBUG(\"ORIG_HEAD reflog message given without updating ORIG_HEAD\");\n+\n+\tif (opts->branch_msg && !opts->branch)\n+\t\tBUG(\"branch reflog message given without a branch\");\n+\n \tif (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {\n \t\tret = -1;\n \t\tgoto leave_reset_head;\ndiff --git a/reset.h b/reset.h\nindex a205be2fb85..7ef7e43ea8c 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -30,6 +30,10 @@ struct reset_head_opts {\n \t * Flags defined above.\n \t */\n \tunsigned flags;\n+\t/*\n+\t * Optional reflog message for branch, defaults to head_msg.\n+\t */\n+\tconst char *branch_msg;\n \t/*\n \t * Optional reflog message for HEAD, if this omitted but oid or branch\n \t * are given then default_reflog_action must be given.\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 77a313f62eb..d17b450e811 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase --apply reflog' '\n+\tgit checkout -b reflog-apply start &&\n+\told_head_reflog=\"$(git log -g --format=%gs -1 HEAD)\" &&\n+\n+\tgit rebase --apply Y &&\n+\n+\tgit log -g --format=%gs -4 HEAD >actual &&\n+\tcat >expect <<-EOF &&\n+\trebase finished: returning to refs/heads/reflog-apply\n+\trebase: Z\n+\trebase: checkout Y\n+\t$old_head_reflog\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit log -g --format=%gs -2 reflog-apply >actual &&\n+\tcat >expect <<-EOF &&\n+\trebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)\n+\tbranch: Created from start\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'rebase -i onto unrelated history' '\n \tgit init unrelated &&\n \ttest_commit -C unrelated 1 &&\n-- \ngitgitgadget\n\n"},{"id":"443464","messageId":"45a5b5e981804c540991a63675909c4155b86b5e.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 13/14] rebase --apply: set ORIG_HEAD correctly","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:58:00Z","receivedAt":"2021-12-08T14:58:32Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAt the start of a rebase ORIG_HEAD is updated to tip of the branch\nbeing rebased. Unfortunately reset_head() always uses the current\nvalue of HEAD for this which is incorrect if the rebase is started\nwith \"git rebase <upstream> <branch>\" as in that case ORIG_HEAD should\nbe updated to <branch>. This only affects the \"apply\" backend as the\n\"merge\" backend does not yet use reset_head() for the initial\ncheckout. Fix this by passing in orig_head when calling reset_head()\nand add some regression tests.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c           |  1 +\n reset.c                    |  4 +++-\n reset.h                    |  4 ++++\n t/t3418-rebase-continue.sh | 26 ++++++++++++++++++++++++++\n 4 files changed, 34 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b55a9cff05d..e942c300f8c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1769,6 +1769,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tropts.oid = &options.onto->object.oid;\n+\tropts.orig_head = &options.orig_head,\n \tropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tropts.head_msg = msg.buf;\ndiff --git a/reset.c b/reset.c\nindex e02915c0f65..448cb3fd785 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -15,6 +15,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \tunsigned detach_head = opts->flags & RESET_HEAD_DETACH;\n \tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n+\tconst struct object_id *orig_head = opts->orig_head;\n \tconst char *switch_to_branch = opts->branch;\n \tconst char *reflog_branch = opts->branch_msg;\n \tconst char *reflog_head = opts->head_msg;\n@@ -43,7 +44,8 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n \t\t\t\treflog_orig_head = msg.buf;\n \t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\",\n+\t\t\t\t   orig_head ? orig_head : head,\n \t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n \t\t} else if (old_orig)\n \t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\ndiff --git a/reset.h b/reset.h\nindex 7ef7e43ea8c..a28f81829d8 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -22,6 +22,10 @@ struct reset_head_opts {\n \t * The commit to checkout/reset to. Defaults to HEAD.\n \t */\n \tconst struct object_id *oid;\n+\t/*\n+\t * Optional value to set ORIG_HEAD. Defaults to HEAD.\n+\t */\n+\tconst struct object_id *orig_head;\n \t/*\n \t * Optional branch to switch to.\n \t */\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 22eca73aa3e..130e2f9b553 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -308,4 +308,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n '\n \n+test_orig_head_helper () {\n+\ttest_when_finished 'git rebase --abort &&\n+\t\tgit checkout topic &&\n+\t\tgit reset --hard commit-new-file-F2-on-topic-branch' &&\n+\tgit update-ref -d ORIG_HEAD &&\n+\ttest_must_fail git rebase \"$@\" &&\n+\ttest_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch\n+}\n+\n+test_orig_head () {\n+\ttype=$1\n+\ttest_expect_success \"rebase $type sets ORIG_HEAD correctly\" '\n+\t\tgit checkout topic &&\n+\t\tgit reset --hard commit-new-file-F2-on-topic-branch &&\n+\t\ttest_orig_head_helper $type main\n+\t'\n+\n+\ttest_expect_success \"rebase $type <upstream> <branch> sets ORIG_HEAD correctly\" '\n+\t\tgit checkout main &&\n+\t\ttest_orig_head_helper $type main topic\n+\t'\n+}\n+\n+test_orig_head --apply\n+test_orig_head --merge\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"443465","messageId":"3f64b9274b5580d14ea9cac916be5077b5eebdd2.1638975482.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v2 14/14] rebase -m: don't fork git checkout","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T14:58:01Z","receivedAt":"2021-12-08T14:59:24Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nNow that reset_head() can handle the initial checkout of onto\ncorrectly use it in the \"merge\" backend instead of forking \"git\ncheckout\".  This opens the way for us to stop calling the\npost-checkout hook in the future. Not running \"git checkout\" means\nthat \"rebase -i/m\" no longer recurse submodules when checking out\n\"onto\" (thanks to Philippe Blain for pointing this out). As the rest\nof rebase does not know what to do with submodules this is probably a\ngood thing. When using merge-ort rebase ought be able to handle\nsubmodules correctly if it parsed the submodule config, such a change\nis left for a future patch series.\n\nThe \"apply\" based rebase has avoided forking git checkout\nsince ac7f467fef (\"builtin/rebase: support running \"git rebase\n<upstream>\"\", 2018-08-07). The code that handles the checkout was\nmoved into libgit by b309a97108 (\"reset: extract reset_head() from\nrebase\", 2020-04-07).\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 38 +++++++++++---------------------------\n 1 file changed, 11 insertions(+), 27 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex a62ea9d0e05..19082aa6c9b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4217,42 +4217,26 @@ int apply_autostash_oid(const char *stash_oid)\n \treturn apply_save_autostash_oid(stash_oid, 1);\n }\n \n-static int run_git_checkout(struct repository *r, struct replay_opts *opts,\n-\t\t\t    const char *commit, const char *action)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tint ret;\n-\n-\tcmd.git_cmd = 1;\n-\n-\tstrvec_push(&cmd.args, \"checkout\");\n-\tstrvec_push(&cmd.args, commit);\n-\tstrvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION \"=%s\", action);\n-\n-\tif (opts->verbose)\n-\t\tret = run_command(&cmd);\n-\telse\n-\t\tret = run_command_silent_on_success(&cmd);\n-\n-\tif (!ret)\n-\t\tdiscard_index(r->index);\n-\n-\treturn ret;\n-}\n-\n static int checkout_onto(struct repository *r, struct replay_opts *opts,\n \t\t\t const char *onto_name, const struct object_id *onto,\n \t\t\t const struct object_id *orig_head)\n {\n-\tconst char *action = reflog_message(opts, \"start\", \"checkout %s\", onto_name);\n-\n-\tif (run_git_checkout(r, opts, oid_to_hex(onto), action)) {\n+\tstruct reset_head_opts ropts = {\n+\t\t.oid = onto,\n+\t\t.orig_head = orig_head,\n+\t\t.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n+\t\t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n+\t\t.head_msg = reflog_message(opts, \"start\", \"checkout %s\",\n+\t\t\t\t\t   onto_name),\n+\t\t.default_reflog_action = \"rebase\"\n+\t};\n+\tif (reset_head(r, &ropts)) {\n \t\tapply_autostash(rebase_path_autostash());\n \t\tsequencer_remove_state(opts);\n \t\treturn error(_(\"could not detach HEAD\"));\n \t}\n \n-\treturn update_ref(NULL, \"ORIG_HEAD\", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\treturn 0;\n }\n \n static int stopped_at_head(struct repository *r)\n-- \ngitgitgadget\n"},{"id":"443660","messageId":"xmqqk0gdskkh.fsf@gitster.g","threadId":"56620","inReplyTo":"a67a5a03b94ccbaec85da2c93ce5bbffcd5cfac9.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 02/14] t5403: refactor rebase post-checkout hook tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T18:24:30Z","receivedAt":"2021-12-09T18:24:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> These tests only test the default backend and do not check that the\n> arguments passed to the hook are correct. Fix this by running the\n> tests with both backends and adding checks for the hook arguments.\n\nNice.\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  t/t5403-post-checkout-hook.sh | 42 ++++++++++++++++++++++-------------\n>  1 file changed, 26 insertions(+), 16 deletions(-)\n>\n> diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\n> index 1ec9e23be75..272b02687ba 100755\n> --- a/t/t5403-post-checkout-hook.sh\n> +++ b/t/t5403-post-checkout-hook.sh\n> @@ -49,23 +49,33 @@ test_expect_success 'post-checkout receives the right args when not switching br\n>  \ttest $old = $new && test $flag = 0\n>  '\n>  \n> -test_expect_success 'post-checkout is triggered on rebase' '\n> -\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n> -\tgit checkout -b rebase-test main &&\n> -\trm -f .git/post-checkout.args &&\n> -\tgit rebase rebase-on-me &&\n> -\tread old new flag <.git/post-checkout.args &&\n> -\ttest $old != $new && test $flag = 1\n> -'\n> +test_rebase () {\n> +\targs=\"$*\" &&\n> +\ttest_expect_success \"post-checkout is triggered on rebase $args\" '\n> +\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n> +\t\tgit checkout -B rebase-test main &&\n> +\t\trm -f .git/post-checkout.args &&\n> +\t\tgit rebase $args rebase-on-me &&\n> +\t\tread old new flag <.git/post-checkout.args &&\n> +\t\ttest_cmp_rev main $old &&\n> +\t\ttest_cmp_rev rebase-on-me $new &&\n> +\t\ttest $flag = 1\n> +\t'\n\nOK, so we now make sure $old and $new are what we expect, in\naddition to what $flag we got.  And the change from -b to -B is\nunderstandable as this needs to be prepared to run more than once.\n\n> -test_expect_success 'post-checkout is triggered on rebase with fast-forward' '\n> -\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n> -\tgit checkout -b ff-rebase-test rebase-on-me^ &&\n> -\trm -f .git/post-checkout.args &&\n> -\tgit rebase rebase-on-me &&\n> -\tread old new flag <.git/post-checkout.args &&\n> -\ttest $old != $new && test $flag = 1\n> -'\n> +\ttest_expect_success \"post-checkout is triggered on rebase $args with fast-forward\" '\n> +\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n> +\t\tgit checkout -B ff-rebase-test rebase-on-me^ &&\n> +\t\trm -f .git/post-checkout.args &&\n> +\t\tgit rebase $args rebase-on-me &&\n> +\t\tread old new flag <.git/post-checkout.args &&\n> +\t\ttest_cmp_rev rebase-on-me^ $old &&\n> +\t\ttest_cmp_rev rebase-on-me $new &&\n> +\t\ttest $flag = 1\n> +\t'\n> +}\n\nLikewise.\n\n> +test_rebase --apply &&\n> +test_rebase --merge\n\nI am not sure if \"&&\" is appropriate here.  It is like saying\n\n\ttest_expect_success \"test on apply\" '\n\t\tbody of apply test\n\t' &&\n\n\ttest_expect_success \"test on merge\" '\n\t\tbody of merge test\n\t'\n\nno?  In other words, even if somebody broke the apply backend, we\nstill are interested in seeing the merge backend succeed.\n\n>  test_expect_success 'post-checkout hook is triggered by clone' '\n>  \tmkdir -p templates/hooks &&\n"},{"id":"443667","messageId":"xmqqa6h9sj8e.fsf@gitster.g","threadId":"56620","inReplyTo":"07867760e68a6b28f718bdcb33094461592affaa.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/14] rebase: pass correct arguments to post-checkout hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T18:53:21Z","receivedAt":"2021-12-09T18:53:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> If a rebase started with \"rebase [--apply|--merge] <upstream> <branch>\"\n> detects that <upstream> is an ancestor of <branch> then it fast-forwards\n> and checks out <branch>. Unfortunately in that case it passed the null\n> oid as the first argument to the post-checkout hook rather than the oid\n> of HEAD.\n>\n> A side effect of this change is that the call to update_ref() which\n> updates HEAD now always receives the old value of HEAD. This provides\n> protection against another process updating HEAD during the checkout.\n\nNicely described.\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  reset.c                       | 18 +++++++++---------\n>  t/t5403-post-checkout-hook.sh | 13 +++++++++++++\n>  2 files changed, 22 insertions(+), 9 deletions(-)\n>\n> diff --git a/reset.c b/reset.c\n> index f214df3d96c..315fef91d33 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -18,7 +18,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n>  \tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n>  \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n>  \tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n> -\tstruct object_id head_oid;\n> +\tstruct object_id *head = NULL, head_oid;\n>  \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n>  \tstruct lock_file lock = LOCK_INIT;\n>  \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n> @@ -26,8 +26,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n>  \tconst char *reflog_action;\n>  \tstruct strbuf msg = STRBUF_INIT;\n>  \tsize_t prefix_len;\n> -\tstruct object_id *orig = NULL, oid_orig,\n> -\t\t*old_orig = NULL, oid_old_orig;\n> +\tstruct object_id *old_orig = NULL, oid_old_orig;\n>  \tint ret = 0, nr = 0;\n>  \n>  \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n> @@ -38,7 +37,9 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n>  \t\tgoto leave_reset_head;\n>  \t}\n>  \n> -\tif ((!oid || !reset_hard) && get_oid(\"HEAD\", &head_oid)) {\n> +\tif (!get_oid(\"HEAD\", &head_oid)) {\n> +\t\thead = &head_oid;\n> +\t} else if (!oid || !reset_hard) {\n>  \t\tret = error(_(\"could not determine HEAD revision\"));\n>  \t\tgoto leave_reset_head;\n>  \t}\n\nHere, the original grabbed head_oid only when oid is not known or we\nare not doing a hard reset.  Now we grab head_oid when available,\nregardless of the other conditions.  But the logic to error out is\nthe same.  If oid is not given or not doing hard reset, it is an\nerror if we cannot figure out what object HEAD points at (is that\n\"unborn branch\"?).\n\nOK.\n\n\n> @@ -98,13 +99,12 @@ reset_head_refs:\n>  \tif (update_orig_head) {\n>  \t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n>  \t\t\told_orig = &oid_old_orig;\n> -\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n> -\t\t\torig = &oid_orig;\n> +\t\tif (head) {\n>  \t\t\tif (!reflog_orig_head) {\n>  \t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n>  \t\t\t\treflog_orig_head = msg.buf;\n>  \t\t\t}\n> -\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n> +\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n>  \t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n>  \t\t} else if (old_orig)\n>  \t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n\nThe variable 'orig' was initialized to NULL and then got assigned\nthe value of HEAD here, which is used in update_ref().  The new code\ndoes the same thing with the 'head' variable that we have already\nprepared earlier.  So there is no change here locally, but this\naffects correctness later ...\n\n> @@ -116,7 +116,7 @@ reset_head_refs:\n>  \t\treflog_head = msg.buf;\n>  \t}\n>  \tif (!switch_to_branch)\n> -\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n> +\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n>  \t\t\t\t detach_head ? REF_NO_DEREF : 0,\n>  \t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n\n... here.  In the original code, there is nothing that populates the\n'orig' variable, if \"update_orig_head\" is not true, so we used to\npass NULL here.  Now as long as we know what HEAD is, we pass use it.\n\n\n\n> @@ -128,7 +128,7 @@ reset_head_refs:\n>  \t}\n>  \tif (run_hook)\n>  \t\trun_hook_le(NULL, \"post-checkout\",\n> -\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n> +\t\t\t    oid_to_hex(head ? head : null_oid()),\n\nAnd the same fix is in effect here.\n\nNice.\n\n\n> diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\n> index 272b02687ba..17ab518f268 100755\n> --- a/t/t5403-post-checkout-hook.sh\n> +++ b/t/t5403-post-checkout-hook.sh\n> @@ -72,6 +72,19 @@ test_rebase () {\n>  \t\ttest_cmp_rev rebase-on-me $new &&\n>  \t\ttest $flag = 1\n>  \t'\n> +\n> +\ttest_expect_success \"rebase $args fast-forward branch checkout runs post-checkout hook\" '\n> +\t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n> +\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n> +\t\tgit update-ref refs/heads/rebase-fast-forward three &&\n> +\t\tgit checkout two  &&\n> +\t\trm -f .git/post-checkout.args &&\n> +\t\tgit rebase $args HEAD rebase-fast-forward  &&\n> +\t\tread old new flag <.git/post-checkout.args &&\n> +\t\ttest_cmp_rev two $old &&\n> +\t\ttest_cmp_rev three $new &&\n> +\t\ttest $flag = 1\n> +\t'\n>  }\n>  \n>  test_rebase --apply &&\n"},{"id":"443668","messageId":"xmqqwnkdr3xb.fsf@gitster.g","threadId":"56620","inReplyTo":"2b499704c8f132f173c7328d5f020330481b24dc.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/14] rebase: do not remove untracked files on checkout","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T19:09:20Z","receivedAt":"2021-12-09T19:09:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> If \"git rebase [--apply|--merge] <upstream> <branch>\" detects that\n> <upstream> is an ancestor of <branch> then it will fast-forward and\n> checkout <branch>. Normally a checkout or picking a commit during a\n> rebase will refuse to overwrite untracked files, however rebase does\n> overwrite untracked files when checking <branch>.\n\n\"when checking <branch> out\", you mean?\n\n> The fix is to only set reset in `unpack_tree_opts` if flags contains\n> `RESET_HEAD_HARD`. t5403 may seem like an odd home for the new test\n> but it will be extended in the next commit to check that the\n> post-checkout hook is not run when the checkout fails.\n>\n> The test for `!deatch_head` dates back to the\n\n\"detach_head\"?\n\n> original implementation of reset_head() in\n> ac7f467fef (\"builtin/rebase: support running \"git rebase <upstream>\"\",\n> 2018-08-07) and was correct until e65123a71d\n> (\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n> 2018-09-04) started using reset_head() to checkout <switch-to> when\n> fast-forwarding.\n\nIn other words, before e64123a71d, there was only one caller of\nreset_head(), and the caller always gave detach_head==1, so we never\nhad .reset member set to true.  So what this patch fixes was a piece\nof dead code that did a wrong thing but hurted nobody only because\nnobody called it back then.  e64123a71d started to exercise the code\nand immediately got affected by it.\n\nOK, and this fixes it.  Good.\n\n> Note that 480d3d6bf9 (\"Change unpack_trees' 'reset' flag into an\n> enum\", 2021-09-27) also fixes this bug as it changes reset_head() to\n> never remove untracked files. I think this fix is still worthwhile as\n> it makes it clear that the same settings are used for detached and\n> non-detached checkouts.\n\nIf detached-ness has nothing to do with PROTECT_UNTRACKED but the\ntrue logic is tied to \"are we doing a hard reset?\" (e.g. \"we are\ndoing a hard reset so we need not to care about untracked files, we\nare allowed to nuke them\"), this change makes quite a lot of sense.\n\n> diff --git a/reset.c b/reset.c\n> index 315fef91d33..3e7b9e2e131 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -59,7 +59,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n>  \tunpack_tree_opts.merge = 1;\n>  \tunpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */\n>  \tinit_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);\n> -\tif (!detach_head)\n> +\tif (reset_hard)\n>  \t\tunpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;\n\nThe polarity of the check goes against my intuition, though.  If we\nare resetting hard, shouldn't we be allowed to nuke untracked paths?\nPerhaps it is just the naming of variables that confuses me.  I dunno.\n\n>  \tif (repo_read_index_unmerged(r) < 0) {\n> diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\n> index 17ab518f268..fd2817b4068 100755\n> --- a/t/t5403-post-checkout-hook.sh\n> +++ b/t/t5403-post-checkout-hook.sh\n> @@ -85,6 +85,16 @@ test_rebase () {\n>  \t\ttest_cmp_rev three $new &&\n>  \t\ttest $flag = 1\n>  \t'\n> +\n> +\ttest_expect_success \"rebase $args checkout does not remove untracked files\" '\n> +\t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n> +\t\tgit update-ref refs/heads/rebase-fast-forward three &&\n> +\t\tgit checkout two &&\n> +\t\techo untracked >three.t &&\n> +\t\ttest_when_finished \"rm three.t\" &&\n> +\t\ttest_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&\n> +\t\tgrep \"untracked working tree files would be overwritten by checkout\" err\n> +'\n>  }\n>  \n>  test_rebase --apply &&\n"},{"id":"443670","messageId":"xmqqpmq5r3j9.fsf@gitster.g","threadId":"56620","inReplyTo":"341fe183c18ee28b459ba26f2c8c369d9367c328.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/14] create_autostash(): remove unneeded parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T19:17:46Z","receivedAt":"2021-12-09T19:17:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> The default_reflog parameter of create_autostash() is passed to\n> reset_head(). However as creating a stash does not involve updating\n> any refs the parameter is not used by reset_head(). Removing the\n> parameter from create_autostash() simplifies the callers.\n\nIt does make the callers of create_autostash() easier to reason\nabout, but ...\n\n> @@ -4132,7 +4131,7 @@ void create_autostash(struct repository *r, const char *path,\n>  \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n>  \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n>  \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n> -\t\t\t       default_reflog_action) < 0)\n> +\t\t\t       \"\") < 0)\n\n... makes the reader wonder what the empty string is doing here.\nThe fact that reset_head() does not care about the last parameter\nwhen not given oid or switch_to_branch parameters feels like too\nmuch implementation detail to expect the callers to know about.\n\nUnless it is documented in reset.[ch] before the beginning of the\n\"int reset_head(...)\" definition/declaration, that is.\n\n>  \t\t\tdie(_(\"could not reset --hard\"));\n>  \n>  \t\tif (discard_index(r->index) < 0 ||\n> diff --git a/sequencer.h b/sequencer.h\n> index 05a7d2ba6b3..da64473636b 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -197,8 +197,7 @@ void commit_post_rewrite(struct repository *r,\n>  \t\t\t const struct commit *current_head,\n>  \t\t\t const struct object_id *new_head);\n>  \n> -void create_autostash(struct repository *r, const char *path,\n> -\t\t      const char *default_reflog_action);\n> +void create_autostash(struct repository *r, const char *path);\n>  int save_autostash(const char *path);\n>  int apply_autostash(const char *path);\n>  int apply_autostash_oid(const char *stash_oid);\n"},{"id":"443682","messageId":"xmqqk0gdr39e.fsf@gitster.g","threadId":"56620","inReplyTo":"9d00a218daf68a86e5dfba0c0cd66d6aaa6706b3.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/14] reset_head(): make default_reflog_action optional","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T19:23:41Z","receivedAt":"2021-12-09T19:23:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This parameter is only needed when a ref is going to be updated and\n> the caller does not pass an explicit reflog message. Callers that are\n> only discarding uncommitted changes in the working tree such as such\n> as \"rebase --skip\" or create_autostash() do not update any refs so\n> should not have to worry about passing this parameter.\n\nThis answers the question I had on 07/14.  I think the part with\ncreate_autostash() in this step should be dropped, and 07/14 should\nbe done after this step.\n\nUnlike \"\", NULL makes it very clear that it will be a bug if the\nimplementation used it as the message.\n\n> This change is not intended to have any user visible changes. The\n> pointer comparison between `oid` and `&head_oid` checks that the\n> caller did not pass an oid to be checked out. As no callers pass\n> RESET_HEAD_RUN_POST_CHECKOUT_HOOK without passing an oid there are\n> no changes to when the post-checkout hook is run. As update_ref() only\n> updates the ref if the oid passed to it differs from the current ref\n> there are no changes to when HEAD is updated.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  builtin/rebase.c | 10 ++++------\n>  reset.c          | 16 ++++++++++++----\n>  sequencer.c      |  2 +-\n>  3 files changed, 17 insertions(+), 11 deletions(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 832e6954827..3d78b5c8bef 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -585,8 +585,7 @@ static int move_to_original_branch(struct rebase_options *opts)\n>  \t\t    opts->head_name);\n>  \tret = reset_head(the_repository, NULL, opts->head_name,\n>  \t\t\t RESET_HEAD_REFS_ONLY,\n> -\t\t\t orig_head_reflog.buf, head_reflog.buf,\n> -\t\t\t DEFAULT_REFLOG_ACTION);\n> +\t\t\t orig_head_reflog.buf, head_reflog.buf, NULL);\n>  \n>  \tstrbuf_release(&orig_head_reflog);\n>  \tstrbuf_release(&head_reflog);\n> @@ -822,7 +821,7 @@ static int checkout_up_to_date(struct rebase_options *options)\n>  \t\t    options->switch_to);\n>  \tif (reset_head(the_repository, &options->orig_head,\n>  \t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n> -\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n> +\t\t       NULL, buf.buf, NULL) < 0)\n>  \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n>  \tstrbuf_release(&buf);\n>  \n> @@ -1273,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tstring_list_clear(&merge_rr, 1);\n>  \n>  \t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n> -\t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n> +\t\t\t       NULL, NULL, NULL) < 0)\n>  \t\t\tdie(_(\"could not discard worktree changes\"));\n>  \t\tremove_branch_state(the_repository, 0);\n>  \t\tif (read_basic_state(&options))\n> @@ -1778,8 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n>  \t\t\toid_to_hex(&options.onto->object.oid));\n>  \t\treset_head(the_repository, NULL, options.head_name,\n> -\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf,\n> -\t\t\t   DEFAULT_REFLOG_ACTION);\n> +\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n>  \t\tstrbuf_release(&msg);\n>  \t\tret = finish_rebase(&options);\n>  \t\tgoto cleanup;\n> diff --git a/reset.c b/reset.c\n> index 56d6e2a06d9..4a92e4bc30b 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -22,8 +22,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch\n>  \tsize_t prefix_len;\n>  \tint ret;\n>  \n> -\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n> -\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n> +\tif ((update_orig_head && !reflog_orig_head) || !reflog_head) {\n> +\t\tif (!default_reflog_action)\n> +\t\t\tBUG(\"default_reflog_action must be given when reflog messages are omitted\");\n> +\t\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n> +\t\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action :\n> +\t\t\t\t\t\t\t  default_reflog_action);\n> +\t}\n>  \tprefix_len = msg.len;\n>  \n>  \tif (update_orig_head) {\n> @@ -71,6 +76,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n>  {\n>  \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n>  \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n> +\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n>  \tstruct object_id *head = NULL, head_oid;\n>  \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n>  \tstruct lock_file lock = LOCK_INIT;\n> @@ -144,8 +150,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n>  \t\tgoto leave_reset_head;\n>  \t}\n>  \n> -\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n> -\t\t\t  reflog_orig_head, default_reflog_action, flags);\n> +\tif (oid != &head_oid || update_orig_head || switch_to_branch)\n> +\t\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n> +\t\t\t\t  reflog_orig_head, default_reflog_action,\n> +\t\t\t\t  flags);\n>  \n>  leave_reset_head:\n>  \trollback_lock_file(&lock);\n> diff --git a/sequencer.c b/sequencer.c\n> index 5c65f5f1ac5..78d203dec47 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4131,7 +4131,7 @@ void create_autostash(struct repository *r, const char *path)\n>  \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n>  \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n>  \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n> -\t\t\t       \"\") < 0)\n> +\t\t\t       NULL) < 0)\n>  \t\t\tdie(_(\"could not reset --hard\"));\n>  \n>  \t\tif (discard_index(r->index) < 0 ||\n"},{"id":"443683","messageId":"xmqqczm5r34h.fsf@gitster.g","threadId":"56620","inReplyTo":"5ea636009e7858e50357f0f6f8d8fa42e056db60.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/14] rebase: cleanup reset_head() calls","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T19:26:38Z","receivedAt":"2021-12-09T19:26:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> If ORIG_HEAD is not set by passing RESET_ORIG_HEAD then there is no\n> need to pass anything for reflog_orig_head. In addition to the callers\n> fixed in this commit move_to_original_branch() also passes\n> reflog_orig_head without setting ORIG_HEAD. That caller is mistakenly\n> passing the message it wants to put in the branch reflog which is not\n> currently possible so we delay fixing that caller until we can pass\n> the message as the branch reflog.\n\nAs I hinted elsewhere, these rules should be spelled out in a\ncomment before \"int reset_head(...)\" either in reset.[ch].\n\nFor this particular one, I wonder if \n\n (A) we can lose RESET_ORIG_HEAD bit and use the presence of\n     reflog_orig_head to mean what that bit currently means, or\n\n (B) we keep the current code strucure, but make it a BUG() if\n     a non-NULL reflog_orig_head is given without RESET_ORIG_HEAD\n     bit set.\n\n\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  builtin/rebase.c | 4 ++--\n>  1 file changed, 2 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 3d78b5c8bef..fdd822c470f 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -675,7 +675,7 @@ static int run_am(struct rebase_options *opts)\n>  \n>  \t\treset_head(the_repository, &opts->orig_head,\n>  \t\t\t   opts->head_name, 0,\n> -\t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n> +\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n>  \t\terror(_(\"\\ngit encountered an error while preparing the \"\n>  \t\t\t\"patches to replay\\n\"\n>  \t\t\t\"these revisions:\\n\"\n> @@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n>  \t\t\toid_to_hex(&options.onto->object.oid));\n>  \t\treset_head(the_repository, NULL, options.head_name,\n> -\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n> +\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n>  \t\tstrbuf_release(&msg);\n>  \t\tret = finish_rebase(&options);\n>  \t\tgoto cleanup;\n"},{"id":"443684","messageId":"xmqq7dcdr2x5.fsf@gitster.g","threadId":"56620","inReplyTo":"24b0566aba5d8800d209261a14aca4d872af8027.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/14] reset_head(): take struct rebase_head_opts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T19:31:02Z","receivedAt":"2021-12-09T19:31:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This function takes a confusingly large number of parameters which\n> makes it difficult to remember which order to pass them in. The\n> following commits will add a couple more parameters which makes the\n> problem worse. To address this change the function to take a struct of\n> options. Using a struct means that it is no longer necessary to\n> remember which order to pass the parameters in and anyone reading the\n> code can easily see which value is passed to each parameter.\n\nOK.  The documentation for members in the structure will serve as\nthe user manual for the reset_head() function.  Good.\n\n> +struct reset_head_opts {\n> +\t/*\n> +\t * The commit to checkout/reset to. Defaults to HEAD.\n> +\t */\n> +\tconst struct object_id *oid;\n> +\t/*\n> +\t * Optional branch to switch to.\n> +\t */\n> +\tconst char *branch;\n> +\t/*\n> +\t * Flags defined above.\n> +\t */\n> +\tunsigned flags;\n> +\t/*\n> +\t * Optional reflog message for HEAD, if this omitted but oid or branch\n> +\t * are given then default_reflog_action must be given.\n> +\t */\n\nIf we can enforce such a requirement in reset_head() implementation\nwith \"if (condition) BUG(...)\", we should do so to catch mistakes.\n\nThanks.  Looking good.\n\n"},{"id":"443694","messageId":"xmqq35n1qza2.fsf@gitster.g","threadId":"56620","inReplyTo":"dc5d11291e78bddde448f6e273bc58e9f2f83c95.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 12/14] rebase --apply: fix reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T20:49:41Z","receivedAt":"2021-12-09T20:49:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +\tif (opts->orig_head_msg && !update_orig_head)\n> +\t\tBUG(\"ORIG_HEAD reflog message given without updating ORIG_HEAD\");\n> +\n> +\tif (opts->branch_msg && !opts->branch)\n> +\t\tBUG(\"branch reflog message given without a branch\");\n> +\n\nLooking pretty good.\n\n> diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\n> index 77a313f62eb..d17b450e811 100755\n> --- a/t/t3406-rebase-message.sh\n> +++ b/t/t3406-rebase-message.sh\n> @@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'rebase --apply reflog' '\n> +\tgit checkout -b reflog-apply start &&\n> +\told_head_reflog=\"$(git log -g --format=%gs -1 HEAD)\" &&\n> +\n> +\tgit rebase --apply Y &&\n> +\n> +\tgit log -g --format=%gs -4 HEAD >actual &&\n> +\tcat >expect <<-EOF &&\n> +\trebase finished: returning to refs/heads/reflog-apply\n> +\trebase: Z\n> +\trebase: checkout Y\n> +\t$old_head_reflog\n> +\tEOF\n> +\ttest_cmp expect actual &&\n> +\n> +\tgit log -g --format=%gs -2 reflog-apply >actual &&\n> +\tcat >expect <<-EOF &&\n> +\trebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)\n> +\tbranch: Created from start\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'rebase -i onto unrelated history' '\n>  \tgit init unrelated &&\n>  \ttest_commit -C unrelated 1 &&\n"},{"id":"443695","messageId":"xmqqtufhpk1a.fsf@gitster.g","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/14] rebase: reset_head() related fixes and improvements","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T21:04:17Z","receivedAt":"2021-12-09T21:04:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Thanks for the comments on V1. I have tried to improve the commit messages\n> to explain better the motivation and implications of the changes in this\n> series and I have added some more tests. I have rebased onto v2.34.0 to\n> avoid some merges conflicts.\n\nThanks.\n\nIt still is not clear in the cover letter what the overall theme of\nthe topic is, and the original cover letter was deliberately vague\nby saying \"Fix *some* issues\".  Random assortment of changes to\nvarious code paths, the only common trait among them being they are\nsomehow related to reset_head()?\n\n> Cover letter for V1: Fix some issues with the implementation and use of\n> reset_head(). The last patch was previously posted as [1], I have updated\n> the commit message and rebased it onto the fixes in this series. There are a\n> couple of small conflicts merging this into seen, I think they should be\n> easy to resolve (in rebase.c take both sides in reset.c take the changed\n> lines from each side). These patches are based on pw/rebase-of-a-tag-fix\n\nI've read the patches through.  It does revolve around the use of\nreset_head().  I would have appreciated if the cover letter said\nsomething along this line:\n\n    reset.c::reset_head() started its life at ac7f467f\n    (builtin/rebase: support running \"git rebase <upstream>\",\n    2018-08-07) as a way to detach the HEAD to replay the commits\n    during \"git rebase\", but over time it learned to do many things,\n    like switching the tip of the branch to another commit,\n    recording the old value of HEAD in ORIG_HEAD while it does so,\n    recording reflog entries for both HEAD and for the branch.\n\n    The API into the function got clunky and it is harder than\n    necessary for the callers to use the function correctly, which\n    led to a handful of bugs that this series is going to fix.\n\n       ... list of bugs here ...\n\n    Later steps of this series revamps the API so that it is harder\n    to use it incorrectly to prevent future bugs.\n\nAnyway, I think the series is more or less in a very good shape,\neven though a few comments I threw at this round may result in a\nfurther improvement.\n\nThanks for working on this.\n"},{"id":"443696","messageId":"xmqqk0gdpk0v.fsf@gitster.g","threadId":"56620","inReplyTo":"0e84d00572ed9a99d68ec856b779f550b2a934de.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 01/14] rebase: factor out checkout for up to date branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-09T21:04:32Z","receivedAt":"2021-12-09T21:04:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This code is heavily indented and it will be convenient later in the\n> series to have it in its own function.\n\nLooks good in the sense that it is straight code movement without\nchanging any behaviour that won't hurt.\n\nWithout a clear overall direction given in the cover letter, however,\nit is hard to judge if \"being convenient later in the series\" is a\ngood thing in the first place, though.\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  builtin/rebase.c | 33 +++++++++++++++++++--------------\n>  1 file changed, 19 insertions(+), 14 deletions(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 34b4744e5f3..f5c37b7d4a5 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -812,6 +812,23 @@ static int rebase_config(const char *var, const char *value, void *data)\n>  \treturn git_default_config(var, value, data);\n>  }\n>  \n> +static int checkout_up_to_date(struct rebase_options *options)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tint ret = 0;\n> +\n> +\tstrbuf_addf(&buf, \"%s: checkout %s\",\n> +\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n> +\t\t    options->switch_to);\n> +\tif (reset_head(the_repository, &options->orig_head, \"checkout\",\n> +\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n> +\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n> +\t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n> +\tstrbuf_release(&buf);\n> +\n> +\treturn ret;\n> +}\n> +\n>  /*\n>   * Determines whether the commits in from..to are linear, i.e. contain\n>   * no merge commits. This function *expects* `from` to be an ancestor of\n> @@ -1673,21 +1690,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tif (!(options.flags & REBASE_FORCE)) {\n>  \t\t\t/* Lazily switch to the target branch if needed... */\n>  \t\t\tif (options.switch_to) {\n> -\t\t\t\tstrbuf_reset(&buf);\n> -\t\t\t\tstrbuf_addf(&buf, \"%s: checkout %s\",\n> -\t\t\t\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n> -\t\t\t\t\t    options.switch_to);\n> -\t\t\t\tif (reset_head(the_repository,\n> -\t\t\t\t\t       &options.orig_head, \"checkout\",\n> -\t\t\t\t\t       options.head_name,\n> -\t\t\t\t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n> -\t\t\t\t\t       NULL, buf.buf,\n> -\t\t\t\t\t       DEFAULT_REFLOG_ACTION) < 0) {\n> -\t\t\t\t\tret = error(_(\"could not switch to \"\n> -\t\t\t\t\t\t\t\"%s\"),\n> -\t\t\t\t\t\t      options.switch_to);\n> +\t\t\t\tret = checkout_up_to_date(&options);\n> +\t\t\t\tif (ret)\n>  \t\t\t\t\tgoto cleanup;\n> -\t\t\t\t}\n>  \t\t\t}\n>  \n>  \t\t\tif (!(options.flags & REBASE_NO_QUIET))\n"},{"id":"443884","messageId":"CABPp-BEHW4VLG18twcM_8iOco1jZ2iuGT+KN8aS+-sAAnBhTnw@mail.gmail.com","threadId":"56620","inReplyTo":"45a5b5e981804c540991a63675909c4155b86b5e.1638975482.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 13/14] rebase --apply: set ORIG_HEAD correctly","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-12-11T10:59:29Z","receivedAt":"2021-12-11T10:59:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Dec 8, 2021 at 6:58 AM Phillip Wood via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> At the start of a rebase ORIG_HEAD is updated to tip of the branch\n\nCould we insert a comma between \"rebase\" and \"ORIG_HEAD\"?  Otherwise I\ntry to read it as \"At the start of a `rebase ORIG_HEAD` is updated...\"\nand get totally lost.  I had to re-read the sentence a few times\nbefore I understood what it was trying to say.\n\nAlso, perhaps s/to tip of/to the tip of/ ?\n\n> being rebased. Unfortunately reset_head() always uses the current\n> value of HEAD for this which is incorrect if the rebase is started\n> with \"git rebase <upstream> <branch>\" as in that case ORIG_HEAD should\n> be updated to <branch>. This only affects the \"apply\" backend as the\n> \"merge\" backend does not yet use reset_head() for the initial\n> checkout. Fix this by passing in orig_head when calling reset_head()\n> and add some regression tests.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  builtin/rebase.c           |  1 +\n>  reset.c                    |  4 +++-\n>  reset.h                    |  4 ++++\n>  t/t3418-rebase-continue.sh | 26 ++++++++++++++++++++++++++\n>  4 files changed, 34 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index b55a9cff05d..e942c300f8c 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -1769,6 +1769,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>         strbuf_addf(&msg, \"%s: checkout %s\",\n>                     getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n>         ropts.oid = &options.onto->object.oid;\n> +       ropts.orig_head = &options.orig_head,\n>         ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n>                         RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n>         ropts.head_msg = msg.buf;\n> diff --git a/reset.c b/reset.c\n> index e02915c0f65..448cb3fd785 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -15,6 +15,7 @@ static int update_refs(const struct reset_head_opts *opts,\n>         unsigned detach_head = opts->flags & RESET_HEAD_DETACH;\n>         unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n>         unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n> +       const struct object_id *orig_head = opts->orig_head;\n>         const char *switch_to_branch = opts->branch;\n>         const char *reflog_branch = opts->branch_msg;\n>         const char *reflog_head = opts->head_msg;\n> @@ -43,7 +44,8 @@ static int update_refs(const struct reset_head_opts *opts,\n>                                 strbuf_addstr(&msg, \"updating ORIG_HEAD\");\n>                                 reflog_orig_head = msg.buf;\n>                         }\n> -                       update_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n> +                       update_ref(reflog_orig_head, \"ORIG_HEAD\",\n> +                                  orig_head ? orig_head : head,\n>                                    old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n>                 } else if (old_orig)\n>                         delete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n> diff --git a/reset.h b/reset.h\n> index 7ef7e43ea8c..a28f81829d8 100644\n> --- a/reset.h\n> +++ b/reset.h\n> @@ -22,6 +22,10 @@ struct reset_head_opts {\n>          * The commit to checkout/reset to. Defaults to HEAD.\n>          */\n>         const struct object_id *oid;\n> +       /*\n> +        * Optional value to set ORIG_HEAD. Defaults to HEAD.\n> +        */\n> +       const struct object_id *orig_head;\n>         /*\n>          * Optional branch to switch to.\n>          */\n> diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n> index 22eca73aa3e..130e2f9b553 100755\n> --- a/t/t3418-rebase-continue.sh\n> +++ b/t/t3418-rebase-continue.sh\n> @@ -308,4 +308,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n>         test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n>  '\n>\n> +test_orig_head_helper () {\n> +       test_when_finished 'git rebase --abort &&\n> +               git checkout topic &&\n> +               git reset --hard commit-new-file-F2-on-topic-branch' &&\n> +       git update-ref -d ORIG_HEAD &&\n> +       test_must_fail git rebase \"$@\" &&\n> +       test_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch\n> +}\n> +\n> +test_orig_head () {\n> +       type=$1\n> +       test_expect_success \"rebase $type sets ORIG_HEAD correctly\" '\n> +               git checkout topic &&\n> +               git reset --hard commit-new-file-F2-on-topic-branch &&\n> +               test_orig_head_helper $type main\n> +       '\n> +\n> +       test_expect_success \"rebase $type <upstream> <branch> sets ORIG_HEAD correctly\" '\n> +               git checkout main &&\n> +               test_orig_head_helper $type main topic\n> +       '\n> +}\n> +\n> +test_orig_head --apply\n> +test_orig_head --merge\n> +\n>  test_done\n> --\n"},{"id":"443885","messageId":"CABPp-BFopbtU7uNmf_7Y7rR3o7iZKt2JL8hX8k9HfCLjTVAxJA@mail.gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/14] rebase: reset_head() related fixes and improvements","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-12-11T11:05:24Z","receivedAt":"2021-12-11T11:05:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Dec 8, 2021 at 6:58 AM Phillip Wood via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Thanks for the comments on V1. I have tried to improve the commit messages\n> to explain better the motivation and implications of the changes in this\n> series and I have added some more tests. I have rebased onto v2.34.0 to\n> avoid some merges conflicts.\n>\n> Changes since V1:\n>\n>  * Patch 1 - unchanged.\n>  * Patches 2, 3 - these are new and fix an bug I noticed while adding a test\n>    to patch 4.\n>  * Patches 4, 5 - improved commit messages and added tests.\n>  * Patch 6 - reworded commit message.\n>  * Patch 7 - split out some changes that used to be in patch 9.\n>  * Patch 8 - in principle the same but the range-diff is noisy due to the\n>    addition of patch 3.\n>  * Patch 9 - reworded commit message.\n>  * Patch 10 - unchanged.\n>  * Patch 11 - reworded commit message and a couple of comments.\n>  * Patch 12 - minor changes to comments.\n>  * Patch 13 - cosmetic changes to commit message and tests.\n>  * Patch 14 - cosmetic changes to commit message.\n\nI don't know why, but I seem to have gotten interrupted a lot more\nreviewing this series than others; I've come back to it multiple\ntimes.  Most of the stuff I found had already been noted by Junio; the\nonly new thing was some tiny grammatical comments I left on the commit\nmessage in Patch 13.\n\nOverall, the series looks very nice; thanks for working on this.\n"},{"id":"446838","messageId":"36966d92-1460-699a-007d-da2a7da6c088@gmail.com","threadId":"56620","inReplyTo":"xmqqpmq5r3j9.fsf@gitster.g","subject":"Re: [PATCH v2 07/14] create_autostash(): remove unneeded parameter","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-01-25T11:06:19Z","receivedAt":"2022-01-25T11:08:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 09/12/2021 19:17, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> The default_reflog parameter of create_autostash() is passed to\n>> reset_head(). However as creating a stash does not involve updating\n>> any refs the parameter is not used by reset_head(). Removing the\n>> parameter from create_autostash() simplifies the callers.\n> \n> It does make the callers of create_autostash() easier to reason\n> about, but ...\n> \n>> @@ -4132,7 +4131,7 @@ void create_autostash(struct repository *r, const char *path,\n>>   \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n>>   \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n>>   \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n>> -\t\t\t       default_reflog_action) < 0)\n>> +\t\t\t       \"\") < 0)\n> \n> ... makes the reader wonder what the empty string is doing here.\n> The fact that reset_head() does not care about the last parameter\n> when not given oid or switch_to_branch parameters feels like too\n> much implementation detail to expect the callers to know about.\n> \n> Unless it is documented in reset.[ch] before the beginning of the\n> \"int reset_head(...)\" definition/declaration, that is.\n\nI've moved this patch down the series so it looks like\n-\t\t\t       default_reflog_action) < 0)\n+\t\t\t       NULL) < 0)\n\nwhich should be clearer\n\nBest Wishes\n\nPhillip\n\n> \n>>   \t\t\tdie(_(\"could not reset --hard\"));\n>>   \n>>   \t\tif (discard_index(r->index) < 0 ||\n>> diff --git a/sequencer.h b/sequencer.h\n>> index 05a7d2ba6b3..da64473636b 100644\n>> --- a/sequencer.h\n>> +++ b/sequencer.h\n>> @@ -197,8 +197,7 @@ void commit_post_rewrite(struct repository *r,\n>>   \t\t\t const struct commit *current_head,\n>>   \t\t\t const struct object_id *new_head);\n>>   \n>> -void create_autostash(struct repository *r, const char *path,\n>> -\t\t      const char *default_reflog_action);\n>> +void create_autostash(struct repository *r, const char *path);\n>>   int save_autostash(const char *path);\n>>   int apply_autostash(const char *path);\n>>   int apply_autostash_oid(const char *stash_oid);\n\n"},{"id":"446839","messageId":"74ef1b90-0d7a-fa28-9c5a-4c328674384e@gmail.com","threadId":"56620","inReplyTo":"xmqqczm5r34h.fsf@gitster.g","subject":"Re: [PATCH v2 10/14] rebase: cleanup reset_head() calls","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-01-25T11:07:57Z","receivedAt":"2022-01-25T11:10:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 09/12/2021 19:26, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> If ORIG_HEAD is not set by passing RESET_ORIG_HEAD then there is no\n>> need to pass anything for reflog_orig_head. In addition to the callers\n>> fixed in this commit move_to_original_branch() also passes\n>> reflog_orig_head without setting ORIG_HEAD. That caller is mistakenly\n>> passing the message it wants to put in the branch reflog which is not\n>> currently possible so we delay fixing that caller until we can pass\n>> the message as the branch reflog.\n> \n> As I hinted elsewhere, these rules should be spelled out in a\n> comment before \"int reset_head(...)\" either in reset.[ch].\n> \n> For this particular one, I wonder if\n> \n>   (A) we can lose RESET_ORIG_HEAD bit and use the presence of\n>       reflog_orig_head to mean what that bit currently means, or\n> \n>   (B) we keep the current code strucure, but make it a BUG() if\n>       a non-NULL reflog_orig_head is given without RESET_ORIG_HEAD\n>       bit set.\n\nWe do (B) later in the series, it cannot be done in this commit as we \nleave move_to_original_branch() unchanged here and that would BUG() out. \nI've added a comment it the commit message to explain this.\n\nBest Wishes\n\nPhillip\n\n> \n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   builtin/rebase.c | 4 ++--\n>>   1 file changed, 2 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/builtin/rebase.c b/builtin/rebase.c\n>> index 3d78b5c8bef..fdd822c470f 100644\n>> --- a/builtin/rebase.c\n>> +++ b/builtin/rebase.c\n>> @@ -675,7 +675,7 @@ static int run_am(struct rebase_options *opts)\n>>   \n>>   \t\treset_head(the_repository, &opts->orig_head,\n>>   \t\t\t   opts->head_name, 0,\n>> -\t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n>> +\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n>>   \t\terror(_(\"\\ngit encountered an error while preparing the \"\n>>   \t\t\t\"patches to replay\\n\"\n>>   \t\t\t\"these revisions:\\n\"\n>> @@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>>   \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n>>   \t\t\toid_to_hex(&options.onto->object.oid));\n>>   \t\treset_head(the_repository, NULL, options.head_name,\n>> -\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n>> +\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n>>   \t\tstrbuf_release(&msg);\n>>   \t\tret = finish_rebase(&options);\n>>   \t\tgoto cleanup;\n\n"},{"id":"446930","messageId":"aedee582-7f00-7e12-c378-35b0cbc6b565@gmail.com","threadId":"56620","inReplyTo":"xmqqtufhpk1a.fsf@gitster.g","subject":"Re: [PATCH v2 00/14] rebase: reset_head() related fixes and improvements","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-01-26T10:53:44Z","receivedAt":"2022-01-26T10:53:51Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 09/12/2021 21:04, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> Thanks for the comments on V1. I have tried to improve the commit messages\n>> to explain better the motivation and implications of the changes in this\n>> series and I have added some more tests. I have rebased onto v2.34.0 to\n>> avoid some merges conflicts.\n> \n> Thanks.\n> \n> It still is not clear in the cover letter what the overall theme of\n> the topic is, and the original cover letter was deliberately vague\n> by saying \"Fix *some* issues\".  Random assortment of changes to\n> various code paths, the only common trait among them being they are\n> somehow related to reset_head()?\n\nSo the theme started out as \"convert 'rebase -i' to use reset_head() and \nstop forking 'git checkout'\" which I thought would be one to two \npatches. Then I started looking at the code and realized that \nreset_head() needed some work before we could use it for 'rebase -i' and \nthat work ended up dominating the series. I've updated the cover letter \nas you suggested.\n\nBest Wishes\n\nPhillip\n\n>> Cover letter for V1: Fix some issues with the implementation and use of\n>> reset_head(). The last patch was previously posted as [1], I have updated\n>> the commit message and rebased it onto the fixes in this series. There are a\n>> couple of small conflicts merging this into seen, I think they should be\n>> easy to resolve (in rebase.c take both sides in reset.c take the changed\n>> lines from each side). These patches are based on pw/rebase-of-a-tag-fix\n> \n> I've read the patches through.  It does revolve around the use of\n> reset_head().  I would have appreciated if the cover letter said\n> something along this line:\n> \n>      reset.c::reset_head() started its life at ac7f467f\n>      (builtin/rebase: support running \"git rebase <upstream>\",\n>      2018-08-07) as a way to detach the HEAD to replay the commits\n>      during \"git rebase\", but over time it learned to do many things,\n>      like switching the tip of the branch to another commit,\n>      recording the old value of HEAD in ORIG_HEAD while it does so,\n>      recording reflog entries for both HEAD and for the branch.\n> \n>      The API into the function got clunky and it is harder than\n>      necessary for the callers to use the function correctly, which\n>      led to a handful of bugs that this series is going to fix.\n> \n>         ... list of bugs here ...\n> \n>      Later steps of this series revamps the API so that it is harder\n>      to use it incorrectly to prevent future bugs.\n> \n> Anyway, I think the series is more or less in a very good shape,\n> even though a few comments I threw at this round may result in a\n> further improvement.\n> \n> Thanks for working on this.\n\n"},{"id":"446942","messageId":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v2.git.1638975481.gitgitgadget@gmail.com","subject":"[PATCH v3 00/14] rebase: reset_head() related fixes and improvements","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:35Z","receivedAt":"2022-01-26T13:05:55Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Thanks to Junio and Elijah for their comments on V2. There aren't too many\nchanges this time.\n\nThis series started out with the aim of converting 'rebase -i' to use\nreset_head() instead of forking 'git checkout'. As 'rebase --apply' already\nuses reset_head() I assumed this would be straight forward. However it has\nmorphed into a series of fixes for reset_head() followed by the intended\nconversion of 'rebase -i'.\n\nreset.c::reset_head() started its life at ac7f467f (builtin/rebase: support\nrunning \"git rebase \", 2018-08-07) as a way to detach the HEAD to replay the\ncommits during \"git rebase\", but over time it learned to do many things,\nlike switching the tip of the branch to another commit, recording the old\nvalue of HEAD in ORIG_HEAD while it does so, recording reflog entries for\nboth HEAD and for the branch.\n\nThe API into the function got clunky and it is harder than necessary for the\ncallers to use the function correctly, which led to a handful of bugs that\nare fixed by this series. The bugs include\n\n * passing the wrong oid to the post-checkout hook\n * removing untracked files on checkout\n * running the post-checkout hook if the checkout fails\n * passing parameters to reset_head() that it does not use\n * incorrect reflog messages for 'rebase --apply'\n * sometimes setting ORIG_HEAD incorrectly at the start 'rebase --apply'\n\nLater steps of this series revamps the API so that it is harder to use it\nincorrectly to prevent future bugs and finally convert 'rebase -i' to use\nreset_head()\n\nChanges since V2:\n\n * Updated cover letter as suggested by Junio\n * Patch 4 - fixed typos in the commit message spotted by Junio\n * Patch 9 - Moved later in the series to simplify the autostash canges as\n   suggested by Junio. This used to be patch 7\n * Patch 10 - Added a comment to the commit message explaining why we cannot\n   BUG() on an invalid parameter until a change is made in a later commit\n * Patch 13 - Reworded the first sentence commit message as suggest by\n   Elijah.\n\nCover letter for V2:\n\nThanks for the comments on V1. I have tried to improve the commit messages\nto explain better the motivation and implications of the changes in this\nseries and I have added some more tests. I have rebased onto v2.34.0 to\navoid some merges conflicts.\n\nChanges since V1:\n\n * Patch 1 - unchanged.\n * Patches 2, 3 - these are new and fix an bug I noticed while adding a test\n   to patch 4.\n * Patches 4, 5 - improved commit messages and added tests.\n * Patch 6 - reworded commit message.\n * Patch 7 - split out some changes that used to be in patch 9.\n * Patch 8 - in principle the same but the range-diff is noisy due to the\n   addition of patch 3.\n * Patch 9 - reworded commit message.\n * Patch 10 - unchanged.\n * Patch 11 - reworded commit message and a couple of comments.\n * Patch 12 - minor changes to comments.\n * Patch 13 - cosmetic changes to commit message and tests.\n * Patch 14 - cosmetic changes to commit message.\n\nCover letter for V1: Fix some issues with the implementation and use of\nreset_head(). The last patch was previously posted as [1], I have updated\nthe commit message and rebased it onto the fixes in this series. There are a\ncouple of small conflicts merging this into seen, I think they should be\neasy to resolve (in rebase.c take both sides in reset.c take the changed\nlines from each side). These patches are based on pw/rebase-of-a-tag-fix\n\n[1]\nhttps://lore.kernel.org/git/39ad40c9297531a2d42b7263a1d41b1ecbc23c0a.1631108472.git.gitgitgadget@gmail.com/\n\nPhillip Wood (14):\n  rebase: factor out checkout for up to date branch\n  t5403: refactor rebase post-checkout hook tests\n  rebase: pass correct arguments to post-checkout hook\n  rebase: do not remove untracked files on checkout\n  rebase --apply: don't run post-checkout hook if there is an error\n  reset_head(): remove action parameter\n  reset_head(): factor out ref updates\n  reset_head(): make default_reflog_action optional\n  create_autostash(): remove unneeded parameter\n  rebase: cleanup reset_head() calls\n  reset_head(): take struct rebase_head_opts\n  rebase --apply: fix reflog\n  rebase --apply: set ORIG_HEAD correctly\n  rebase -m: don't fork git checkout\n\n builtin/merge.c               |   6 +-\n builtin/rebase.c              | 101 +++++++++++++----------\n reset.c                       | 149 ++++++++++++++++++++--------------\n reset.h                       |  48 ++++++++++-\n sequencer.c                   |  47 ++++-------\n sequencer.h                   |   3 +-\n t/t3406-rebase-message.sh     |  23 ++++++\n t/t3418-rebase-continue.sh    |  26 ++++++\n t/t5403-post-checkout-hook.sh |  67 +++++++++++----\n 9 files changed, 312 insertions(+), 158 deletions(-)\n\n\nbase-commit: cd3e606211bb1cf8bc57f7d76bab98cc17a150bc\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1049%2Fphillipwood%2Fwip%2Frebase-reset-head-fixes-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1049/phillipwood/wip/rebase-reset-head-fixes-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1049\n\nRange-diff vs v2:\n\n  1:  0e84d00572e =  1:  0e84d00572e rebase: factor out checkout for up to date branch\n  2:  a67a5a03b94 =  2:  a67a5a03b94 t5403: refactor rebase post-checkout hook tests\n  3:  07867760e68 =  3:  07867760e68 rebase: pass correct arguments to post-checkout hook\n  4:  2b499704c8f !  4:  f4b925508e7 rebase: do not remove untracked files on checkout\n     @@ Commit message\n          <upstream> is an ancestor of <branch> then it will fast-forward and\n          checkout <branch>. Normally a checkout or picking a commit during a\n          rebase will refuse to overwrite untracked files, however rebase does\n     -    overwrite untracked files when checking <branch>.\n     +    overwrite untracked files when checking out <branch>.\n      \n          The fix is to only set reset in `unpack_tree_opts` if flags contains\n          `RESET_HEAD_HARD`. t5403 may seem like an odd home for the new test\n          but it will be extended in the next commit to check that the\n          post-checkout hook is not run when the checkout fails.\n      \n     -    The test for `!deatch_head` dates back to the\n     +    The test for `!detach_head` dates back to the\n          original implementation of reset_head() in\n          ac7f467fef (\"builtin/rebase: support running \"git rebase <upstream>\"\",\n          2018-08-07) and was correct until e65123a71d\n  5:  04e7340a7e7 =  5:  4de5104d22d rebase --apply: don't run post-checkout hook if there is an error\n  6:  32ffa98c1bc =  6:  ff23498e93e reset_head(): remove action parameter\n  8:  29e06e7d36d =  7:  688ebc45bf7 reset_head(): factor out ref updates\n  9:  9d00a218daf !  8:  a5cc7eaa925 reset_head(): make default_reflog_action optional\n     @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n       \n       leave_reset_head:\n       \trollback_lock_file(&lock);\n     -\n     - ## sequencer.c ##\n     -@@ sequencer.c: void create_autostash(struct repository *r, const char *path)\n     - \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n     - \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n     - \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n     --\t\t\t       \"\") < 0)\n     -+\t\t\t       NULL) < 0)\n     - \t\t\tdie(_(\"could not reset --hard\"));\n     - \n     - \t\tif (discard_index(r->index) < 0 ||\n  7:  341fe183c18 !  9:  dd3a22384d2 create_autostash(): remove unneeded parameter\n     @@ sequencer.c: void create_autostash(struct repository *r, const char *path,\n       \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n       \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n      -\t\t\t       default_reflog_action) < 0)\n     -+\t\t\t       \"\") < 0)\n     ++\t\t\t       NULL) < 0)\n       \t\t\tdie(_(\"could not reset --hard\"));\n       \n       \t\tif (discard_index(r->index) < 0 ||\n 10:  5ea636009e7 ! 10:  ad7c6467987 rebase: cleanup reset_head() calls\n     @@ Commit message\n          currently possible so we delay fixing that caller until we can pass\n          the message as the branch reflog.\n      \n     +    A later commit will make it a BUG() to pass reflog_orig_head without\n     +    RESET_ORIG_HEAD, that changes cannot be done here as it needs to wait\n     +    for move_to_original_branch() to be fixed first.\n     +\n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## builtin/rebase.c ##\n 11:  24b0566aba5 = 11:  d170703e833 reset_head(): take struct rebase_head_opts\n 12:  dc5d11291e7 = 12:  4973892561e rebase --apply: fix reflog\n 13:  45a5b5e9818 ! 13:  0ef0e978112 rebase --apply: set ORIG_HEAD correctly\n     @@ Metadata\n       ## Commit message ##\n          rebase --apply: set ORIG_HEAD correctly\n      \n     -    At the start of a rebase ORIG_HEAD is updated to tip of the branch\n     -    being rebased. Unfortunately reset_head() always uses the current\n     -    value of HEAD for this which is incorrect if the rebase is started\n     -    with \"git rebase <upstream> <branch>\" as in that case ORIG_HEAD should\n     -    be updated to <branch>. This only affects the \"apply\" backend as the\n     -    \"merge\" backend does not yet use reset_head() for the initial\n     -    checkout. Fix this by passing in orig_head when calling reset_head()\n     -    and add some regression tests.\n     +    At the start of a rebase, ORIG_HEAD is updated to the tip of the\n     +    branch being rebased. Unfortunately reset_head() always uses the\n     +    current value of HEAD for this which is incorrect if the rebase is\n     +    started with \"git rebase <upstream> <branch>\" as in that case\n     +    ORIG_HEAD should be updated to <branch>. This only affects the \"apply\"\n     +    backend as the \"merge\" backend does not yet use reset_head() for the\n     +    initial checkout. Fix this by passing in orig_head when calling\n     +    reset_head() and add some regression tests.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n 14:  3f64b9274b5 = 14:  9b9560ef676 rebase -m: don't fork git checkout\n\n-- \ngitgitgadget\n"},{"id":"446943","messageId":"0e84d00572ed9a99d68ec856b779f550b2a934de.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 01/14] rebase: factor out checkout for up to date branch","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:36Z","receivedAt":"2022-01-26T13:05:56Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis code is heavily indented and it will be convenient later in the\nseries to have it in its own function.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 33 +++++++++++++++++++--------------\n 1 file changed, 19 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 34b4744e5f3..f5c37b7d4a5 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -812,6 +812,23 @@ static int rebase_config(const char *var, const char *value, void *data)\n \treturn git_default_config(var, value, data);\n }\n \n+static int checkout_up_to_date(struct rebase_options *options)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tstrbuf_addf(&buf, \"%s: checkout %s\",\n+\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n+\t\t    options->switch_to);\n+\tif (reset_head(the_repository, &options->orig_head, \"checkout\",\n+\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n+\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n+\t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n /*\n  * Determines whether the commits in from..to are linear, i.e. contain\n  * no merge commits. This function *expects* `from` to be an ancestor of\n@@ -1673,21 +1690,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tif (!(options.flags & REBASE_FORCE)) {\n \t\t\t/* Lazily switch to the target branch if needed... */\n \t\t\tif (options.switch_to) {\n-\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\tstrbuf_addf(&buf, \"%s: checkout %s\",\n-\t\t\t\t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n-\t\t\t\t\t    options.switch_to);\n-\t\t\t\tif (reset_head(the_repository,\n-\t\t\t\t\t       &options.orig_head, \"checkout\",\n-\t\t\t\t\t       options.head_name,\n-\t\t\t\t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t\t\t\t       NULL, buf.buf,\n-\t\t\t\t\t       DEFAULT_REFLOG_ACTION) < 0) {\n-\t\t\t\t\tret = error(_(\"could not switch to \"\n-\t\t\t\t\t\t\t\"%s\"),\n-\t\t\t\t\t\t      options.switch_to);\n+\t\t\t\tret = checkout_up_to_date(&options);\n+\t\t\t\tif (ret)\n \t\t\t\t\tgoto cleanup;\n-\t\t\t\t}\n \t\t\t}\n \n \t\t\tif (!(options.flags & REBASE_NO_QUIET))\n-- \ngitgitgadget\n\n"},{"id":"446944","messageId":"a67a5a03b94ccbaec85da2c93ce5bbffcd5cfac9.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 02/14] t5403: refactor rebase post-checkout hook tests","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:37Z","receivedAt":"2022-01-26T13:05:57Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThese tests only test the default backend and do not check that the\narguments passed to the hook are correct. Fix this by running the\ntests with both backends and adding checks for the hook arguments.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n t/t5403-post-checkout-hook.sh | 42 ++++++++++++++++++++++-------------\n 1 file changed, 26 insertions(+), 16 deletions(-)\n\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex 1ec9e23be75..272b02687ba 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -49,23 +49,33 @@ test_expect_success 'post-checkout receives the right args when not switching br\n \ttest $old = $new && test $flag = 0\n '\n \n-test_expect_success 'post-checkout is triggered on rebase' '\n-\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n-\tgit checkout -b rebase-test main &&\n-\trm -f .git/post-checkout.args &&\n-\tgit rebase rebase-on-me &&\n-\tread old new flag <.git/post-checkout.args &&\n-\ttest $old != $new && test $flag = 1\n-'\n+test_rebase () {\n+\targs=\"$*\" &&\n+\ttest_expect_success \"post-checkout is triggered on rebase $args\" '\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n+\t\tgit checkout -B rebase-test main &&\n+\t\trm -f .git/post-checkout.args &&\n+\t\tgit rebase $args rebase-on-me &&\n+\t\tread old new flag <.git/post-checkout.args &&\n+\t\ttest_cmp_rev main $old &&\n+\t\ttest_cmp_rev rebase-on-me $new &&\n+\t\ttest $flag = 1\n+\t'\n \n-test_expect_success 'post-checkout is triggered on rebase with fast-forward' '\n-\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n-\tgit checkout -b ff-rebase-test rebase-on-me^ &&\n-\trm -f .git/post-checkout.args &&\n-\tgit rebase rebase-on-me &&\n-\tread old new flag <.git/post-checkout.args &&\n-\ttest $old != $new && test $flag = 1\n-'\n+\ttest_expect_success \"post-checkout is triggered on rebase $args with fast-forward\" '\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n+\t\tgit checkout -B ff-rebase-test rebase-on-me^ &&\n+\t\trm -f .git/post-checkout.args &&\n+\t\tgit rebase $args rebase-on-me &&\n+\t\tread old new flag <.git/post-checkout.args &&\n+\t\ttest_cmp_rev rebase-on-me^ $old &&\n+\t\ttest_cmp_rev rebase-on-me $new &&\n+\t\ttest $flag = 1\n+\t'\n+}\n+\n+test_rebase --apply &&\n+test_rebase --merge\n \n test_expect_success 'post-checkout hook is triggered by clone' '\n \tmkdir -p templates/hooks &&\n-- \ngitgitgadget\n\n"},{"id":"446945","messageId":"07867760e68a6b28f718bdcb33094461592affaa.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 03/14] rebase: pass correct arguments to post-checkout hook","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:38Z","receivedAt":"2022-01-26T13:05:57Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf a rebase started with \"rebase [--apply|--merge] <upstream> <branch>\"\ndetects that <upstream> is an ancestor of <branch> then it fast-forwards\nand checks out <branch>. Unfortunately in that case it passed the null\noid as the first argument to the post-checkout hook rather than the oid\nof HEAD.\n\nA side effect of this change is that the call to update_ref() which\nupdates HEAD now always receives the old value of HEAD. This provides\nprotection against another process updating HEAD during the checkout.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c                       | 18 +++++++++---------\n t/t5403-post-checkout-hook.sh | 13 +++++++++++++\n 2 files changed, 22 insertions(+), 9 deletions(-)\n\ndiff --git a/reset.c b/reset.c\nindex f214df3d96c..315fef91d33 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -18,7 +18,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n \tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n-\tstruct object_id head_oid;\n+\tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n@@ -26,8 +26,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tconst char *reflog_action;\n \tstruct strbuf msg = STRBUF_INIT;\n \tsize_t prefix_len;\n-\tstruct object_id *orig = NULL, oid_orig,\n-\t\t*old_orig = NULL, oid_old_orig;\n+\tstruct object_id *old_orig = NULL, oid_old_orig;\n \tint ret = 0, nr = 0;\n \n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n@@ -38,7 +37,9 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t\tgoto leave_reset_head;\n \t}\n \n-\tif ((!oid || !reset_hard) && get_oid(\"HEAD\", &head_oid)) {\n+\tif (!get_oid(\"HEAD\", &head_oid)) {\n+\t\thead = &head_oid;\n+\t} else if (!oid || !reset_hard) {\n \t\tret = error(_(\"could not determine HEAD revision\"));\n \t\tgoto leave_reset_head;\n \t}\n@@ -98,13 +99,12 @@ reset_head_refs:\n \tif (update_orig_head) {\n \t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n \t\t\told_orig = &oid_old_orig;\n-\t\tif (!get_oid(\"HEAD\", &oid_orig)) {\n-\t\t\torig = &oid_orig;\n+\t\tif (head) {\n \t\t\tif (!reflog_orig_head) {\n \t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n \t\t\t\treflog_orig_head = msg.buf;\n \t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", orig,\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n \t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n \t\t} else if (old_orig)\n \t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n@@ -116,7 +116,7 @@ reset_head_refs:\n \t\treflog_head = msg.buf;\n \t}\n \tif (!switch_to_branch)\n-\t\tret = update_ref(reflog_head, \"HEAD\", oid, orig,\n+\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n \t\t\t\t detach_head ? REF_NO_DEREF : 0,\n \t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \telse {\n@@ -128,7 +128,7 @@ reset_head_refs:\n \t}\n \tif (run_hook)\n \t\trun_hook_le(NULL, \"post-checkout\",\n-\t\t\t    oid_to_hex(orig ? orig : null_oid()),\n+\t\t\t    oid_to_hex(head ? head : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\n \n leave_reset_head:\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex 272b02687ba..17ab518f268 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -72,6 +72,19 @@ test_rebase () {\n \t\ttest_cmp_rev rebase-on-me $new &&\n \t\ttest $flag = 1\n \t'\n+\n+\ttest_expect_success \"rebase $args fast-forward branch checkout runs post-checkout hook\" '\n+\t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n+\t\tgit update-ref refs/heads/rebase-fast-forward three &&\n+\t\tgit checkout two  &&\n+\t\trm -f .git/post-checkout.args &&\n+\t\tgit rebase $args HEAD rebase-fast-forward  &&\n+\t\tread old new flag <.git/post-checkout.args &&\n+\t\ttest_cmp_rev two $old &&\n+\t\ttest_cmp_rev three $new &&\n+\t\ttest $flag = 1\n+\t'\n }\n \n test_rebase --apply &&\n-- \ngitgitgadget\n\n"},{"id":"446946","messageId":"f4b925508e77573bc4c4783f82c1a4385a68fcde.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 04/14] rebase: do not remove untracked files on checkout","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:39Z","receivedAt":"2022-01-26T13:05:58Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf \"git rebase [--apply|--merge] <upstream> <branch>\" detects that\n<upstream> is an ancestor of <branch> then it will fast-forward and\ncheckout <branch>. Normally a checkout or picking a commit during a\nrebase will refuse to overwrite untracked files, however rebase does\noverwrite untracked files when checking out <branch>.\n\nThe fix is to only set reset in `unpack_tree_opts` if flags contains\n`RESET_HEAD_HARD`. t5403 may seem like an odd home for the new test\nbut it will be extended in the next commit to check that the\npost-checkout hook is not run when the checkout fails.\n\nThe test for `!detach_head` dates back to the\noriginal implementation of reset_head() in\nac7f467fef (\"builtin/rebase: support running \"git rebase <upstream>\"\",\n2018-08-07) and was correct until e65123a71d\n(\"builtin rebase: support `git rebase <upstream> <switch-to>`\",\n2018-09-04) started using reset_head() to checkout <switch-to> when\nfast-forwarding.\n\nNote that 480d3d6bf9 (\"Change unpack_trees' 'reset' flag into an\nenum\", 2021-09-27) also fixes this bug as it changes reset_head() to\nnever remove untracked files. I think this fix is still worthwhile as\nit makes it clear that the same settings are used for detached and\nnon-detached checkouts.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c                       |  2 +-\n t/t5403-post-checkout-hook.sh | 10 ++++++++++\n 2 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/reset.c b/reset.c\nindex 315fef91d33..3e7b9e2e131 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -59,7 +59,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tunpack_tree_opts.merge = 1;\n \tunpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */\n \tinit_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);\n-\tif (!detach_head)\n+\tif (reset_hard)\n \t\tunpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;\n \n \tif (repo_read_index_unmerged(r) < 0) {\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex 17ab518f268..fd2817b4068 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -85,6 +85,16 @@ test_rebase () {\n \t\ttest_cmp_rev three $new &&\n \t\ttest $flag = 1\n \t'\n+\n+\ttest_expect_success \"rebase $args checkout does not remove untracked files\" '\n+\t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\t\tgit update-ref refs/heads/rebase-fast-forward three &&\n+\t\tgit checkout two &&\n+\t\techo untracked >three.t &&\n+\t\ttest_when_finished \"rm three.t\" &&\n+\t\ttest_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&\n+\t\tgrep \"untracked working tree files would be overwritten by checkout\" err\n+'\n }\n \n test_rebase --apply &&\n-- \ngitgitgadget\n\n"},{"id":"446947","messageId":"4de5104d22d2ddd9c0cdf0e2173667c4796d3322.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 05/14] rebase --apply: don't run post-checkout hook if there is an error","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:40Z","receivedAt":"2022-01-26T13:06:03Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe hook should only be run if the worktree and refs were successfully\nupdated. This primarily affects \"rebase --apply\" but also \"rebase\n--merge\" when it fast-forwards.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c                       | 2 +-\n t/t5403-post-checkout-hook.sh | 6 +++++-\n 2 files changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/reset.c b/reset.c\nindex 3e7b9e2e131..3537de91f65 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -126,7 +126,7 @@ reset_head_refs:\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n \t}\n-\tif (run_hook)\n+\tif (!ret && run_hook)\n \t\trun_hook_le(NULL, \"post-checkout\",\n \t\t\t    oid_to_hex(head ? head : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\ndiff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh\nindex fd2817b4068..d1181816906 100755\n--- a/t/t5403-post-checkout-hook.sh\n+++ b/t/t5403-post-checkout-hook.sh\n@@ -88,12 +88,16 @@ test_rebase () {\n \n \ttest_expect_success \"rebase $args checkout does not remove untracked files\" '\n \t\ttest_when_finished \"test_might_fail git rebase --abort\" &&\n+\t\ttest_when_finished \"rm -f .git/post-checkout.args\" &&\n \t\tgit update-ref refs/heads/rebase-fast-forward three &&\n \t\tgit checkout two &&\n+\t\trm -f .git/post-checkout.args &&\n \t\techo untracked >three.t &&\n \t\ttest_when_finished \"rm three.t\" &&\n \t\ttest_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&\n-\t\tgrep \"untracked working tree files would be overwritten by checkout\" err\n+\t\tgrep \"untracked working tree files would be overwritten by checkout\" err &&\n+\t\ttest_path_is_missing .git/post-checkout.args\n+\n '\n }\n \n-- \ngitgitgadget\n\n"},{"id":"446948","messageId":"ff23498e93eab5cc1d9f040aedb45c54025d0b62.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 06/14] reset_head(): remove action parameter","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:41Z","receivedAt":"2022-01-26T13:06:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe only use of the action parameter is to setup the error messages\nfor unpack_trees(). All but two cases pass either \"checkout\" or\n\"reset\". The case that passes \"reset --hard\" would be better passing\n\"reset\" so that the error messages match the builtin reset command\nlike all the other callers that are doing a reset. The case that\npasses \"Fast-forwarded\" is only updating HEAD and so the parameter is\nunused in that case as it does not call unpack_trees(). The value to\npass to setup_unpack_trees_porcelain() can be determined by checking\nwhether flags contains RESET_HEAD_HARD without the caller having to\nspecify it.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 14 +++++++-------\n reset.c          |  5 +++--\n reset.h          |  2 +-\n sequencer.c      |  3 +--\n 4 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f5c37b7d4a5..2e5a535b54e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -583,7 +583,7 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n-\tret = reset_head(the_repository, NULL, \"\", opts->head_name,\n+\tret = reset_head(the_repository, NULL, opts->head_name,\n \t\t\t RESET_HEAD_REFS_ONLY,\n \t\t\t orig_head_reflog.buf, head_reflog.buf,\n \t\t\t DEFAULT_REFLOG_ACTION);\n@@ -674,7 +674,7 @@ static int run_am(struct rebase_options *opts)\n \t\tfree(rebased_patches);\n \t\tstrvec_clear(&am.args);\n \n-\t\treset_head(the_repository, &opts->orig_head, \"checkout\",\n+\t\treset_head(the_repository, &opts->orig_head,\n \t\t\t   opts->head_name, 0,\n \t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n@@ -820,7 +820,7 @@ static int checkout_up_to_date(struct rebase_options *options)\n \tstrbuf_addf(&buf, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n \t\t    options->switch_to);\n-\tif (reset_head(the_repository, &options->orig_head, \"checkout\",\n+\tif (reset_head(the_repository, &options->orig_head,\n \t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n@@ -1272,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\trerere_clear(the_repository, &merge_rr);\n \t\tstring_list_clear(&merge_rr, 1);\n \n-\t\tif (reset_head(the_repository, NULL, \"reset\", NULL, RESET_HEAD_HARD,\n+\t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n \t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n@@ -1290,7 +1290,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\tif (read_basic_state(&options))\n \t\t\texit(1);\n-\t\tif (reset_head(the_repository, &options.orig_head, \"reset\",\n+\t\tif (reset_head(the_repository, &options.orig_head,\n \t\t\t       options.head_name, RESET_HEAD_HARD,\n \t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n \t\t\tdie(_(\"could not move back to %s\"),\n@@ -1759,7 +1759,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n-\tif (reset_head(the_repository, &options.onto->object.oid, \"checkout\", NULL,\n+\tif (reset_head(the_repository, &options.onto->object.oid, NULL,\n \t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf, DEFAULT_REFLOG_ACTION))\n@@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&msg, \"rebase finished: %s onto %s\",\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n-\t\treset_head(the_repository, NULL, \"Fast-forwarded\", options.head_name,\n+\t\treset_head(the_repository, NULL, options.head_name,\n \t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf,\n \t\t\t   DEFAULT_REFLOG_ACTION);\n \t\tstrbuf_release(&msg);\ndiff --git a/reset.c b/reset.c\nindex 3537de91f65..7841b2b2a02 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,7 +8,7 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n-int reset_head(struct repository *r, struct object_id *oid, const char *action,\n+int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action)\n@@ -23,7 +23,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n \tstruct tree *tree;\n-\tconst char *reflog_action;\n+\tconst char *action, *reflog_action;\n \tstruct strbuf msg = STRBUF_INIT;\n \tsize_t prefix_len;\n \tstruct object_id *old_orig = NULL, oid_old_orig;\n@@ -50,6 +50,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \tif (refs_only)\n \t\tgoto reset_head_refs;\n \n+\taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n \tunpack_tree_opts.head_idx = 1;\n \tunpack_tree_opts.src_index = r->index;\ndiff --git a/reset.h b/reset.h\nindex 12f83c78e28..2daec804259 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -12,7 +12,7 @@\n #define RESET_HEAD_REFS_ONLY (1<<3)\n #define RESET_ORIG_HEAD (1<<4)\n \n-int reset_head(struct repository *r, struct object_id *oid, const char *action,\n+int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action);\ndiff --git a/sequencer.c b/sequencer.c\nindex ea96837cde3..3d3c3fbe305 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4131,8 +4131,7 @@ void create_autostash(struct repository *r, const char *path,\n \t\t\t    path);\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n-\t\tif (reset_head(r, NULL, \"reset --hard\",\n-\t\t\t       NULL, RESET_HEAD_HARD, NULL, NULL,\n+\t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n \t\t\t       default_reflog_action) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \n-- \ngitgitgadget\n\n"},{"id":"446949","messageId":"688ebc45bf7410dea4c3454f98f69fb2212938e5.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 07/14] reset_head(): factor out ref updates","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:42Z","receivedAt":"2022-01-26T13:06:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIn the next commit we will stop trying to update HEAD when we are\nremoving uncommitted changes from the working tree. Move the code that\nupdates the refs to its own function in preparation for that.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n reset.c | 110 +++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 62 insertions(+), 48 deletions(-)\n\ndiff --git a/reset.c b/reset.c\nindex 7841b2b2a02..56d6e2a06d9 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,25 +8,75 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n+static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n+\t\t       const struct object_id *head, const char *reflog_head,\n+\t\t       const char *reflog_orig_head,\n+\t\t       const char *default_reflog_action, unsigned flags)\n+{\n+\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n+\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tstruct object_id *old_orig = NULL, oid_old_orig;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *reflog_action;\n+\tsize_t prefix_len;\n+\tint ret;\n+\n+\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n+\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n+\tprefix_len = msg.len;\n+\n+\tif (update_orig_head) {\n+\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n+\t\t\told_orig = &oid_old_orig;\n+\t\tif (head) {\n+\t\t\tif (!reflog_orig_head) {\n+\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n+\t\t\t\treflog_orig_head = msg.buf;\n+\t\t\t}\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n+\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\t} else if (old_orig)\n+\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n+\t}\n+\n+\tif (!reflog_head) {\n+\t\tstrbuf_setlen(&msg, prefix_len);\n+\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n+\t\treflog_head = msg.buf;\n+\t}\n+\tif (!switch_to_branch)\n+\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n+\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n+\telse {\n+\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n+\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tif (!ret)\n+\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n+\t\t\t\t\t    reflog_head);\n+\t}\n+\tif (!ret && run_hook)\n+\t\trun_hook_le(NULL, \"post-checkout\",\n+\t\t\t    oid_to_hex(head ? head : null_oid()),\n+\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tstrbuf_release(&msg);\n+\treturn ret;\n+}\n+\n int reset_head(struct repository *r, struct object_id *oid,\n \t       const char *switch_to_branch, unsigned flags,\n \t       const char *reflog_orig_head, const char *reflog_head,\n \t       const char *default_reflog_action)\n {\n-\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n-\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n \tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct unpack_trees_options unpack_tree_opts = { 0 };\n \tstruct tree *tree;\n-\tconst char *action, *reflog_action;\n-\tstruct strbuf msg = STRBUF_INIT;\n-\tsize_t prefix_len;\n-\tstruct object_id *old_orig = NULL, oid_old_orig;\n+\tconst char *action;\n \tint ret = 0, nr = 0;\n \n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n@@ -48,7 +98,9 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\toid = &head_oid;\n \n \tif (refs_only)\n-\t\tgoto reset_head_refs;\n+\t\treturn update_refs(oid, switch_to_branch, head, reflog_head,\n+\t\t\t\t   reflog_orig_head, default_reflog_action,\n+\t\t\t\t   flags);\n \n \taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n@@ -92,48 +144,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\tgoto leave_reset_head;\n \t}\n \n-reset_head_refs:\n-\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n-\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n-\tprefix_len = msg.len;\n-\n-\tif (update_orig_head) {\n-\t\tif (!get_oid(\"ORIG_HEAD\", &oid_old_orig))\n-\t\t\told_orig = &oid_old_orig;\n-\t\tif (head) {\n-\t\t\tif (!reflog_orig_head) {\n-\t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n-\t\t\t\treflog_orig_head = msg.buf;\n-\t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n-\t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n-\t\t} else if (old_orig)\n-\t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\n-\t}\n-\n-\tif (!reflog_head) {\n-\t\tstrbuf_setlen(&msg, prefix_len);\n-\t\tstrbuf_addstr(&msg, \"updating HEAD\");\n-\t\treflog_head = msg.buf;\n-\t}\n-\tif (!switch_to_branch)\n-\t\tret = update_ref(reflog_head, \"HEAD\", oid, head,\n-\t\t\t\t detach_head ? REF_NO_DEREF : 0,\n-\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n-\telse {\n-\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n-\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n-\t\tif (!ret)\n-\t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n-\t\t\t\t\t    reflog_head);\n-\t}\n-\tif (!ret && run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n-\t\t\t    oid_to_hex(head ? head : null_oid()),\n-\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n+\t\t\t  reflog_orig_head, default_reflog_action, flags);\n \n leave_reset_head:\n-\tstrbuf_release(&msg);\n \trollback_lock_file(&lock);\n \tclear_unpack_trees_porcelain(&unpack_tree_opts);\n \twhile (nr)\n-- \ngitgitgadget\n\n"},{"id":"446950","messageId":"a5cc7eaa9256dbb8bc5722a4324bf20b8f08bd5c.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 08/14] reset_head(): make default_reflog_action optional","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:43Z","receivedAt":"2022-01-26T13:06:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis parameter is only needed when a ref is going to be updated and\nthe caller does not pass an explicit reflog message. Callers that are\nonly discarding uncommitted changes in the working tree such as such\nas \"rebase --skip\" or create_autostash() do not update any refs so\nshould not have to worry about passing this parameter.\n\nThis change is not intended to have any user visible changes. The\npointer comparison between `oid` and `&head_oid` checks that the\ncaller did not pass an oid to be checked out. As no callers pass\nRESET_HEAD_RUN_POST_CHECKOUT_HOOK without passing an oid there are\nno changes to when the post-checkout hook is run. As update_ref() only\nupdates the ref if the oid passed to it differs from the current ref\nthere are no changes to when HEAD is updated.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 10 ++++------\n reset.c          | 16 ++++++++++++----\n 2 files changed, 16 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 2e5a535b54e..82be965915c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -585,8 +585,7 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name);\n \tret = reset_head(the_repository, NULL, opts->head_name,\n \t\t\t RESET_HEAD_REFS_ONLY,\n-\t\t\t orig_head_reflog.buf, head_reflog.buf,\n-\t\t\t DEFAULT_REFLOG_ACTION);\n+\t\t\t orig_head_reflog.buf, head_reflog.buf, NULL);\n \n \tstrbuf_release(&orig_head_reflog);\n \tstrbuf_release(&head_reflog);\n@@ -822,7 +821,7 @@ static int checkout_up_to_date(struct rebase_options *options)\n \t\t    options->switch_to);\n \tif (reset_head(the_repository, &options->orig_head,\n \t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0)\n+\t\t       NULL, buf.buf, NULL) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n \tstrbuf_release(&buf);\n \n@@ -1273,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstring_list_clear(&merge_rr, 1);\n \n \t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n+\t\t\t       NULL, NULL, NULL) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n \t\tif (read_basic_state(&options))\n@@ -1778,8 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n \t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf,\n-\t\t\t   DEFAULT_REFLOG_ACTION);\n+\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\ndiff --git a/reset.c b/reset.c\nindex 56d6e2a06d9..4a92e4bc30b 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -22,8 +22,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch\n \tsize_t prefix_len;\n \tint ret;\n \n-\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n-\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n+\tif ((update_orig_head && !reflog_orig_head) || !reflog_head) {\n+\t\tif (!default_reflog_action)\n+\t\t\tBUG(\"default_reflog_action must be given when reflog messages are omitted\");\n+\t\treflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n+\t\tstrbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action :\n+\t\t\t\t\t\t\t  default_reflog_action);\n+\t}\n \tprefix_len = msg.len;\n \n \tif (update_orig_head) {\n@@ -71,6 +76,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n {\n \tunsigned reset_hard = flags & RESET_HEAD_HARD;\n \tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n+\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n \tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n@@ -144,8 +150,10 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\tgoto leave_reset_head;\n \t}\n \n-\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n-\t\t\t  reflog_orig_head, default_reflog_action, flags);\n+\tif (oid != &head_oid || update_orig_head || switch_to_branch)\n+\t\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n+\t\t\t\t  reflog_orig_head, default_reflog_action,\n+\t\t\t\t  flags);\n \n leave_reset_head:\n \trollback_lock_file(&lock);\n-- \ngitgitgadget\n\n"},{"id":"446951","messageId":"dd3a22384d23e670bea0abeab6c32c463a542a94.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 09/14] create_autostash(): remove unneeded parameter","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:44Z","receivedAt":"2022-01-26T13:06:13Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThe default_reflog parameter of create_autostash() is passed to\nreset_head(). However as creating a stash does not involve updating\nany refs the parameter is not used by reset_head(). Removing the\nparameter from create_autostash() simplifies the callers.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/merge.c  | 6 ++----\n builtin/rebase.c | 8 ++++----\n sequencer.c      | 5 ++---\n sequencer.h      | 3 +--\n 4 files changed, 9 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ea3112e0c0b..cb0e4e22258 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1565,8 +1565,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \t\tif (autostash)\n \t\t\tcreate_autostash(the_repository,\n-\t\t\t\t\t git_path_merge_autostash(the_repository),\n-\t\t\t\t\t \"merge\");\n+\t\t\t\t\t git_path_merge_autostash(the_repository));\n \t\tif (checkout_fast_forward(the_repository,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n@@ -1637,8 +1636,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\n-\t\t\t\t git_path_merge_autostash(the_repository),\n-\t\t\t\t \"merge\");\n+\t\t\t\t git_path_merge_autostash(the_repository));\n \n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 82be965915c..3d78b5c8bef 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1657,10 +1657,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (repo_read_index(the_repository) < 0)\n \t\tdie(_(\"could not read index\"));\n \n-\tif (options.autostash) {\n-\t\tcreate_autostash(the_repository, state_dir_path(\"autostash\", &options),\n-\t\t\t\t DEFAULT_REFLOG_ACTION);\n-\t}\n+\tif (options.autostash)\n+\t\tcreate_autostash(the_repository,\n+\t\t\t\t state_dir_path(\"autostash\", &options));\n+\n \n \tif (require_clean_work_tree(the_repository, \"rebase\",\n \t\t\t\t    _(\"Please commit or stash them.\"), 1, 1)) {\ndiff --git a/sequencer.c b/sequencer.c\nindex 3d3c3fbe305..78d203dec47 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4094,8 +4094,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \treturn -1;\n }\n \n-void create_autostash(struct repository *r, const char *path,\n-\t\t      const char *default_reflog_action)\n+void create_autostash(struct repository *r, const char *path)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4132,7 +4131,7 @@ void create_autostash(struct repository *r, const char *path,\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n-\t\t\t       default_reflog_action) < 0)\n+\t\t\t       NULL) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \n \t\tif (discard_index(r->index) < 0 ||\ndiff --git a/sequencer.h b/sequencer.h\nindex 05a7d2ba6b3..da64473636b 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -197,8 +197,7 @@ void commit_post_rewrite(struct repository *r,\n \t\t\t const struct commit *current_head,\n \t\t\t const struct object_id *new_head);\n \n-void create_autostash(struct repository *r, const char *path,\n-\t\t      const char *default_reflog_action);\n+void create_autostash(struct repository *r, const char *path);\n int save_autostash(const char *path);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n-- \ngitgitgadget\n\n"},{"id":"446952","messageId":"ad7c646798780bfa2fb33bd40b1196802b8a3d27.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 10/14] rebase: cleanup reset_head() calls","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:45Z","receivedAt":"2022-01-26T13:06:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf ORIG_HEAD is not set by passing RESET_ORIG_HEAD then there is no\nneed to pass anything for reflog_orig_head. In addition to the callers\nfixed in this commit move_to_original_branch() also passes\nreflog_orig_head without setting ORIG_HEAD. That caller is mistakenly\npassing the message it wants to put in the branch reflog which is not\ncurrently possible so we delay fixing that caller until we can pass\nthe message as the branch reflog.\n\nA later commit will make it a BUG() to pass reflog_orig_head without\nRESET_ORIG_HEAD, that changes cannot be done here as it needs to wait\nfor move_to_original_branch() to be fixed first.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 3d78b5c8bef..fdd822c470f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -675,7 +675,7 @@ static int run_am(struct rebase_options *opts)\n \n \t\treset_head(the_repository, &opts->orig_head,\n \t\t\t   opts->head_name, 0,\n-\t\t\t   \"HEAD\", NULL, DEFAULT_REFLOG_ACTION);\n+\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n \t\t\t\"patches to replay\\n\"\n \t\t\t\"these revisions:\\n\"\n@@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n \t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, \"HEAD\", msg.buf, NULL);\n+\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\n-- \ngitgitgadget\n\n"},{"id":"446953","messageId":"d170703e8334a6f082f18b9e5bde3be3a60deed7.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 11/14] reset_head(): take struct rebase_head_opts","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:46Z","receivedAt":"2022-01-26T13:06:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis function takes a confusingly large number of parameters which\nmakes it difficult to remember which order to pass them in. The\nfollowing commits will add a couple more parameters which makes the\nproblem worse. To address this change the function to take a struct of\noptions. Using a struct means that it is no longer necessary to\nremember which order to pass the parameters in and anyone reading the\ncode can easily see which value is passed to each parameter.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c | 57 ++++++++++++++++++++++++++++++------------------\n reset.c          | 38 +++++++++++++++-----------------\n reset.h          | 40 +++++++++++++++++++++++++++++----\n sequencer.c      |  5 ++---\n 4 files changed, 92 insertions(+), 48 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex fdd822c470f..ecc368dd4f4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -571,6 +571,7 @@ static int finish_rebase(struct rebase_options *opts)\n static int move_to_original_branch(struct rebase_options *opts)\n {\n \tstruct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n+\tstruct reset_head_opts ropts = { 0 };\n \tint ret;\n \n \tif (!opts->head_name)\n@@ -583,9 +584,11 @@ static int move_to_original_branch(struct rebase_options *opts)\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n-\tret = reset_head(the_repository, NULL, opts->head_name,\n-\t\t\t RESET_HEAD_REFS_ONLY,\n-\t\t\t orig_head_reflog.buf, head_reflog.buf, NULL);\n+\tropts.branch = opts->head_name;\n+\tropts.flags = RESET_HEAD_REFS_ONLY;\n+\tropts.orig_head_msg = orig_head_reflog.buf;\n+\tropts.head_msg = head_reflog.buf;\n+\tret = reset_head(the_repository, &ropts);\n \n \tstrbuf_release(&orig_head_reflog);\n \tstrbuf_release(&head_reflog);\n@@ -669,13 +672,15 @@ static int run_am(struct rebase_options *opts)\n \n \tstatus = run_command(&format_patch);\n \tif (status) {\n+\t\tstruct reset_head_opts ropts = { 0 };\n \t\tunlink(rebased_patches);\n \t\tfree(rebased_patches);\n \t\tstrvec_clear(&am.args);\n \n-\t\treset_head(the_repository, &opts->orig_head,\n-\t\t\t   opts->head_name, 0,\n-\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n+\t\tropts.oid = &opts->orig_head;\n+\t\tropts.branch = opts->head_name;\n+\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\t\treset_head(the_repository, &ropts);\n \t\terror(_(\"\\ngit encountered an error while preparing the \"\n \t\t\t\"patches to replay\\n\"\n \t\t\t\"these revisions:\\n\"\n@@ -814,14 +819,17 @@ static int rebase_config(const char *var, const char *value, void *data)\n static int checkout_up_to_date(struct rebase_options *options)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reset_head_opts ropts = { 0 };\n \tint ret = 0;\n \n \tstrbuf_addf(&buf, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n \t\t    options->switch_to);\n-\tif (reset_head(the_repository, &options->orig_head,\n-\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, buf.buf, NULL) < 0)\n+\tropts.oid = &options->orig_head;\n+\tropts.branch = options->head_name;\n+\tropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tropts.head_msg = buf.buf;\n+\tif (reset_head(the_repository, &ropts) < 0)\n \t\tret = error(_(\"could not switch to %s\"), options->switch_to);\n \tstrbuf_release(&buf);\n \n@@ -1033,6 +1041,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tint reschedule_failed_exec = -1;\n \tint allow_preemptive_ff = 1;\n \tint preserve_merges_selected = 0;\n+\tstruct reset_head_opts ropts = { 0 };\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1270,9 +1279,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\trerere_clear(the_repository, &merge_rr);\n \t\tstring_list_clear(&merge_rr, 1);\n-\n-\t\tif (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, NULL) < 0)\n+\t\tropts.flags = RESET_HEAD_HARD;\n+\t\tif (reset_head(the_repository, &ropts) < 0)\n \t\t\tdie(_(\"could not discard worktree changes\"));\n \t\tremove_branch_state(the_repository, 0);\n \t\tif (read_basic_state(&options))\n@@ -1289,9 +1297,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t\tif (read_basic_state(&options))\n \t\t\texit(1);\n-\t\tif (reset_head(the_repository, &options.orig_head,\n-\t\t\t       options.head_name, RESET_HEAD_HARD,\n-\t\t\t       NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)\n+\t\tropts.oid = &options.orig_head;\n+\t\tropts.branch = options.head_name;\n+\t\tropts.flags = RESET_HEAD_HARD;\n+\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\t\tif (reset_head(the_repository, &ropts) < 0)\n \t\t\tdie(_(\"could not move back to %s\"),\n \t\t\t    oid_to_hex(&options.orig_head));\n \t\tremove_branch_state(the_repository, 0);\n@@ -1758,10 +1768,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n-\tif (reset_head(the_repository, &options.onto->object.oid, NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n-\t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n-\t\t       NULL, msg.buf, DEFAULT_REFLOG_ACTION))\n+\tropts.oid = &options.onto->object.oid;\n+\tropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n+\t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tropts.head_msg = msg.buf;\n+\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n+\tif (reset_head(the_repository, &ropts))\n \t\tdie(_(\"Could not detach HEAD\"));\n \tstrbuf_release(&msg);\n \n@@ -1776,8 +1788,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&msg, \"rebase finished: %s onto %s\",\n \t\t\toptions.head_name ? options.head_name : \"detached HEAD\",\n \t\t\toid_to_hex(&options.onto->object.oid));\n-\t\treset_head(the_repository, NULL, options.head_name,\n-\t\t\t   RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL);\n+\t\tmemset(&ropts, 0, sizeof(ropts));\n+\t\tropts.branch = options.head_name;\n+\t\tropts.flags = RESET_HEAD_REFS_ONLY;\n+\t\tropts.head_msg = msg.buf;\n+\t\treset_head(the_repository, &ropts);\n \t\tstrbuf_release(&msg);\n \t\tret = finish_rebase(&options);\n \t\tgoto cleanup;\ndiff --git a/reset.c b/reset.c\nindex 4a92e4bc30b..78145d5c456 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -8,14 +8,17 @@\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n \n-static int update_refs(const struct object_id *oid, const char *switch_to_branch,\n-\t\t       const struct object_id *head, const char *reflog_head,\n-\t\t       const char *reflog_orig_head,\n-\t\t       const char *default_reflog_action, unsigned flags)\n+static int update_refs(const struct reset_head_opts *opts,\n+\t\t       const struct object_id *oid,\n+\t\t       const struct object_id *head)\n {\n-\tunsigned detach_head = flags & RESET_HEAD_DETACH;\n-\tunsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tunsigned detach_head = opts->flags & RESET_HEAD_DETACH;\n+\tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n+\tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n+\tconst char *switch_to_branch = opts->branch;\n+\tconst char *reflog_head = opts->head_msg;\n+\tconst char *reflog_orig_head = opts->orig_head_msg;\n+\tconst char *default_reflog_action = opts->default_reflog_action;\n \tstruct object_id *old_orig = NULL, oid_old_orig;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *reflog_action;\n@@ -69,14 +72,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch\n \treturn ret;\n }\n \n-int reset_head(struct repository *r, struct object_id *oid,\n-\t       const char *switch_to_branch, unsigned flags,\n-\t       const char *reflog_orig_head, const char *reflog_head,\n-\t       const char *default_reflog_action)\n+int reset_head(struct repository *r, const struct reset_head_opts *opts)\n {\n-\tunsigned reset_hard = flags & RESET_HEAD_HARD;\n-\tunsigned refs_only = flags & RESET_HEAD_REFS_ONLY;\n-\tunsigned update_orig_head = flags & RESET_ORIG_HEAD;\n+\tconst struct object_id *oid = opts->oid;\n+\tconst char *switch_to_branch = opts->branch;\n+\tunsigned reset_hard = opts->flags & RESET_HEAD_HARD;\n+\tunsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;\n+\tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n \tstruct object_id *head = NULL, head_oid;\n \tstruct tree_desc desc[2] = { { NULL }, { NULL } };\n \tstruct lock_file lock = LOCK_INIT;\n@@ -104,9 +106,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t\toid = &head_oid;\n \n \tif (refs_only)\n-\t\treturn update_refs(oid, switch_to_branch, head, reflog_head,\n-\t\t\t\t   reflog_orig_head, default_reflog_action,\n-\t\t\t\t   flags);\n+\t\treturn update_refs(opts, oid, head);\n \n \taction = reset_hard ? \"reset\" : \"checkout\";\n \tsetup_unpack_trees_porcelain(&unpack_tree_opts, action);\n@@ -151,9 +151,7 @@ int reset_head(struct repository *r, struct object_id *oid,\n \t}\n \n \tif (oid != &head_oid || update_orig_head || switch_to_branch)\n-\t\tret = update_refs(oid, switch_to_branch, head, reflog_head,\n-\t\t\t\t  reflog_orig_head, default_reflog_action,\n-\t\t\t\t  flags);\n+\t\tret = update_refs(opts, oid, head);\n \n leave_reset_head:\n \trollback_lock_file(&lock);\ndiff --git a/reset.h b/reset.h\nindex 2daec804259..a205be2fb85 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -6,15 +6,47 @@\n \n #define GIT_REFLOG_ACTION_ENVIRONMENT \"GIT_REFLOG_ACTION\"\n \n+/* Request a detached checkout */\n #define RESET_HEAD_DETACH (1<<0)\n+/* Request a reset rather than a checkout */\n #define RESET_HEAD_HARD (1<<1)\n+/* Run the post-checkout hook */\n #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)\n+/* Only update refs, do not touch the worktree */\n #define RESET_HEAD_REFS_ONLY (1<<3)\n+/* Update ORIG_HEAD as well as HEAD */\n #define RESET_ORIG_HEAD (1<<4)\n \n-int reset_head(struct repository *r, struct object_id *oid,\n-\t       const char *switch_to_branch, unsigned flags,\n-\t       const char *reflog_orig_head, const char *reflog_head,\n-\t       const char *default_reflog_action);\n+struct reset_head_opts {\n+\t/*\n+\t * The commit to checkout/reset to. Defaults to HEAD.\n+\t */\n+\tconst struct object_id *oid;\n+\t/*\n+\t * Optional branch to switch to.\n+\t */\n+\tconst char *branch;\n+\t/*\n+\t * Flags defined above.\n+\t */\n+\tunsigned flags;\n+\t/*\n+\t * Optional reflog message for HEAD, if this omitted but oid or branch\n+\t * are given then default_reflog_action must be given.\n+\t */\n+\tconst char *head_msg;\n+\t/*\n+\t * Optional reflog message for ORIG_HEAD, if this omitted and flags\n+\t * contains RESET_ORIG_HEAD then default_reflog_action must be given.\n+\t */\n+\tconst char *orig_head_msg;\n+\t/*\n+\t * Action to use in default reflog messages, only required if a ref is\n+\t * being updated and the reflog messages above are omitted.\n+\t */\n+\tconst char *default_reflog_action;\n+};\n+\n+int reset_head(struct repository *r, const struct reset_head_opts *opts);\n \n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex 78d203dec47..a62ea9d0e05 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4109,6 +4109,7 @@ void create_autostash(struct repository *r, const char *path)\n \tif (has_unstaged_changes(r, 1) ||\n \t    has_uncommitted_changes(r, 1)) {\n \t\tstruct child_process stash = CHILD_PROCESS_INIT;\n+\t\tstruct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n@@ -4130,10 +4131,8 @@ void create_autostash(struct repository *r, const char *path)\n \t\t\t    path);\n \t\twrite_file(path, \"%s\", oid_to_hex(&oid));\n \t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n-\t\tif (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n-\t\t\t       NULL) < 0)\n+\t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n-\n \t\tif (discard_index(r->index) < 0 ||\n \t\t\trepo_read_index(r) < 0)\n \t\t\tdie(_(\"could not read index\"));\n-- \ngitgitgadget\n\n"},{"id":"446954","messageId":"4973892561e4c527aa167ca190d59e7b3a489133.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 12/14] rebase --apply: fix reflog","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:47Z","receivedAt":"2022-01-26T13:06:20Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nmove_to_original_branch() passes the message intended for the branch\nreflog as `orig_head_msg`. Fix this by adding a `branch_msg` member to\nstruct reset_head_opts and add a regression test.  Note that these\nreflog messages do not respect GIT_REFLOG_ACTION. They are not alone\nin that and will be fixed in a future series.\n\nThe \"merge\" backend already has tests that check both the branch and\nHEAD reflogs.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c          |  8 ++++----\n reset.c                   | 12 ++++++++++--\n reset.h                   |  4 ++++\n t/t3406-rebase-message.sh | 23 +++++++++++++++++++++++\n 4 files changed, 41 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ecc368dd4f4..b55a9cff05d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -570,7 +570,7 @@ static int finish_rebase(struct rebase_options *opts)\n \n static int move_to_original_branch(struct rebase_options *opts)\n {\n-\tstruct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n+\tstruct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;\n \tstruct reset_head_opts ropts = { 0 };\n \tint ret;\n \n@@ -580,17 +580,17 @@ static int move_to_original_branch(struct rebase_options *opts)\n \tif (!opts->onto)\n \t\tBUG(\"move_to_original_branch without onto\");\n \n-\tstrbuf_addf(&orig_head_reflog, \"rebase finished: %s onto %s\",\n+\tstrbuf_addf(&branch_reflog, \"rebase finished: %s onto %s\",\n \t\t    opts->head_name, oid_to_hex(&opts->onto->object.oid));\n \tstrbuf_addf(&head_reflog, \"rebase finished: returning to %s\",\n \t\t    opts->head_name);\n \tropts.branch = opts->head_name;\n \tropts.flags = RESET_HEAD_REFS_ONLY;\n-\tropts.orig_head_msg = orig_head_reflog.buf;\n+\tropts.branch_msg = branch_reflog.buf;\n \tropts.head_msg = head_reflog.buf;\n \tret = reset_head(the_repository, &ropts);\n \n-\tstrbuf_release(&orig_head_reflog);\n+\tstrbuf_release(&branch_reflog);\n \tstrbuf_release(&head_reflog);\n \treturn ret;\n }\ndiff --git a/reset.c b/reset.c\nindex 78145d5c456..e02915c0f65 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -16,6 +16,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n \tconst char *switch_to_branch = opts->branch;\n+\tconst char *reflog_branch = opts->branch_msg;\n \tconst char *reflog_head = opts->head_msg;\n \tconst char *reflog_orig_head = opts->orig_head_msg;\n \tconst char *default_reflog_action = opts->default_reflog_action;\n@@ -58,8 +59,9 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\t\t\t detach_head ? REF_NO_DEREF : 0,\n \t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \telse {\n-\t\tret = update_ref(reflog_head, switch_to_branch, oid,\n-\t\t\t\t NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tret = update_ref(reflog_branch ? reflog_branch : reflog_head,\n+\t\t\t\t switch_to_branch, oid, NULL, 0,\n+\t\t\t\t UPDATE_REFS_MSG_ON_ERR);\n \t\tif (!ret)\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n@@ -90,6 +92,12 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)\n \tif (switch_to_branch && !starts_with(switch_to_branch, \"refs/\"))\n \t\tBUG(\"Not a fully qualified branch: '%s'\", switch_to_branch);\n \n+\tif (opts->orig_head_msg && !update_orig_head)\n+\t\tBUG(\"ORIG_HEAD reflog message given without updating ORIG_HEAD\");\n+\n+\tif (opts->branch_msg && !opts->branch)\n+\t\tBUG(\"branch reflog message given without a branch\");\n+\n \tif (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {\n \t\tret = -1;\n \t\tgoto leave_reset_head;\ndiff --git a/reset.h b/reset.h\nindex a205be2fb85..7ef7e43ea8c 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -30,6 +30,10 @@ struct reset_head_opts {\n \t * Flags defined above.\n \t */\n \tunsigned flags;\n+\t/*\n+\t * Optional reflog message for branch, defaults to head_msg.\n+\t */\n+\tconst char *branch_msg;\n \t/*\n \t * Optional reflog message for HEAD, if this omitted but oid or branch\n \t * are given then default_reflog_action must be given.\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 77a313f62eb..d17b450e811 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase --apply reflog' '\n+\tgit checkout -b reflog-apply start &&\n+\told_head_reflog=\"$(git log -g --format=%gs -1 HEAD)\" &&\n+\n+\tgit rebase --apply Y &&\n+\n+\tgit log -g --format=%gs -4 HEAD >actual &&\n+\tcat >expect <<-EOF &&\n+\trebase finished: returning to refs/heads/reflog-apply\n+\trebase: Z\n+\trebase: checkout Y\n+\t$old_head_reflog\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit log -g --format=%gs -2 reflog-apply >actual &&\n+\tcat >expect <<-EOF &&\n+\trebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)\n+\tbranch: Created from start\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'rebase -i onto unrelated history' '\n \tgit init unrelated &&\n \ttest_commit -C unrelated 1 &&\n-- \ngitgitgadget\n\n"},{"id":"446955","messageId":"0ef0e9781127df7485069c6e84786b08d4841422.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 13/14] rebase --apply: set ORIG_HEAD correctly","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:48Z","receivedAt":"2022-01-26T13:06:21Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAt the start of a rebase, ORIG_HEAD is updated to the tip of the\nbranch being rebased. Unfortunately reset_head() always uses the\ncurrent value of HEAD for this which is incorrect if the rebase is\nstarted with \"git rebase <upstream> <branch>\" as in that case\nORIG_HEAD should be updated to <branch>. This only affects the \"apply\"\nbackend as the \"merge\" backend does not yet use reset_head() for the\ninitial checkout. Fix this by passing in orig_head when calling\nreset_head() and add some regression tests.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/rebase.c           |  1 +\n reset.c                    |  4 +++-\n reset.h                    |  4 ++++\n t/t3418-rebase-continue.sh | 26 ++++++++++++++++++++++++++\n 4 files changed, 34 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b55a9cff05d..e942c300f8c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1769,6 +1769,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tropts.oid = &options.onto->object.oid;\n+\tropts.orig_head = &options.orig_head,\n \tropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tropts.head_msg = msg.buf;\ndiff --git a/reset.c b/reset.c\nindex e02915c0f65..448cb3fd785 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -15,6 +15,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \tunsigned detach_head = opts->flags & RESET_HEAD_DETACH;\n \tunsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n \tunsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;\n+\tconst struct object_id *orig_head = opts->orig_head;\n \tconst char *switch_to_branch = opts->branch;\n \tconst char *reflog_branch = opts->branch_msg;\n \tconst char *reflog_head = opts->head_msg;\n@@ -43,7 +44,8 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\t\t\tstrbuf_addstr(&msg, \"updating ORIG_HEAD\");\n \t\t\t\treflog_orig_head = msg.buf;\n \t\t\t}\n-\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\", head,\n+\t\t\tupdate_ref(reflog_orig_head, \"ORIG_HEAD\",\n+\t\t\t\t   orig_head ? orig_head : head,\n \t\t\t\t   old_orig, 0, UPDATE_REFS_MSG_ON_ERR);\n \t\t} else if (old_orig)\n \t\t\tdelete_ref(NULL, \"ORIG_HEAD\", old_orig, 0);\ndiff --git a/reset.h b/reset.h\nindex 7ef7e43ea8c..a28f81829d8 100644\n--- a/reset.h\n+++ b/reset.h\n@@ -22,6 +22,10 @@ struct reset_head_opts {\n \t * The commit to checkout/reset to. Defaults to HEAD.\n \t */\n \tconst struct object_id *oid;\n+\t/*\n+\t * Optional value to set ORIG_HEAD. Defaults to HEAD.\n+\t */\n+\tconst struct object_id *orig_head;\n \t/*\n \t * Optional branch to switch to.\n \t */\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 22eca73aa3e..130e2f9b553 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -308,4 +308,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n '\n \n+test_orig_head_helper () {\n+\ttest_when_finished 'git rebase --abort &&\n+\t\tgit checkout topic &&\n+\t\tgit reset --hard commit-new-file-F2-on-topic-branch' &&\n+\tgit update-ref -d ORIG_HEAD &&\n+\ttest_must_fail git rebase \"$@\" &&\n+\ttest_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch\n+}\n+\n+test_orig_head () {\n+\ttype=$1\n+\ttest_expect_success \"rebase $type sets ORIG_HEAD correctly\" '\n+\t\tgit checkout topic &&\n+\t\tgit reset --hard commit-new-file-F2-on-topic-branch &&\n+\t\ttest_orig_head_helper $type main\n+\t'\n+\n+\ttest_expect_success \"rebase $type <upstream> <branch> sets ORIG_HEAD correctly\" '\n+\t\tgit checkout main &&\n+\t\ttest_orig_head_helper $type main topic\n+\t'\n+}\n+\n+test_orig_head --apply\n+test_orig_head --merge\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"446956","messageId":"9b9560ef6767721d2e6095a1515dfa8a01ec7be1.1643202349.git.gitgitgadget@gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"[PATCH v3 14/14] rebase -m: don't fork git checkout","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-26T13:05:49Z","receivedAt":"2022-01-26T13:06:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nNow that reset_head() can handle the initial checkout of onto\ncorrectly use it in the \"merge\" backend instead of forking \"git\ncheckout\".  This opens the way for us to stop calling the\npost-checkout hook in the future. Not running \"git checkout\" means\nthat \"rebase -i/m\" no longer recurse submodules when checking out\n\"onto\" (thanks to Philippe Blain for pointing this out). As the rest\nof rebase does not know what to do with submodules this is probably a\ngood thing. When using merge-ort rebase ought be able to handle\nsubmodules correctly if it parsed the submodule config, such a change\nis left for a future patch series.\n\nThe \"apply\" based rebase has avoided forking git checkout\nsince ac7f467fef (\"builtin/rebase: support running \"git rebase\n<upstream>\"\", 2018-08-07). The code that handles the checkout was\nmoved into libgit by b309a97108 (\"reset: extract reset_head() from\nrebase\", 2020-04-07).\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 38 +++++++++++---------------------------\n 1 file changed, 11 insertions(+), 27 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex a62ea9d0e05..19082aa6c9b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4217,42 +4217,26 @@ int apply_autostash_oid(const char *stash_oid)\n \treturn apply_save_autostash_oid(stash_oid, 1);\n }\n \n-static int run_git_checkout(struct repository *r, struct replay_opts *opts,\n-\t\t\t    const char *commit, const char *action)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tint ret;\n-\n-\tcmd.git_cmd = 1;\n-\n-\tstrvec_push(&cmd.args, \"checkout\");\n-\tstrvec_push(&cmd.args, commit);\n-\tstrvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION \"=%s\", action);\n-\n-\tif (opts->verbose)\n-\t\tret = run_command(&cmd);\n-\telse\n-\t\tret = run_command_silent_on_success(&cmd);\n-\n-\tif (!ret)\n-\t\tdiscard_index(r->index);\n-\n-\treturn ret;\n-}\n-\n static int checkout_onto(struct repository *r, struct replay_opts *opts,\n \t\t\t const char *onto_name, const struct object_id *onto,\n \t\t\t const struct object_id *orig_head)\n {\n-\tconst char *action = reflog_message(opts, \"start\", \"checkout %s\", onto_name);\n-\n-\tif (run_git_checkout(r, opts, oid_to_hex(onto), action)) {\n+\tstruct reset_head_opts ropts = {\n+\t\t.oid = onto,\n+\t\t.orig_head = orig_head,\n+\t\t.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n+\t\t\t\tRESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n+\t\t.head_msg = reflog_message(opts, \"start\", \"checkout %s\",\n+\t\t\t\t\t   onto_name),\n+\t\t.default_reflog_action = \"rebase\"\n+\t};\n+\tif (reset_head(r, &ropts)) {\n \t\tapply_autostash(rebase_path_autostash());\n \t\tsequencer_remove_state(opts);\n \t\treturn error(_(\"could not detach HEAD\"));\n \t}\n \n-\treturn update_ref(NULL, \"ORIG_HEAD\", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n+\treturn 0;\n }\n \n static int stopped_at_head(struct repository *r)\n-- \ngitgitgadget\n"},{"id":"446958","messageId":"220126.86fspafwg8.gmgdl@evledraar.gmail.com","threadId":"56620","inReplyTo":"d170703e8334a6f082f18b9e5bde3be3a60deed7.1643202349.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 11/14] reset_head(): take struct rebase_head_opts","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-26T13:35:11Z","receivedAt":"2022-01-26T13:39:08Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Jan 26 2022, Phillip Wood via GitGitGadget wrote:\n\n> @@ -669,13 +672,15 @@ static int run_am(struct rebase_options *opts)\n>  \n>  \tstatus = run_command(&format_patch);\n>  \tif (status) {\n> +\t\tstruct reset_head_opts ropts = { 0 };\n>  \t\tunlink(rebased_patches);\n>  \t\tfree(rebased_patches);\n>  \t\tstrvec_clear(&am.args);\n>  \n> -\t\treset_head(the_repository, &opts->orig_head,\n> -\t\t\t   opts->head_name, 0,\n> -\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n> +\t\tropts.oid = &opts->orig_head;\n> +\t\tropts.branch = opts->head_name;\n> +\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n> +\t\treset_head(the_repository, &ropts);\n>  \t\terror(_(\"\\ngit encountered an error while preparing the \"\n>  \t\t\t\"patches to replay\\n\"\n>  \t\t\t\"these revisions:\\n\"\n\nWouldn't these and the rest be easier to read as:\n\n\tstruct reset_head_opts ropts = {\n\t\t.oid = &opts->orig_head,\n                .branch = opts->head_name,\n                .default_reflog_action = DEFAULT_REFLOG_ACTION,\n        };\n\n....\n\n\n> @@ -814,14 +819,17 @@ static int rebase_config(const char *var, const char *value, void *data)\n>  static int checkout_up_to_date(struct rebase_options *options)\n>  {\n>  \tstruct strbuf buf = STRBUF_INIT;\n> +\tstruct reset_head_opts ropts = { 0 };\n>  \tint ret = 0;\n>  \n>  \tstrbuf_addf(&buf, \"%s: checkout %s\",\n>  \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n>  \t\t    options->switch_to);\n> -\tif (reset_head(the_repository, &options->orig_head,\n> -\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n> -\t\t       NULL, buf.buf, NULL) < 0)\n> +\tropts.oid = &options->orig_head;\n> +\tropts.branch = options->head_name;\n> +\tropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n> +\tropts.head_msg = buf.buf;\n\n...and then for some of the ones like this \"ropts.head_msg = buf.buf\"\nassignment you just do that one immediately after the strbuf_addf() or\nwhatever modifies it.\n\nThat way it's clear what options we get from the function arguments and\ncan populate right away, and which ones we need to run some code in the\nfunction before we can update \"ropts\".\n\n[Ditto for the elided parts below]\n\n>  #define GIT_REFLOG_ACTION_ENVIRONMENT \"GIT_REFLOG_ACTION\"\n>  \n> +/* Request a detached checkout */\n>  #define RESET_HEAD_DETACH (1<<0)\n> +/* Request a reset rather than a checkout */\n>  #define RESET_HEAD_HARD (1<<1)\n> +/* Run the post-checkout hook */\n>  #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)\n> +/* Only update refs, do not touch the worktree */\n>  #define RESET_HEAD_REFS_ONLY (1<<3)\n> +/* Update ORIG_HEAD as well as HEAD */\n>  #define RESET_ORIG_HEAD (1<<4)\n>  \n> -int reset_head(struct repository *r, struct object_id *oid,\n> -\t       const char *switch_to_branch, unsigned flags,\n> -\t       const char *reflog_orig_head, const char *reflog_head,\n> -\t       const char *default_reflog_action);\n> +struct reset_head_opts {\n> +\t/*\n> +\t * The commit to checkout/reset to. Defaults to HEAD.\n> +\t */\n> +\tconst struct object_id *oid;\n> +\t/*\n> +\t * Optional branch to switch to.\n> +\t */\n> +\tconst char *branch;\n> +\t/*\n> +\t * Flags defined above.\n> +\t */\n> +\tunsigned flags;\n\nIt's nice to make these sort of things an enum type for the reasons\nexplained in 3f9ab7ccdea (parse-options.[ch]: consistently use \"enum\nparse_opt_flags\", 2021-10-08), i.e. gdb and the like will give you the\nlabels in the debugger.\n"},{"id":"446965","messageId":"7b56e91b-a7c9-b7cb-356c-e9615b9459fa@gmail.com","threadId":"56620","inReplyTo":"220126.86fspafwg8.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 11/14] reset_head(): take struct rebase_head_opts","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-01-26T14:52:17Z","receivedAt":"2022-01-26T14:52:45Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Ævar\n\nOn 26/01/2022 13:35, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Wed, Jan 26 2022, Phillip Wood via GitGitGadget wrote:\n> \n>> @@ -669,13 +672,15 @@ static int run_am(struct rebase_options *opts)\n>>   \n>>   \tstatus = run_command(&format_patch);\n>>   \tif (status) {\n>> +\t\tstruct reset_head_opts ropts = { 0 };\n>>   \t\tunlink(rebased_patches);\n>>   \t\tfree(rebased_patches);\n>>   \t\tstrvec_clear(&am.args);\n>>   \n>> -\t\treset_head(the_repository, &opts->orig_head,\n>> -\t\t\t   opts->head_name, 0,\n>> -\t\t\t   NULL, NULL, DEFAULT_REFLOG_ACTION);\n>> +\t\tropts.oid = &opts->orig_head;\n>> +\t\tropts.branch = opts->head_name;\n>> +\t\tropts.default_reflog_action = DEFAULT_REFLOG_ACTION;\n>> +\t\treset_head(the_repository, &ropts);\n>>   \t\terror(_(\"\\ngit encountered an error while preparing the \"\n>>   \t\t\t\"patches to replay\\n\"\n>>   \t\t\t\"these revisions:\\n\"\n> \n> Wouldn't these and the rest be easier to read as:\n> \n> \tstruct reset_head_opts ropts = {\n> \t\t.oid = &opts->orig_head,\n>                  .branch = opts->head_name,\n>                  .default_reflog_action = DEFAULT_REFLOG_ACTION,\n>          };\n\nI did start out doing something like that but changed to the current \nstyle as I felt it made it easier to convert the calls correctly and for \nreviewers to verify that the conversion is correct when the deletion of \nthe old function arguments is adjacent to the insertion of the new \nstruct assignments and the assignments are in the same order as the old \nfunction arguments.\n\n> ....\n> \n> \n>> @@ -814,14 +819,17 @@ static int rebase_config(const char *var, const char *value, void *data)\n>>   static int checkout_up_to_date(struct rebase_options *options)\n>>   {\n>>   \tstruct strbuf buf = STRBUF_INIT;\n>> +\tstruct reset_head_opts ropts = { 0 };\n>>   \tint ret = 0;\n>>   \n>>   \tstrbuf_addf(&buf, \"%s: checkout %s\",\n>>   \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),\n>>   \t\t    options->switch_to);\n>> -\tif (reset_head(the_repository, &options->orig_head,\n>> -\t\t       options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n>> -\t\t       NULL, buf.buf, NULL) < 0)\n>> +\tropts.oid = &options->orig_head;\n>> +\tropts.branch = options->head_name;\n>> +\tropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;\n>> +\tropts.head_msg = buf.buf;\n> \n> ...and then for some of the ones like this \"ropts.head_msg = buf.buf\"\n> assignment you just do that one immediately after the strbuf_addf() or\n> whatever modifies it.\n> \n> That way it's clear what options we get from the function arguments and\n> can populate right away, and which ones we need to run some code in the\n> function before we can update \"ropts\".\n\nI'm not immediately clear why that matters. My priority was to keep the \nassignments in the same order an the old function arguments to make the \nconversion and review easier.\n\n> [Ditto for the elided parts below]\n> \n>>   #define GIT_REFLOG_ACTION_ENVIRONMENT \"GIT_REFLOG_ACTION\"\n>>   \n>> +/* Request a detached checkout */\n>>   #define RESET_HEAD_DETACH (1<<0)\n>> +/* Request a reset rather than a checkout */\n>>   #define RESET_HEAD_HARD (1<<1)\n>> +/* Run the post-checkout hook */\n>>   #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)\n>> +/* Only update refs, do not touch the worktree */\n>>   #define RESET_HEAD_REFS_ONLY (1<<3)\n>> +/* Update ORIG_HEAD as well as HEAD */\n>>   #define RESET_ORIG_HEAD (1<<4)\n>>   \n>> -int reset_head(struct repository *r, struct object_id *oid,\n>> -\t       const char *switch_to_branch, unsigned flags,\n>> -\t       const char *reflog_orig_head, const char *reflog_head,\n>> -\t       const char *default_reflog_action);\n>> +struct reset_head_opts {\n>> +\t/*\n>> +\t * The commit to checkout/reset to. Defaults to HEAD.\n>> +\t */\n>> +\tconst struct object_id *oid;\n>> +\t/*\n>> +\t * Optional branch to switch to.\n>> +\t */\n>> +\tconst char *branch;\n>> +\t/*\n>> +\t * Flags defined above.\n>> +\t */\n>> +\tunsigned flags;\n> \n> It's nice to make these sort of things an enum type for the reasons\n> explained in 3f9ab7ccdea (parse-options.[ch]: consistently use \"enum\n> parse_opt_flags\", 2021-10-08), i.e. gdb and the like will give you the\n> labels in the debugger.\n\nYeah that's true but I'm not actually touching the flags here, just \nadding comments.\n\nBest Wishes\n\nPhillip\n"},{"id":"447102","messageId":"xmqqilu53wsb.fsf@gitster.g","threadId":"56620","inReplyTo":"aedee582-7f00-7e12-c378-35b0cbc6b565@gmail.com","subject":"Re: [PATCH v2 00/14] rebase: reset_head() related fixes and improvements","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-27T17:37:08Z","receivedAt":"2022-01-27T17:37:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> So the theme started out as \"convert 'rebase -i' to use reset_head()\n> and stop forking 'git checkout'\" which I thought would be one to two \n> patches. Then I started looking at the code and realized that\n> reset_head() needed some work before we could use it for 'rebase -i'\n> and that work ended up dominating the series. I've updated the cover\n> letter as you suggested.\n\nGreat.\n\nIt helped me summarizing the topic in the \"What's cooking\" report.\n\n Use an internal call to reset_head() helper function instead of\n spawning \"git checkout\" in \"rebase\", and update code paths that are\n involved in the change.\n\nPerhaps I'd rename the topic branch, but perhaps not.\n\nThanks.  \n"},{"id":"447432","messageId":"CABPp-BEr+UVFo27qwnYuTO4WtWyz4yzUM8d0_zuxa52_XX9zow@mail.gmail.com","threadId":"56620","inReplyTo":"pull.1049.v3.git.1643202349.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/14] rebase: reset_head() related fixes and improvements","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-01T17:03:35Z","receivedAt":"2022-02-01T17:03:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jan 26, 2022 at 5:05 AM Phillip Wood via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Thanks to Junio and Elijah for their comments on V2. There aren't too many\n> changes this time.\n>\n> This series started out with the aim of converting 'rebase -i' to use\n> reset_head() instead of forking 'git checkout'. As 'rebase --apply' already\n> uses reset_head() I assumed this would be straight forward. However it has\n> morphed into a series of fixes for reset_head() followed by the intended\n> conversion of 'rebase -i'.\n>\n> reset.c::reset_head() started its life at ac7f467f (builtin/rebase: support\n> running \"git rebase \", 2018-08-07) as a way to detach the HEAD to replay the\n> commits during \"git rebase\", but over time it learned to do many things,\n> like switching the tip of the branch to another commit, recording the old\n> value of HEAD in ORIG_HEAD while it does so, recording reflog entries for\n> both HEAD and for the branch.\n>\n> The API into the function got clunky and it is harder than necessary for the\n> callers to use the function correctly, which led to a handful of bugs that\n> are fixed by this series. The bugs include\n>\n>  * passing the wrong oid to the post-checkout hook\n>  * removing untracked files on checkout\n>  * running the post-checkout hook if the checkout fails\n>  * passing parameters to reset_head() that it does not use\n>  * incorrect reflog messages for 'rebase --apply'\n>  * sometimes setting ORIG_HEAD incorrectly at the start 'rebase --apply'\n>\n> Later steps of this series revamps the API so that it is harder to use it\n> incorrectly to prevent future bugs and finally convert 'rebase -i' to use\n> reset_head()\n>\n> Changes since V2:\n>\n>  * Updated cover letter as suggested by Junio\n>  * Patch 4 - fixed typos in the commit message spotted by Junio\n>  * Patch 9 - Moved later in the series to simplify the autostash canges as\n>    suggested by Junio. This used to be patch 7\n>  * Patch 10 - Added a comment to the commit message explaining why we cannot\n>    BUG() on an invalid parameter until a change is made in a later commit\n>  * Patch 13 - Reworded the first sentence commit message as suggest by\n>    Elijah.\n\nThis round looks good to me.  Thanks!\n\n> Cover letter for V2:\n>\n> Thanks for the comments on V1. I have tried to improve the commit messages\n> to explain better the motivation and implications of the changes in this\n> series and I have added some more tests. I have rebased onto v2.34.0 to\n> avoid some merges conflicts.\n>\n> Changes since V1:\n>\n>  * Patch 1 - unchanged.\n>  * Patches 2, 3 - these are new and fix an bug I noticed while adding a test\n>    to patch 4.\n>  * Patches 4, 5 - improved commit messages and added tests.\n>  * Patch 6 - reworded commit message.\n>  * Patch 7 - split out some changes that used to be in patch 9.\n>  * Patch 8 - in principle the same but the range-diff is noisy due to the\n>    addition of patch 3.\n>  * Patch 9 - reworded commit message.\n>  * Patch 10 - unchanged.\n>  * Patch 11 - reworded commit message and a couple of comments.\n>  * Patch 12 - minor changes to comments.\n>  * Patch 13 - cosmetic changes to commit message and tests.\n>  * Patch 14 - cosmetic changes to commit message.\n>\n> Cover letter for V1: Fix some issues with the implementation and use of\n> reset_head(). The last patch was previously posted as [1], I have updated\n> the commit message and rebased it onto the fixes in this series. There are a\n> couple of small conflicts merging this into seen, I think they should be\n> easy to resolve (in rebase.c take both sides in reset.c take the changed\n> lines from each side). These patches are based on pw/rebase-of-a-tag-fix\n>\n> [1]\n> https://lore.kernel.org/git/39ad40c9297531a2d42b7263a1d41b1ecbc23c0a.1631108472.git.gitgitgadget@gmail.com/\n>\n> Phillip Wood (14):\n>   rebase: factor out checkout for up to date branch\n>   t5403: refactor rebase post-checkout hook tests\n>   rebase: pass correct arguments to post-checkout hook\n>   rebase: do not remove untracked files on checkout\n>   rebase --apply: don't run post-checkout hook if there is an error\n>   reset_head(): remove action parameter\n>   reset_head(): factor out ref updates\n>   reset_head(): make default_reflog_action optional\n>   create_autostash(): remove unneeded parameter\n>   rebase: cleanup reset_head() calls\n>   reset_head(): take struct rebase_head_opts\n>   rebase --apply: fix reflog\n>   rebase --apply: set ORIG_HEAD correctly\n>   rebase -m: don't fork git checkout\n>\n>  builtin/merge.c               |   6 +-\n>  builtin/rebase.c              | 101 +++++++++++++----------\n>  reset.c                       | 149 ++++++++++++++++++++--------------\n>  reset.h                       |  48 ++++++++++-\n>  sequencer.c                   |  47 ++++-------\n>  sequencer.h                   |   3 +-\n>  t/t3406-rebase-message.sh     |  23 ++++++\n>  t/t3418-rebase-continue.sh    |  26 ++++++\n>  t/t5403-post-checkout-hook.sh |  67 +++++++++++----\n>  9 files changed, 312 insertions(+), 158 deletions(-)\n>\n>\n> base-commit: cd3e606211bb1cf8bc57f7d76bab98cc17a150bc\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1049%2Fphillipwood%2Fwip%2Frebase-reset-head-fixes-v3\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1049/phillipwood/wip/rebase-reset-head-fixes-v3\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1049\n>\n> Range-diff vs v2:\n>\n>   1:  0e84d00572e =  1:  0e84d00572e rebase: factor out checkout for up to date branch\n>   2:  a67a5a03b94 =  2:  a67a5a03b94 t5403: refactor rebase post-checkout hook tests\n>   3:  07867760e68 =  3:  07867760e68 rebase: pass correct arguments to post-checkout hook\n>   4:  2b499704c8f !  4:  f4b925508e7 rebase: do not remove untracked files on checkout\n>      @@ Commit message\n>           <upstream> is an ancestor of <branch> then it will fast-forward and\n>           checkout <branch>. Normally a checkout or picking a commit during a\n>           rebase will refuse to overwrite untracked files, however rebase does\n>      -    overwrite untracked files when checking <branch>.\n>      +    overwrite untracked files when checking out <branch>.\n>\n>           The fix is to only set reset in `unpack_tree_opts` if flags contains\n>           `RESET_HEAD_HARD`. t5403 may seem like an odd home for the new test\n>           but it will be extended in the next commit to check that the\n>           post-checkout hook is not run when the checkout fails.\n>\n>      -    The test for `!deatch_head` dates back to the\n>      +    The test for `!detach_head` dates back to the\n>           original implementation of reset_head() in\n>           ac7f467fef (\"builtin/rebase: support running \"git rebase <upstream>\"\",\n>           2018-08-07) and was correct until e65123a71d\n>   5:  04e7340a7e7 =  5:  4de5104d22d rebase --apply: don't run post-checkout hook if there is an error\n>   6:  32ffa98c1bc =  6:  ff23498e93e reset_head(): remove action parameter\n>   8:  29e06e7d36d =  7:  688ebc45bf7 reset_head(): factor out ref updates\n>   9:  9d00a218daf !  8:  a5cc7eaa925 reset_head(): make default_reflog_action optional\n>      @@ reset.c: int reset_head(struct repository *r, struct object_id *oid,\n>\n>        leave_reset_head:\n>         rollback_lock_file(&lock);\n>      -\n>      - ## sequencer.c ##\n>      -@@ sequencer.c: void create_autostash(struct repository *r, const char *path)\n>      -          write_file(path, \"%s\", oid_to_hex(&oid));\n>      -          printf(_(\"Created autostash: %s\\n\"), buf.buf);\n>      -          if (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n>      --                        \"\") < 0)\n>      -+                        NULL) < 0)\n>      -                  die(_(\"could not reset --hard\"));\n>      -\n>      -          if (discard_index(r->index) < 0 ||\n>   7:  341fe183c18 !  9:  dd3a22384d2 create_autostash(): remove unneeded parameter\n>      @@ sequencer.c: void create_autostash(struct repository *r, const char *path,\n>                 printf(_(\"Created autostash: %s\\n\"), buf.buf);\n>                 if (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL,\n>       -                        default_reflog_action) < 0)\n>      -+                        \"\") < 0)\n>      ++                        NULL) < 0)\n>                         die(_(\"could not reset --hard\"));\n>\n>                 if (discard_index(r->index) < 0 ||\n>  10:  5ea636009e7 ! 10:  ad7c6467987 rebase: cleanup reset_head() calls\n>      @@ Commit message\n>           currently possible so we delay fixing that caller until we can pass\n>           the message as the branch reflog.\n>\n>      +    A later commit will make it a BUG() to pass reflog_orig_head without\n>      +    RESET_ORIG_HEAD, that changes cannot be done here as it needs to wait\n>      +    for move_to_original_branch() to be fixed first.\n>      +\n>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n>        ## builtin/rebase.c ##\n>  11:  24b0566aba5 = 11:  d170703e833 reset_head(): take struct rebase_head_opts\n>  12:  dc5d11291e7 = 12:  4973892561e rebase --apply: fix reflog\n>  13:  45a5b5e9818 ! 13:  0ef0e978112 rebase --apply: set ORIG_HEAD correctly\n>      @@ Metadata\n>        ## Commit message ##\n>           rebase --apply: set ORIG_HEAD correctly\n>\n>      -    At the start of a rebase ORIG_HEAD is updated to tip of the branch\n>      -    being rebased. Unfortunately reset_head() always uses the current\n>      -    value of HEAD for this which is incorrect if the rebase is started\n>      -    with \"git rebase <upstream> <branch>\" as in that case ORIG_HEAD should\n>      -    be updated to <branch>. This only affects the \"apply\" backend as the\n>      -    \"merge\" backend does not yet use reset_head() for the initial\n>      -    checkout. Fix this by passing in orig_head when calling reset_head()\n>      -    and add some regression tests.\n>      +    At the start of a rebase, ORIG_HEAD is updated to the tip of the\n>      +    branch being rebased. Unfortunately reset_head() always uses the\n>      +    current value of HEAD for this which is incorrect if the rebase is\n>      +    started with \"git rebase <upstream> <branch>\" as in that case\n>      +    ORIG_HEAD should be updated to <branch>. This only affects the \"apply\"\n>      +    backend as the \"merge\" backend does not yet use reset_head() for the\n>      +    initial checkout. Fix this by passing in orig_head when calling\n>      +    reset_head() and add some regression tests.\n>\n>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n>  14:  3f64b9274b5 = 14:  9b9560ef676 rebase -m: don't fork git checkout\n>\n> --\n> gitgitgadget\n"}]}