{"thread":{"id":"57949","subject":"[PATCH 0/4] rebase: update branches in multi-part topic","startedAt":"2022-06-03T13:37:59Z","lastAt":"2022-07-21T20:05:06Z","messageCount":144,"participants":["Derrick Stolee via GitGitGadget","Junio C Hamano","Derrick Stolee","Taylor Blau","Jeff Hostetler","Phillip Wood","Elijah Newren","SZEDER Gábor"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"456576","messageId":"pull.1247.git.1654263472.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":null,"subject":"[PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-03T13:37:48Z","receivedAt":"2022-06-03T13:37:59Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This is a feature I've wanted for quite a while. When working on the sparse\nindex topic, I created a long RFC that actually broke into three topics for\nfull review upstream. These topics were sequential, so any feedback on an\nearlier one required updates to the later ones. I would work on the full\nfeature and use interactive rebase to update the full list of commits.\nHowever, I would need to update the branches pointing to those sub-topics.\n\nThis series adds a new --update-refs option to 'git rebase' (along with a\nrebase.updateRefs config option) that adds 'git update-ref' commands into\nthe TODO list. This is powered by the commit decoration machinery.\n\nAs an example, here is my in-progress bundle URI RFC split into subtopics as\nthey appear during the TODO list of a git rebase -i --update-refs:\n\npick 2d966282ff3 docs: document bundle URI standard\npick 31396e9171a remote-curl: add 'get' capability\npick 54c6ab70f67 bundle-uri: create basic file-copy logic\npick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\npick 6adaf842684 fetch: add --bundle-uri option\npick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\nexec git update-ref refs/heads/bundle-redo/fetch HEAD 6c5840ed77e1bc41c1fe6fb7c894ceede1b8d730\n\npick 1e3f6546632 clone: add --bundle-uri option\npick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\nexec git update-ref refs/heads/bundle-redo/clone HEAD 9e4a6fe9b68a8455b427c9ac8cdbff30c96653b4\n\npick 5451cb6599c bundle-uri: create bundle_list struct and helpers\npick 3029c3aca15 bundle-uri: create base key-value pair parsing\npick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\npick 92625a47673 bundle-uri: unit test \"key=value\" parsing\npick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\npick 9d6809a8d53 bundle-uri: parse bundle list in config format\npick 287a732b54c bundle-uri: fetch a list of bundles\nexec git update-ref refs/heads/bundle-redo/list HEAD 287a732b54c4d95e7f410b3b36ef90d8a19cd346\n\npick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\npick 520204dcd1c bundle-uri client: add minimal NOOP client\npick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\npick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\npick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\npick caf4599a81d bundle-uri: allow relative URLs in bundle lists\npick df255000b7e bundle-uri: download bundles from an advertised list\npick d71beabf199 clone: unbundle the advertised bundles\npick c9578391976 t5601: basic bundle URI tests\n# Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n\nexec git update-ref refs/heads/bundle-redo/advertise HEAD c9578391976ab9899c4e4f9b5fa2827650097305\n\n\nThe first two patches are helpers that are needed, but the full logic of the\n--update-refs option is introduced in patch 3. The config option is\navailable in patch 4.\n\nThanks, -Stolee\n\nDerrick Stolee (4):\n  log-tree: create for_each_decoration()\n  branch: add branch_checked_out() helper\n  rebase: add --update-refs option\n  rebase: add rebase.updateRefs config option\n\n Documentation/config/rebase.txt |   3 +\n Documentation/git-rebase.txt    |  11 ++++\n branch.c                        |  24 ++++---\n branch.h                        |   8 +++\n builtin/rebase.c                |  10 +++\n log-tree.c                      | 111 ++++++++++++++++++++++----------\n log-tree.h                      |   4 ++\n sequencer.c                     |  99 ++++++++++++++++++++++++++++\n sequencer.h                     |   1 +\n t/t3404-rebase-interactive.sh   |  34 ++++++++++\n 10 files changed, 262 insertions(+), 43 deletions(-)\n\n\nbase-commit: 2668e3608e47494f2f10ef2b6e69f08a84816bcb\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1247\n-- \ngitgitgadget\n"},{"id":"456577","messageId":"5f54766e1032ebf3a331516a6dd696b997bdfdd8.1654263472.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"[PATCH 2/4] branch: add branch_checked_out() helper","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-03T13:37:50Z","receivedAt":"2022-06-03T13:38:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe validate_new_branchname() method contains a check to see if a branch\nis checked out in any non-bare worktree. This is intended to prevent a\nforce push that will mess up an existing checkout. This helper is not\nsuitable to performing just that check, because the method will die()\nwhen the branch is checked out instead of returning an error code.\n\nExtract branch_checked_out() and use it within\nvalidate_new_branchname(). Another caller will be added in a coming\nchange.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n branch.c | 24 ++++++++++++++++--------\n branch.h |  8 ++++++++\n 2 files changed, 24 insertions(+), 8 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 2d6569b0c62..2e6419cdfa5 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -369,6 +369,19 @@ int validate_branchname(const char *name, struct strbuf *ref)\n \treturn ref_exists(ref->buf);\n }\n \n+int branch_checked_out(const char *refname, char **path)\n+{\n+\tstruct worktree **worktrees = get_worktrees();\n+\tconst struct worktree *wt = find_shared_symref(worktrees, \"HEAD\", refname);\n+\tint result = wt && !wt->is_bare;\n+\n+\tif (result && path)\n+\t\t*path = xstrdup(wt->path);\n+\n+\tfree_worktrees(worktrees);\n+\treturn result;\n+}\n+\n /*\n  * Check if a branch 'name' can be created as a new branch; die otherwise.\n  * 'force' can be used when it is OK for the named branch already exists.\n@@ -377,9 +390,7 @@ int validate_branchname(const char *name, struct strbuf *ref)\n  */\n int validate_new_branchname(const char *name, struct strbuf *ref, int force)\n {\n-\tstruct worktree **worktrees;\n-\tconst struct worktree *wt;\n-\n+\tchar *path;\n \tif (!validate_branchname(name, ref))\n \t\treturn 0;\n \n@@ -387,13 +398,10 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force)\n \t\tdie(_(\"a branch named '%s' already exists\"),\n \t\t    ref->buf + strlen(\"refs/heads/\"));\n \n-\tworktrees = get_worktrees();\n-\twt = find_shared_symref(worktrees, \"HEAD\", ref->buf);\n-\tif (wt && !wt->is_bare)\n+\tif (branch_checked_out(ref->buf, &path))\n \t\tdie(_(\"cannot force update the branch '%s' \"\n \t\t      \"checked out at '%s'\"),\n-\t\t    ref->buf + strlen(\"refs/heads/\"), wt->path);\n-\tfree_worktrees(worktrees);\n+\t\t    ref->buf + strlen(\"refs/heads/\"), path);\n \n \treturn 1;\n }\ndiff --git a/branch.h b/branch.h\nindex 560b6b96a8f..5ea93d217b1 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -101,6 +101,14 @@ void create_branches_recursively(struct repository *r, const char *name,\n \t\t\t\t const char *tracking_name, int force,\n \t\t\t\t int reflog, int quiet, enum branch_track track,\n \t\t\t\t int dry_run);\n+\n+/*\n+ * Returns true if the branch at 'refname' is checked out at any\n+ * non-bare worktree. The path of the worktree is stored in the\n+ * given 'path', if provided.\n+ */\n+int branch_checked_out(const char *refname, char **path);\n+\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\n-- \ngitgitgadget\n\n"},{"id":"456578","messageId":"4f9f34876413927d819313a70fcdefcad5b35689.1654263472.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"[PATCH 1/4] log-tree: create for_each_decoration()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-03T13:37:49Z","receivedAt":"2022-06-03T13:38:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nIt can be helpful to iterate through all the decorations on a commit\nwithout necessarily writing them to a stream. Implement\nfor_each_decoration() and reimplement format_decorations_extended() to\nuse that iterator.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n log-tree.c | 111 ++++++++++++++++++++++++++++++++++++-----------------\n log-tree.h |   4 ++\n 2 files changed, 80 insertions(+), 35 deletions(-)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex d0ac0a6327a..b15a7c9db22 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -282,6 +282,54 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio\n \t\tstrbuf_addstr(sb, decoration->name);\n }\n \n+struct format_decorations_context {\n+\tstruct strbuf *sb;\n+\tint use_color;\n+\tconst char *prefix;\n+\tconst char *separator;\n+\tconst char *suffix;\n+\tconst char *color_commit;\n+\tconst char *color_reset;\n+\tconst struct name_decoration *current_and_HEAD;\n+};\n+\n+static int append_decoration(const struct name_decoration *d,\n+\t\t\t     void *data)\n+{\n+\tstruct format_decorations_context *ctx = data;\n+\t/*\n+\t * When both current and HEAD are there, only\n+\t * show HEAD->current where HEAD would have\n+\t * appeared, skipping the entry for current.\n+\t */\n+\tif (d != ctx->current_and_HEAD) {\n+\t\tstrbuf_addstr(ctx->sb, ctx->color_commit);\n+\t\tstrbuf_addstr(ctx->sb, ctx->prefix);\n+\t\tstrbuf_addstr(ctx->sb, ctx->color_reset);\n+\t\tstrbuf_addstr(ctx->sb, decorate_get_color(ctx->use_color, d->type));\n+\t\tif (d->type == DECORATION_REF_TAG)\n+\t\t\tstrbuf_addstr(ctx->sb, \"tag: \");\n+\n+\t\tshow_name(ctx->sb, d);\n+\n+\t\tif (ctx->current_and_HEAD &&\n+\t\t    d->type == DECORATION_REF_HEAD) {\n+\t\t\tstrbuf_addstr(ctx->sb, \" -> \");\n+\t\t\tstrbuf_addstr(ctx->sb, ctx->color_reset);\n+\t\t\tstrbuf_addstr(ctx->sb,\n+\t\t\t\t      decorate_get_color(\n+\t\t\t\t\t\tctx->use_color,\n+\t\t\t\t\t\tctx->current_and_HEAD->type));\n+\t\t\tshow_name(ctx->sb, ctx->current_and_HEAD);\n+\t\t}\n+\t\tstrbuf_addstr(ctx->sb, ctx->color_reset);\n+\n+\t\tctx->prefix = ctx->separator;\n+\t}\n+\n+\treturn 0;\n+}\n+\n /*\n  * The caller makes sure there is no funny color before calling.\n  * format_decorations_extended makes sure the same after return.\n@@ -294,49 +342,42 @@ void format_decorations_extended(struct strbuf *sb,\n \t\t\tconst char *suffix)\n {\n \tconst struct name_decoration *decoration;\n-\tconst struct name_decoration *current_and_HEAD;\n-\tconst char *color_commit =\n-\t\tdiff_get_color(use_color, DIFF_COMMIT);\n-\tconst char *color_reset =\n-\t\tdecorate_get_color(use_color, DECORATION_NONE);\n+\tstruct format_decorations_context ctx = {\n+\t\t.sb = sb,\n+\t\t.use_color = use_color,\n+\t\t.prefix = prefix,\n+\t\t.separator = separator,\n+\t\t.suffix = suffix,\n+\t\t.color_commit =\tdiff_get_color(use_color, DIFF_COMMIT),\n+\t\t.color_reset = decorate_get_color(use_color, DECORATION_NONE),\n+\t};\n \n \tdecoration = get_name_decoration(&commit->object);\n \tif (!decoration)\n \t\treturn;\n \n-\tcurrent_and_HEAD = current_pointed_by_HEAD(decoration);\n-\twhile (decoration) {\n-\t\t/*\n-\t\t * When both current and HEAD are there, only\n-\t\t * show HEAD->current where HEAD would have\n-\t\t * appeared, skipping the entry for current.\n-\t\t */\n-\t\tif (decoration != current_and_HEAD) {\n-\t\t\tstrbuf_addstr(sb, color_commit);\n-\t\t\tstrbuf_addstr(sb, prefix);\n-\t\t\tstrbuf_addstr(sb, color_reset);\n-\t\t\tstrbuf_addstr(sb, decorate_get_color(use_color, decoration->type));\n-\t\t\tif (decoration->type == DECORATION_REF_TAG)\n-\t\t\t\tstrbuf_addstr(sb, \"tag: \");\n-\n-\t\t\tshow_name(sb, decoration);\n-\n-\t\t\tif (current_and_HEAD &&\n-\t\t\t    decoration->type == DECORATION_REF_HEAD) {\n-\t\t\t\tstrbuf_addstr(sb, \" -> \");\n-\t\t\t\tstrbuf_addstr(sb, color_reset);\n-\t\t\t\tstrbuf_addstr(sb, decorate_get_color(use_color, current_and_HEAD->type));\n-\t\t\t\tshow_name(sb, current_and_HEAD);\n-\t\t\t}\n-\t\t\tstrbuf_addstr(sb, color_reset);\n+\tctx.current_and_HEAD = current_pointed_by_HEAD(decoration);\n \n-\t\t\tprefix = separator;\n-\t\t}\n+\tfor_each_decoration(commit, append_decoration, &ctx);\n+\n+\tstrbuf_addstr(sb, ctx.color_commit);\n+\tstrbuf_addstr(sb, ctx.suffix);\n+\tstrbuf_addstr(sb, ctx.color_reset);\n+}\n+\n+int for_each_decoration(const struct commit *c, decoration_fn fn, void *data)\n+{\n+\tconst struct name_decoration *decoration;\n+\n+\tdecoration = get_name_decoration(&c->object);\n+\twhile (decoration) {\n+\t\tint res;\n+\t\tif ((res = fn(decoration, data)))\n+\t\t\treturn res;\n \t\tdecoration = decoration->next;\n \t}\n-\tstrbuf_addstr(sb, color_commit);\n-\tstrbuf_addstr(sb, suffix);\n-\tstrbuf_addstr(sb, color_reset);\n+\n+\treturn 0;\n }\n \n void show_decorations(struct rev_info *opt, struct commit *commit)\ndiff --git a/log-tree.h b/log-tree.h\nindex e7e4641cf83..ea07da2625b 100644\n--- a/log-tree.h\n+++ b/log-tree.h\n@@ -35,4 +35,8 @@ void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);\n void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *);\n void fmt_output_email_subject(struct strbuf *, struct rev_info *);\n \n+typedef int decoration_fn(const struct name_decoration *d,\n+\t\t\t  void *data);\n+int for_each_decoration(const struct commit *c, decoration_fn fn, void *data);\n+\n #endif\n-- \ngitgitgadget\n\n"},{"id":"456579","messageId":"23fa663886405596370e5fc95f679ef299f0725c.1654263472.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"[PATCH 3/4] rebase: add --update-refs option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-03T13:37:51Z","receivedAt":"2022-06-03T13:38:10Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen working on a large feature, it can be helpful to break that feature\ninto multiple smaller parts that become reviewed in sequence. During\ndevelopment or during review, a change to one part of the feature could\naffect multiple of these parts. An interactive rebase can help adjust\nthe multi-part \"story\" of the branch.\n\nHowever, if there are branches tracking the different parts of the\nfeature, then rebasing the entire list of commits can create commits not\nreachable from those \"sub branches\". It can take a manual step to update\nthose branches.\n\nAdd a new --update-refs option to 'git rebase -i' that adds 'git\nupdate-ref' exec steps to the todo file whenever a commit that is being\nrebased is decorated with that <ref>. This allows the user to rebase a\nlong list of commits in a multi-part feature and keep all of their\npointers to those parts.\n\nUse the new for_each_decoration() while iterating over the existing todo\nlist. Be sure to iterate after any squashing or fixups are placed.\nUpdate the branch only after those squashes and fixups are complete.\nThis allows a --fixup commit at the tip of the feature to apply\ncorrectly to the sub branch, even if it is fixing up the most-recent\ncommit in that part.\n\nOne potential problem here is that refs decorating commits that are\nalready marked as \"fixup!\" or \"squash!\" will not be included in this\nlist. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\nto change the relative order of these refs, so it is not recommended.\nThe workflow here is intended to allow these kinds of commits at the tip\nof the rebased branch while the other sub branches come along for the\nride without intervention.\n\nBe careful to not attempt updating any branch that is checked out. The\nmost common example is the branch being rebased is checked out and\ndecorates the tip commit. If the user is rebasing commits reachable from\na different branch that is checked out in a different worktree, then\nthey may be surprised to not see that ref update. However, it's probably\nbest to not optimize for this scenario and do the safest thing that will\nresult in a successful rebase. A comment is left in the TODO list that\nsignals that these refs are currently checked out.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/git-rebase.txt  |  7 +++\n builtin/rebase.c              |  5 ++\n sequencer.c                   | 99 +++++++++++++++++++++++++++++++++++\n sequencer.h                   |  1 +\n t/t3404-rebase-interactive.sh | 29 ++++++++++\n 5 files changed, 141 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 262fb01aec0..866554fc978 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -609,6 +609,13 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n start would be overridden by the presence of\n `rebase.rescheduleFailedExec=true` configuration.\n \n+--update-refs::\n+--no-update-refs::\n+\tAutomatically force-update any branches that point to commits that\n+\tare being rebased. Any branches that are checked out in a worktree\n+\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n+\tin this way.\n+\n INCOMPATIBLE OPTIONS\n --------------------\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7ab50cda2ad..56d82a52106 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -102,6 +102,7 @@ struct rebase_options {\n \tint reschedule_failed_exec;\n \tint reapply_cherry_picks;\n \tint fork_point;\n+\tint update_refs;\n };\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n@@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \t\tret = complete_action(the_repository, &replay, flags,\n \t\t\tshortrevisions, opts->onto_name, opts->onto,\n \t\t\t&opts->orig_head, &commands, opts->autosquash,\n+\t\t\topts->update_refs,\n \t\t\t&todo_list);\n \t}\n \n@@ -1124,6 +1126,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n+\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n+\t\t\t N_(\"update local refs that point to commits \"\n+\t\t\t    \"that are being rebased\")),\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n \t\t\tN_(\"GPG-sign commits\"),\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\ndiff --git a/sequencer.c b/sequencer.c\nindex 8c3ed3532ac..d6151af9849 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -35,6 +35,8 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"branch.h\"\n+#include \"log-tree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -5603,10 +5605,104 @@ static int skip_unnecessary_picks(struct repository *r,\n \treturn 0;\n }\n \n+struct todo_add_branch_context {\n+\tstruct todo_list new_list;\n+\tstruct strbuf *buf;\n+\tstruct commit *commit;\n+};\n+\n+static int add_branch_for_decoration(const struct name_decoration *d, void *data)\n+{\n+\tstruct todo_add_branch_context *ctx = data;\n+\tsize_t base_offset = ctx->buf->len;\n+\tint i = ctx->new_list.nr;\n+\tstruct todo_item *item;\n+\tchar *path;\n+\n+\tALLOC_GROW(ctx->new_list.items,\n+\t\t   ctx->new_list.nr + 1,\n+\t\t   ctx->new_list.alloc);\n+\titem = &ctx->new_list.items[i];\n+\n+\t/* If the branch is checked out, then leave a comment instead. */\n+\tif (branch_checked_out(d->name, &path)) {\n+\t\titem->command = TODO_COMMENT;\n+\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n+\t\t\t    d->name, path);\n+\t\tfree(path);\n+\t} else {\n+\t\titem->command = TODO_EXEC;\n+\t\tstrbuf_addf(ctx->buf, \"git update-ref %s HEAD %s\\n\",\n+\t\t\t    d->name, oid_to_hex(&ctx->commit->object.oid));\n+\t}\n+\n+\titem->commit = NULL;\n+\titem->offset_in_buf = base_offset;\n+\titem->arg_offset = base_offset;\n+\titem->arg_len = ctx->buf->len - base_offset;\n+\tctx->new_list.nr++;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * For each 'pick' command, find out if the commit has a decoration in\n+ * refs/heads/. If so, then add a 'git branch -f' exec command after\n+ * that 'pick' (plus any following 'squash' or 'fixup' commands).\n+ */\n+static int todo_list_add_branch_commands(struct todo_list *todo_list)\n+{\n+\tint i;\n+\tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n+\tstruct decoration_filter decoration_filter = {\n+\t\t.include_ref_pattern = &decorate_refs_include,\n+\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n+\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config\n+\t};\n+\tstruct todo_add_branch_context ctx = {\n+\t\t.new_list = TODO_LIST_INIT,\n+\t\t.buf = &todo_list->buf,\n+\t};\n+\n+\tstring_list_append(&decorate_refs_include, \"refs/heads/\");\n+\tload_ref_decorations(&decoration_filter, 0);\n+\n+\tfor (i = 0; i < todo_list->nr; ) {\n+\t\tstruct todo_item *item = &todo_list->items[i];\n+\n+\t\tdo {\n+\t\t\t/* insert ith item into new list */\n+\t\t\tALLOC_GROW(ctx.new_list.items,\n+\t\t\t\t   ctx.new_list.nr + 1,\n+\t\t\t\t   ctx.new_list.alloc);\n+\n+\t\t\tmemcpy(&ctx.new_list.items[ctx.new_list.nr++],\n+\t\t\t       &todo_list->items[i],\n+\t\t\t       sizeof(struct todo_item));\n+\n+\t\t\ti++;\n+\t\t} while (i < todo_list->nr &&\n+\t\t\t todo_list->items[i].command != TODO_PICK);\n+\n+\t\tctx.commit = item->commit;\n+\t\tfor_each_decoration(item->commit, add_branch_for_decoration, &ctx);\n+\t}\n+\n+\tfree(todo_list->items);\n+\ttodo_list->items = ctx.new_list.items;\n+\ttodo_list->nr = ctx.new_list.nr;\n+\ttodo_list->alloc = ctx.new_list.alloc;\n+\n+\treturn 0;\n+}\n+\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned keep_decorations,\n \t\t    struct todo_list *todo_list)\n {\n \tchar shortonto[GIT_MAX_HEXSZ + 1];\n@@ -5628,6 +5724,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \tif (autosquash && todo_list_rearrange_squash(todo_list))\n \t\treturn -1;\n \n+\tif (keep_decorations && todo_list_add_branch_commands(todo_list))\n+\t\treturn -1;\n+\n \tif (commands->nr)\n \t\ttodo_list_add_exec_commands(todo_list, commands);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex da64473636b..1add3c7119f 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -166,6 +166,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned keep_decorations,\n \t\t    struct todo_list *todo_list);\n int todo_list_rearrange_squash(struct todo_list *todo_list);\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f31afd4a547..38c7ef95e0e 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1743,6 +1743,35 @@ test_expect_success 'ORIG_HEAD is updated correctly' '\n \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n '\n \n+test_expect_success '--update-refs adds git branch commands' '\n+\tgit checkout -b update-refs no-conflict-branch &&\n+\ttest_commit extra fileX &&\n+\tgit commit --amend --fixup=L &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\t\tgit branch -f base A &&\n+\t\tgit branch -f first J &&\n+\t\tgit branch -f second J &&\n+\t\tgit branch -f third L &&\n+\n+\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tpick $(git log -1 --format=%h J) J\n+\t\texec git update-ref refs/heads/second HEAD $(git rev-parse J)\n+\t\texec git update-ref refs/heads/first HEAD $(git rev-parse  J)\n+\t\tpick $(git log -1 --format=%h K) K\n+\t\tpick $(git log -1 --format=%h L) L\n+\t\tfixup $(git log -1 --format=%h update-refs) fixup! L\n+\t\texec git update-ref refs/heads/third HEAD $(git rev-parse L)\n+\t\tpick $(git log -1 --format=%h M) M\n+\t\texec git update-ref refs/heads/no-conflict-branch HEAD $(git rev-parse M)\n+\t\tEOF\n+\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"456580","messageId":"b99c5bf34ef45f8ac9e57ef28af01dfff7a72198.1654263472.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"[PATCH 4/4] rebase: add rebase.updateRefs config option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-03T13:37:52Z","receivedAt":"2022-06-03T13:38:12Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change added the --update-refs command-line option.  For\nusers who always want this mode, create the rebase.updateRefs config\noption which behaves the same way as rebase.autoSquash does with the\n--autosquash option.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/config/rebase.txt | 3 +++\n Documentation/git-rebase.txt    | 4 ++++\n builtin/rebase.c                | 5 +++++\n t/t3404-rebase-interactive.sh   | 5 +++++\n 4 files changed, 17 insertions(+)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex 8c979cb20f2..f19bd0e0407 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -21,6 +21,9 @@ rebase.autoStash::\n \t`--autostash` options of linkgit:git-rebase[1].\n \tDefaults to false.\n \n+rebase.updateRefs::\n+\tIf set to true enable `--update-refs` option by default.\n+\n rebase.missingCommitsCheck::\n \tIf set to \"warn\", git rebase -i will print a warning if some\n \tcommits are removed (e.g. a line was deleted), however the\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 866554fc978..cb1b498bd99 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -615,6 +615,10 @@ start would be overridden by the presence of\n \tare being rebased. Any branches that are checked out in a worktree\n \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n \tin this way.\n++\n+If the `--update-refs` option is enabled by default using the\n+configuration variable `rebase.updateRefs`, this option can be\n+used to override and disable this setting.\n \n INCOMPATIBLE OPTIONS\n --------------------\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 56d82a52106..8ebc98ea505 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -802,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"rebase.updaterefs\")) {\n+\t\topts->update_refs = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"rebase.reschedulefailedexec\")) {\n \t\topts->reschedule_failed_exec = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 38c7ef95e0e..1fa9f78d40d 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1768,6 +1768,11 @@ test_expect_success '--update-refs adds git branch commands' '\n \t\texec git update-ref refs/heads/no-conflict-branch HEAD $(git rev-parse M)\n \t\tEOF\n \n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i primary >todo &&\n \t\ttest_cmp expect todo\n \t)\n '\n-- \ngitgitgadget\n"},{"id":"456594","messageId":"xmqqpmjpheiv.fsf@gitster.g","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-03T16:56:24Z","receivedAt":"2022-06-03T16:56:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This is a feature I've wanted for quite a while. When working on the sparse\n> index topic, I created a long RFC that actually broke into three topics for\n> full review upstream. These topics were sequential, so any feedback on an\n> earlier one required updates to the later ones. I would work on the full\n> feature and use interactive rebase to update the full list of commits.\n> However, I would need to update the branches pointing to those sub-topics.\n>\n> This series adds a new --update-refs option to 'git rebase' (along with a\n> rebase.updateRefs config option) that adds 'git update-ref' commands into\n> the TODO list. This is powered by the commit decoration machinery.\n\n;-)\n"},{"id":"456598","messageId":"xmqqa6athcif.fsf@gitster.g","threadId":"57949","inReplyTo":"4f9f34876413927d819313a70fcdefcad5b35689.1654263472.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/4] log-tree: create for_each_decoration()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-03T17:39:52Z","receivedAt":"2022-06-03T17:39:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  \tconst struct name_decoration *decoration;\n> +\tstruct format_decorations_context ctx = {\n> +\t\t.sb = sb,\n> +\t\t.use_color = use_color,\n> +\t\t.prefix = prefix,\n> +\t\t.separator = separator,\n> +\t\t.suffix = suffix,\n> +\t\t.color_commit =\tdiff_get_color(use_color, DIFF_COMMIT),\n> +\t\t.color_reset = decorate_get_color(use_color, DECORATION_NONE),\n> +\t};\n>  \n>  \tdecoration = get_name_decoration(&commit->object);\n>  \tif (!decoration)\n>  \t\treturn;\n>  \n> +\tctx.current_and_HEAD = current_pointed_by_HEAD(decoration);\n>  \n> +\tfor_each_decoration(commit, append_decoration, &ctx);\n\nThe function for_each_decoration() that does not take decoration but\na commit felt iffy, especially because we already have called\nget_name_decoration() to obtain one for commit, and the API forces\nus to do that again at the beginning of for_each_decoration().\n\n> +\tstrbuf_addstr(sb, ctx.color_commit);\n> +\tstrbuf_addstr(sb, ctx.suffix);\n> +\tstrbuf_addstr(sb, ctx.color_reset);\n> +}\n> +\n> +int for_each_decoration(const struct commit *c, decoration_fn fn, void *data)\n> +{\n> +\tconst struct name_decoration *decoration;\n> +\n> +\tdecoration = get_name_decoration(&c->object);\n> +\twhile (decoration) {\n> +\t\tint res;\n> +\t\tif ((res = fn(decoration, data)))\n> +\t\t\treturn res;\n>  \t\tdecoration = decoration->next;\n>  \t}\n> +\n> +\treturn 0;\n>  }\n\nWe'll know if this small waste is worth it when we see the new\ncaller(s), I guess, but even if they start from commit, allowing\nthem the same early return trick would require this piece of code:\n\n\tdecoration = get_name_decoration(&commit->object);\n\tif (!decoration)\n\t\treturn;\n\nin them, and even if they do not need it, it would be trivial for\nthem to say\n\n\tfor_each_decoration(get_name_decoration(&commit->object),\n        \t\t    do_whatever_they_need_to_do, &ctx);\n\nso, we may want to revisit this after we finish reading the series\nthrough.  Making the iterator take name_decoration does not look\ntoo bad.\n\n>  void show_decorations(struct rev_info *opt, struct commit *commit)\n> diff --git a/log-tree.h b/log-tree.h\n> index e7e4641cf83..ea07da2625b 100644\n> --- a/log-tree.h\n> +++ b/log-tree.h\n> @@ -35,4 +35,8 @@ void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);\n>  void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *);\n>  void fmt_output_email_subject(struct strbuf *, struct rev_info *);\n>  \n> +typedef int decoration_fn(const struct name_decoration *d,\n> +\t\t\t  void *data);\n> +int for_each_decoration(const struct commit *c, decoration_fn fn, void *data);\n> +\n>  #endif\n"},{"id":"456599","messageId":"55d7146d-0fc4-3c29-b9a4-72f5ff856255@github.com","threadId":"57949","inReplyTo":"xmqqa6athcif.fsf@gitster.g","subject":"Re: [PATCH 1/4] log-tree: create for_each_decoration()","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-03T17:58:32Z","receivedAt":"2022-06-03T18:08:49Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/3/2022 1:39 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>>  \tconst struct name_decoration *decoration;\n>> +\tstruct format_decorations_context ctx = {\n>> +\t\t.sb = sb,\n>> +\t\t.use_color = use_color,\n>> +\t\t.prefix = prefix,\n>> +\t\t.separator = separator,\n>> +\t\t.suffix = suffix,\n>> +\t\t.color_commit =\tdiff_get_color(use_color, DIFF_COMMIT),\n>> +\t\t.color_reset = decorate_get_color(use_color, DECORATION_NONE),\n>> +\t};\n>>  \n>>  \tdecoration = get_name_decoration(&commit->object);\n>>  \tif (!decoration)\n>>  \t\treturn;\n>>  \n>> +\tctx.current_and_HEAD = current_pointed_by_HEAD(decoration);\n>>  \n>> +\tfor_each_decoration(commit, append_decoration, &ctx);\n> \n> The function for_each_decoration() that does not take decoration but\n> a commit felt iffy, especially because we already have called\n> get_name_decoration() to obtain one for commit, and the API forces\n> us to do that again at the beginning of for_each_decoration().\n\nThis is more an issue with this particular caller since it needs\nthat current_pointed_by_HEAD() information. I had considered it\nas something to include in the prototype of decoration_fn so it\ncould be incorporated into for_each_decoration(), but it ended up\nbeing overly clunky for this one consumer. I'm open to other ideas,\nthough.\n\n>> +\tstrbuf_addstr(sb, ctx.color_commit);\n>> +\tstrbuf_addstr(sb, ctx.suffix);\n>> +\tstrbuf_addstr(sb, ctx.color_reset);\n>> +}\n>> +\n>> +int for_each_decoration(const struct commit *c, decoration_fn fn, void *data)\n\nThe goal of this method was to make it super simple to iterate\nwithout doing any prep work: just load the commit and go.\n\n>> +{\n>> +\tconst struct name_decoration *decoration;\n>> +\n>> +\tdecoration = get_name_decoration(&c->object);\n>> +\twhile (decoration) {\n>> +\t\tint res;\n>> +\t\tif ((res = fn(decoration, data)))\n>> +\t\t\treturn res;\n>>  \t\tdecoration = decoration->next;\n>>  \t}\n>> +\n>> +\treturn 0;\n>>  }\n> \n> We'll know if this small waste is worth it when we see the new\n> caller(s), I guess, but even if they start from commit, allowing\n> them the same early return trick would require this piece of code:\n> \n> \tdecoration = get_name_decoration(&commit->object);\n> \tif (!decoration)\n> \t\treturn;\n\nThe iterator returns quickly because the while loop notices a\nNULL decoration. This use in format_decorations_extended() needs\nthat check because of the current_pointed_by_HEAD(decoration)\ncall based on the result being non-NULL.\n\n> in them, and even if they do not need it, it would be trivial for\n> them to say\n> \n> \tfor_each_decoration(get_name_decoration(&commit->object),\n>         \t\t    do_whatever_they_need_to_do, &ctx);\n> \n> so, we may want to revisit this after we finish reading the series\n> through.  Making the iterator take name_decoration does not look\n> too bad.\n\nYou are right that this would work, but it's a bit messier. We\ncould go this route to avoid the duplicate get_name_decoration()\ncalls in format_decorations_extended().\n\nThanks,\n-Stolee\n"},{"id":"456602","messageId":"YppSl4rOvVciLuXE@nand.local","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-06-03T18:27:35Z","receivedAt":"2022-06-03T18:36:18Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Jun 03, 2022 at 01:37:48PM +0000, Derrick Stolee via GitGitGadget wrote:\n> This is a feature I've wanted for quite a while. When working on the sparse\n> index topic, I created a long RFC that actually broke into three topics for\n> full review upstream. These topics were sequential, so any feedback on an\n> earlier one required updates to the later ones. I would work on the full\n> feature and use interactive rebase to update the full list of commits.\n> However, I would need to update the branches pointing to those sub-topics.\n\nThis is really exciting. I'm glad that you're working on it, because I\nhave also wanted this feature a handful of times over the years.\n\nThis definitely would have come in handy when writing MIDX bitmaps,\nwhere I was often editing a local branch pointing at the final topic,\nand then trying to reconstruct all of the intermediate branches after\neach rebase. Not ever having to do that again sounds like a delight ;-).\n\n> pick 2d966282ff3 docs: document bundle URI standard\n> pick 31396e9171a remote-curl: add 'get' capability\n> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n> pick 6adaf842684 fetch: add --bundle-uri option\n> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n> exec git update-ref refs/heads/bundle-redo/fetch HEAD 6c5840ed77e1bc41c1fe6fb7c894ceede1b8d730\n\nBut I wonder if we can or should delay these update-refs as long as\npossible. In particular: what happens if I get past this \"exec\" line (so\nthat I've already updated my bundle-redo/fetch branch to point at the\nnew thing), but decide at some later point to abort the rebase?\n\nI think users will expect us to restore bundle-redo/fetch to where it\nwas before if we end up in that case. Recovering from it manually sounds\nlike kind of a headache.\n\nWhat if instead we created labels here, and then delayed all ref updates\nto the end by replacing this with:\n\n    label bundle-redo/fetch\n\nand then at the end of the todo list we'd add:\n\n    exec git update-ref refs/heads/bundle-redo/fetch refs/rewritten/bundle-redo/fetch\n\nIf we do all of those ref updates in a single transaction at the end, it\nshould be much easier to roll back from if desired, and we'd avoid the\naborted-rebase problem entirely.\n\nThanks,\nTaylor\n"},{"id":"456603","messageId":"xmqqzgitfvjs.fsf@gitster.g","threadId":"57949","inReplyTo":"5f54766e1032ebf3a331516a6dd696b997bdfdd8.1654263472.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/4] branch: add branch_checked_out() helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-03T18:31:35Z","receivedAt":"2022-06-03T18:37:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +int branch_checked_out(const char *refname, char **path)\n> +{\n> +\tstruct worktree **worktrees = get_worktrees();\n> +\tconst struct worktree *wt = find_shared_symref(worktrees, \"HEAD\", refname);\n> +\tint result = wt && !wt->is_bare;\n> +\n> +\tif (result && path)\n> +\t\t*path = xstrdup(wt->path);\n> +\n> +\tfree_worktrees(worktrees);\n> +\treturn result;\n> +}\n\nDon't you plan to call this repeatedly from the for_each_deco\niteration?  I am wondering if it should take the result of\nget_worktrees() and reuse the result of get_worktrees().\n\nThere also was another topic that was triggered by\nfind_shared_symref() being relatively heavy-weight, which suggests a\nmore involved refactoring.\n\nI wonder if we rather want to rewrite find_shared_symref() *not* to\ntake the target parameter at all, and instead introduce a new\nfunction that iterates over worktrees and report the branch that is\nchecked out (or being operated on via rebase or bisect).  Then we\ncan\n\n - create a strset out of its result, i.e. set of branches that\n   should not be touched;\n\n - iterate over refs that point into the history being rebased\n   (using for_each_decoration()), and consult that strset to see if\n   any of them is being rewritten.  \n\nWith the API of find_shared_symref(), we'd need to iterate over all\nworktrees for each decoration.  With such a restructuring, we can\niterate over all worktrees just once, and match the result with\ndecoration, so the problem becomes O(N)+O(M) and not O(N*M) for\nnumber of worktrees N and number of decorations M.\n"},{"id":"456619","messageId":"xmqqv8thfv56.fsf@gitster.g","threadId":"57949","inReplyTo":"55d7146d-0fc4-3c29-b9a4-72f5ff856255@github.com","subject":"Re: [PATCH 1/4] log-tree: create for_each_decoration()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-03T18:40:21Z","receivedAt":"2022-06-03T18:40:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n>>> +\tfor_each_decoration(commit, append_decoration, &ctx);\n>> \n>> The function for_each_decoration() that does not take decoration but\n>> a commit felt iffy,...\n>> ...\n>> \tfor_each_decoration(get_name_decoration(&commit->object),\n>>         \t\t    do_whatever_they_need_to_do, &ctx);\n>> \n>> so, we may want to revisit this after we finish reading the series\n>> through.  Making the iterator take name_decoration does not look\n>> too bad.\n>\n> You are right that this would work, but it's a bit messier. We\n> could go this route to avoid the duplicate get_name_decoration()\n> calls in format_decorations_extended().\n\nHaving a convenience function that takes a commit and let you walk\nover its decoration is OK, but calling it for_each_decoration()\nbothers me somewhat (which is where my comment started from after\nall).\n\nHopefully it will stay unambiguous, and it will stay to be\nunnecessary to name it for_each_decoration_for_commit(), as I do not\nthink we'd add \"decoration\" to things that are not commits (or if we\nadded one, we probably will call it differently from \"decoration\").\n\nSo, OK.\n"},{"id":"456623","messageId":"xmqqr145fuku.fsf@gitster.g","threadId":"57949","inReplyTo":"YppSl4rOvVciLuXE@nand.local","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-03T18:52:33Z","receivedAt":"2022-06-03T18:52:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> But I wonder if we can or should delay these update-refs as long as\n> possible. In particular: what happens if I get past this \"exec\" line (so\n> that I've already updated my bundle-redo/fetch branch to point at the\n> new thing), but decide at some later point to abort the rebase?\n>\n> I think users will expect us to restore bundle-redo/fetch to where it\n> was before if we end up in that case. Recovering from it manually sounds\n> like kind of a headache.\n\nThat is a very good safety and usability concern.  I am glad\nsomebody thought of it.\n\n> What if instead we created labels here, and then delayed all ref updates\n> to the end by replacing this with:\n>\n>     label bundle-redo/fetch\n>\n> and then at the end of the todo list we'd add:\n>\n>     exec git update-ref refs/heads/bundle-redo/fetch refs/rewritten/bundle-redo/fetch\n>\n> If we do all of those ref updates in a single transaction at the end, it\n> should be much easier to roll back from if desired, and we'd avoid the\n> aborted-rebase problem entirely.\n\n;-)\n"},{"id":"456630","messageId":"9d2752dd-6029-e71a-4149-0641fea879fe@jeffhostetler.com","threadId":"57949","inReplyTo":"YppSl4rOvVciLuXE@nand.local","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2022-06-03T19:59:00Z","receivedAt":"2022-06-03T19:59:05Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 6/3/22 2:27 PM, Taylor Blau wrote:\n> On Fri, Jun 03, 2022 at 01:37:48PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> This is a feature I've wanted for quite a while. When working on the sparse\n>> index topic, I created a long RFC that actually broke into three topics for\n>> full review upstream. These topics were sequential, so any feedback on an\n>> earlier one required updates to the later ones. I would work on the full\n>> feature and use interactive rebase to update the full list of commits.\n>> However, I would need to update the branches pointing to those sub-topics.\n> \n> This is really exciting. I'm glad that you're working on it, because I\n> have also wanted this feature a handful of times over the years.\n> \n> This definitely would have come in handy when writing MIDX bitmaps,\n> where I was often editing a local branch pointing at the final topic,\n> and then trying to reconstruct all of the intermediate branches after\n> each rebase. Not ever having to do that again sounds like a delight ;-).\n> \n>> pick 2d966282ff3 docs: document bundle URI standard\n>> pick 31396e9171a remote-curl: add 'get' capability\n>> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n>> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n>> pick 6adaf842684 fetch: add --bundle-uri option\n>> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n>> exec git update-ref refs/heads/bundle-redo/fetch HEAD 6c5840ed77e1bc41c1fe6fb7c894ceede1b8d730\n> \n> But I wonder if we can or should delay these update-refs as long as\n> possible. In particular: what happens if I get past this \"exec\" line (so\n> that I've already updated my bundle-redo/fetch branch to point at the\n> new thing), but decide at some later point to abort the rebase?\n> \n> I think users will expect us to restore bundle-redo/fetch to where it\n> was before if we end up in that case. Recovering from it manually sounds\n> like kind of a headache.\n> \n> What if instead we created labels here, and then delayed all ref updates\n> to the end by replacing this with:\n> \n>      label bundle-redo/fetch\n> \n> and then at the end of the todo list we'd add:\n> \n>      exec git update-ref refs/heads/bundle-redo/fetch refs/rewritten/bundle-redo/fetch\n> \n> If we do all of those ref updates in a single transaction at the end, it\n> should be much easier to roll back from if desired, and we'd avoid the\n> aborted-rebase problem entirely.\n> \n> Thanks,\n> Taylor\n> \n\nI agree. I could have really used this while juggling all of the\nparts of FSMonitor recently.  And yes, it should write the updates\nat the bottom in case of an abort.\n\nShould this take a branch pattern/regex to limit the set of branches\nthat are updated (or offered to be updated)?  For example, if I have\nan intermediate commit in the series that has 2 branch names pointing\nat it, do we want to offer to update both of them or only the one\nthat matches some pattern related to the tip?  Or is it sufficient to\njust enumerate them at the bottom of the todo list and let the user\ndelete the lines they don't want?\n\nShould we actually do the update-ref's or should we write a script\nthat lets the user do it later?  The latter would let us also write\nout the commands to force update the remote refs if that would be\nhelpful.\n\nThanks,\nJeff\n"},{"id":"456631","messageId":"YpppCx24rn9UTXe2@nand.local","threadId":"57949","inReplyTo":"9d2752dd-6029-e71a-4149-0641fea879fe@jeffhostetler.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-06-03T20:03:23Z","receivedAt":"2022-06-03T20:03:31Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Jun 03, 2022 at 03:59:00PM -0400, Jeff Hostetler wrote:\n> Should this take a branch pattern/regex to limit the set of branches\n> that are updated (or offered to be updated)?  For example, if I have\n> an intermediate commit in the series that has 2 branch names pointing\n> at it, do we want to offer to update both of them or only the one\n> that matches some pattern related to the tip?  Or is it sufficient to\n> just enumerate them at the bottom of the todo list and let the user\n> delete the lines they don't want?\n\nThat could be a useful feature to layer on top. I don't think it's\nrequired here, though, since (as you note) users can remove lines from\nthe update-ref invocation(s) at the bottom that they don't want to\nperform.\n\n> Should we actually do the update-ref's or should we write a script\n> that lets the user do it later?  The latter would let us also write\n> out the commands to force update the remote refs if that would be\n> helpful.\n\nThat seems like it would be outside the bounds of what `rebase` should\nprovide, but others may feel differently.\n\nThanks,\nTaylor\n"},{"id":"456636","messageId":"xmqqmtetcug2.fsf@gitster.g","threadId":"57949","inReplyTo":"9d2752dd-6029-e71a-4149-0641fea879fe@jeffhostetler.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-03T21:23:41Z","receivedAt":"2022-06-03T21:23:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> I agree. I could have really used this while juggling all of the\n> parts of FSMonitor recently.  And yes, it should write the updates\n> at the bottom in case of an abort.\n>\n> Should this take a branch pattern/regex to limit the set of branches\n> that are updated (or offered to be updated)?  For example, if I have\n> an intermediate commit in the series that has 2 branch names pointing\n> at it, do we want to offer to update both of them or only the one\n> that matches some pattern related to the tip?  Or is it sufficient to\n> just enumerate them at the bottom of the todo list and let the user\n> delete the lines they don't want?\n\nThe latter sounds sufficient (starting from something simpler should\nwork well in this case).\n\n> Should we actually do the update-ref's or should we write a script\n> that lets the user do it later?  The latter would let us also write\n> out the commands to force update the remote refs if that would be\n> helpful.\n\nAren't we writing \"a script\" already by implementing it as an\nadditional \"exec git update-ref\" in the todo list already?  I\nfound that, combined with the idea to use the \"label\" Taylor\nmentioned, was the most brilliant part of this proposal.\n\n"},{"id":"456682","messageId":"a63cba6e-d25a-ee6d-57f0-9562b6235d3b@gmail.com","threadId":"57949","inReplyTo":"YppSl4rOvVciLuXE@nand.local","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-04T15:28:39Z","receivedAt":"2022-06-04T15:28:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 03/06/2022 19:27, Taylor Blau wrote:\n> On Fri, Jun 03, 2022 at 01:37:48PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> This is a feature I've wanted for quite a while. When working on the sparse\n>> index topic, I created a long RFC that actually broke into three topics for\n>> full review upstream. These topics were sequential, so any feedback on an\n>> earlier one required updates to the later ones. I would work on the full\n>> feature and use interactive rebase to update the full list of commits.\n>> However, I would need to update the branches pointing to those sub-topics.\n> \n> This is really exciting. I'm glad that you're working on it, because I\n> have also wanted this feature a handful of times over the years.\n\nYes, thank you Stolee. I agree this will be useful, but I wonder if \nusers will be confused that --update-refs only updates the branch heads \nthat happen to be in the todo list, rather than updating all the \nbranches that contain a rewritten commit. I think the latter is \nsomething we should try to add in the future and so we should take care \nto design this topic so that is possible.\n\n> This definitely would have come in handy when writing MIDX bitmaps,\n> where I was often editing a local branch pointing at the final topic,\n> and then trying to reconstruct all of the intermediate branches after\n> each rebase. Not ever having to do that again sounds like a delight ;-).\n> \n>> pick 2d966282ff3 docs: document bundle URI standard\n>> pick 31396e9171a remote-curl: add 'get' capability\n>> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n>> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n>> pick 6adaf842684 fetch: add --bundle-uri option\n>> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n>> exec git update-ref refs/heads/bundle-redo/fetch HEAD 6c5840ed77e1bc41c1fe6fb7c894ceede1b8d730\n> \n> But I wonder if we can or should delay these update-refs as long as\n> possible. In particular: what happens if I get past this \"exec\" line (so\n> that I've already updated my bundle-redo/fetch branch to point at the\n> new thing), but decide at some later point to abort the rebase?\n\nAbsolutely! There is also the question of what to do if a user skips a \ncommit that is a branch head. It is not obvious if they just want to \ndrop that commit from the branch or if they want to skip updating the \nref as well (I guess they can edit the todo list for the latter case).\n\n> I think users will expect us to restore bundle-redo/fetch to where it\n> was before if we end up in that case. Recovering from it manually sounds\n> like kind of a headache.\n> \n> What if instead we created labels here, and then delayed all ref updates\n> to the end by replacing this with:\n> \n>      label bundle-redo/fetch\n\nInstead of using 'label' and 'exec' I'd prefer a new todo list command \n('update-ref' or 'update-branch'?) used in place of 'label' that takes a \nbranch name and updates the branch ref at the end of the rebase. That \nwould make it easy to do all the updates in a single transaction as you \nsuggested. Adding exec lines to do this makes the todo list messy and we \nhave been trying to stop rebase forking all the time.\n\nBest Wishes\n\nPhillip\n\n> and then at the end of the todo list we'd add:\n> \n>      exec git update-ref refs/heads/bundle-redo/fetch refs/rewritten/bundle-redo/fetch\n> \n> If we do all of those ref updates in a single transaction at the end, it\n> should be much easier to roll back from if desired, and we'd avoid the\n> aborted-rebase problem entirely.\n\n\n> Thanks,\n> Taylor\n\n"},{"id":"456716","messageId":"3bb8baa2-e3e0-5cf1-aace-b0ba3ec6eb77@github.com","threadId":"57949","inReplyTo":"a63cba6e-d25a-ee6d-57f0-9562b6235d3b@gmail.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-06T15:12:46Z","receivedAt":"2022-06-06T15:12:53Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/4/2022 11:28 AM, Phillip Wood wrote:\n> On 03/06/2022 19:27, Taylor Blau wrote:\n>> On Fri, Jun 03, 2022 at 01:37:48PM +0000, Derrick Stolee via GitGitGadget wrote:\n>>> This is a feature I've wanted for quite a while. When working on the sparse\n>>> index topic, I created a long RFC that actually broke into three topics for\n>>> full review upstream. These topics were sequential, so any feedback on an\n>>> earlier one required updates to the later ones. I would work on the full\n>>> feature and use interactive rebase to update the full list of commits.\n>>> However, I would need to update the branches pointing to those sub-topics.\n>>\n>> This is really exciting. I'm glad that you're working on it, because I\n>> have also wanted this feature a handful of times over the years.\n> \n> Yes, thank you Stolee. I agree this will be useful, but I wonder if users\n> will be confused that --update-refs only updates the branch heads that happen\n> to be in the todo list, rather than updating all the branches that contain a\n> rewritten commit. I think the latter is something we should try to add in the\n> future and so we should take care to design this topic so that is possible.\n\nAt the moment, the design adds a comment to the TODO list, showing which\nbranches are not possible to move because they are checked out at another\nworktree (or is currently checked out and will be updated by the rebase\nitself). That seems like a good place to insert alternative logic in the\nfuture if we see a need for better behavior here.\n\nUnless: am I misunderstanding something about your concern here? Are you\nworried about refs outside of refs/heads/*? Are you concerned about it\nbeing _all_ refs/heads/* that we find?\n\nOne potential way to extend this (in the future) is to make --update-refs\ntake an optional string argument containing a refspec. This would replace\nthe default refspec of refs/heads/*.\n \n>> This definitely would have come in handy when writing MIDX bitmaps,\n>> where I was often editing a local branch pointing at the final topic,\n>> and then trying to reconstruct all of the intermediate branches after\n>> each rebase. Not ever having to do that again sounds like a delight ;-).\n>>\n>>> pick 2d966282ff3 docs: document bundle URI standard\n>>> pick 31396e9171a remote-curl: add 'get' capability\n>>> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n>>> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n>>> pick 6adaf842684 fetch: add --bundle-uri option\n>>> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n>>> exec git update-ref refs/heads/bundle-redo/fetch HEAD 6c5840ed77e1bc41c1fe6fb7c894ceede1b8d730\n>>\n>> But I wonder if we can or should delay these update-refs as long as\n>> possible. In particular: what happens if I get past this \"exec\" line (so\n>> that I've already updated my bundle-redo/fetch branch to point at the\n>> new thing), but decide at some later point to abort the rebase?\n> \n> Absolutely! There is also the question of what to do if a user skips a\n> commit that is a branch head. It is not obvious if they just want to drop> that commit from the branch or if they want to skip updating the ref as> well (I guess they can edit the todo list for the latter case).\n\n>> I think users will expect us to restore bundle-redo/fetch to where it\n>> was before if we end up in that case. Recovering from it manually sounds\n>> like kind of a headache.\n>>\n>> What if instead we created labels here, and then delayed all ref updates\n>> to the end by replacing this with:\n>>\n>>      label bundle-redo/fetch\n> \n> Instead of using 'label' and 'exec' I'd prefer a new todo list command\n> ('update-ref' or 'update-branch'?) used in place of 'label' that takes a\n> branch name and updates the branch ref at the end of the rebase. That\n> would make it easy to do all the updates in a single transaction as you\n> suggested. Adding exec lines to do this makes the todo list messy and we\n> have been trying to stop rebase forking all the time.\n\nThanks. Yes, a new 'update-refs' step at the end would be good to make\nit clear we want to rewrite the refs in one go without a possible\ninterruption from the user.\n\nThanks,\n-Stolee\n"},{"id":"456723","messageId":"xmqqr141agwb.fsf@gitster.g","threadId":"57949","inReplyTo":"a63cba6e-d25a-ee6d-57f0-9562b6235d3b@gmail.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-06T16:36:04Z","receivedAt":"2022-06-06T16:36:15Z","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> Instead of using 'label' and 'exec' I'd prefer a new todo list command\n> ('update-ref' or 'update-branch'?) used in place of 'label' that takes\n> a branch name and updates the branch ref at the end of the\n> rebase. That would make it easy to do all the updates in a single\n> transaction as you suggested.\n\nSounds like a good approach.\n"},{"id":"456769","messageId":"CABPp-BEOV53oBoBp4YjiRfksZMmAADanZUUemhxwn7Wor=m-nA@mail.gmail.com","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-06-07T06:25:25Z","receivedAt":"2022-06-07T06:25:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jun 3, 2022 at 11:01 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> This is a feature I've wanted for quite a while. When working on the sparse\n> index topic, I created a long RFC that actually broke into three topics for\n> full review upstream. These topics were sequential, so any feedback on an\n> earlier one required updates to the later ones. I would work on the full\n> feature and use interactive rebase to update the full list of commits.\n> However, I would need to update the branches pointing to those sub-topics.\n>\n> This series adds a new --update-refs option to 'git rebase' (along with a\n> rebase.updateRefs config option) that adds 'git update-ref' commands into\n> the TODO list. This is powered by the commit decoration machinery.\n>\n> As an example, here is my in-progress bundle URI RFC split into subtopics as\n> they appear during the TODO list of a git rebase -i --update-refs:\n>\n> pick 2d966282ff3 docs: document bundle URI standard\n> pick 31396e9171a remote-curl: add 'get' capability\n> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n> pick 6adaf842684 fetch: add --bundle-uri option\n> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n> exec git update-ref refs/heads/bundle-redo/fetch HEAD 6c5840ed77e1bc41c1fe6fb7c894ceede1b8d730\n>\n> pick 1e3f6546632 clone: add --bundle-uri option\n> pick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\n> exec git update-ref refs/heads/bundle-redo/clone HEAD 9e4a6fe9b68a8455b427c9ac8cdbff30c96653b4\n>\n> pick 5451cb6599c bundle-uri: create bundle_list struct and helpers\n> pick 3029c3aca15 bundle-uri: create base key-value pair parsing\n> pick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\n> pick 92625a47673 bundle-uri: unit test \"key=value\" parsing\n> pick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\n> pick 9d6809a8d53 bundle-uri: parse bundle list in config format\n> pick 287a732b54c bundle-uri: fetch a list of bundles\n> exec git update-ref refs/heads/bundle-redo/list HEAD 287a732b54c4d95e7f410b3b36ef90d8a19cd346\n>\n> pick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\n> pick 520204dcd1c bundle-uri client: add minimal NOOP client\n> pick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\n> pick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\n> pick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\n> pick caf4599a81d bundle-uri: allow relative URLs in bundle lists\n> pick df255000b7e bundle-uri: download bundles from an advertised list\n> pick d71beabf199 clone: unbundle the advertised bundles\n> pick c9578391976 t5601: basic bundle URI tests\n> # Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n>\n> exec git update-ref refs/heads/bundle-redo/advertise HEAD c9578391976ab9899c4e4f9b5fa2827650097305\n>\n>\n> The first two patches are helpers that are needed, but the full logic of the\n> --update-refs option is introduced in patch 3. The config option is\n> available in patch 4.\n\nVery interesting; nice to see that others are thinking on a similar\nwavelength.  I've got some related work in git-replay, and it heavily\nuses update-refs style handling.\n\nNot sure if there are any differences worth affecting your design or\nmine, but perhaps it's worth mentioning my current work and plans in\ngit-replay related to the use of update-refs:\n\n\n\nSince git-replay doesn't touch the working tree or index, I had the\nnatural question of what the output should be and whether ref-updates\nshould automatically happen.  I decided that like git-merge-tree, ref\nupdates should not be automatic (and thus it behaves somewhat like a\n\"dry run\" version of rebase/cherry-pick).  Since I also want to\nintrinsically handle replaying multiple branches simultaneously, the\nupdate-ref style output seemed like a natural fit to me (meaning users\nwould pipe the output from git-reply to git-update-ref).  Thus, one\ncan do:\n\n    $ git replay --onto main startpoint..mytopic\n\nand see git-update-ref style output:\n\n    update refs/heads/mytopic ${NEW_mytopic_HASH} ${OLD_mytopic_HASH}\n\nwhere ${NEW_mytopic_HASH} is the commit at the tip of a chain of\nrebased mytopic commits.  (Sidenote: There's the question of whether\nto update 'main' (i.e. cherry-picking) or 'mytopic' (i.e. rebasing);\nthus, users have to choose --advance vs. --onto to specify which of\nthese should happen, except in cases where it can be implicitly\ndetermined.)\n\nOr, if you have several branches to update:\n\n    $ git replay --onto main ^startpoint mytopic1 mytopic2 mytopic3\n\nand see\n\n    update refs/heads/mytopic1 ${NEW_mytopic1_HASH} ${OLD_mytopic1_HASH}\n    update refs/heads/mytopic2 ${NEW_mytopic2_HASH} ${OLD_mytopic2_HASH}\n    update refs/heads/mytopic3 ${NEW_mytopic3_HASH} ${OLD_mytopic3_HASH}\n\n(and yes, common commits from before will be replayed and then shared\nby the new branch versions.)\n\nIf the topics are totally contained within each other, you can do this\nmore simply as\n\n    $ git replay --contained --onto main main..my_tip_topic\n\nand still see all the update commands:\n\n    update refs/heads/mytopic1 ${NEW_mytopic1_HASH} ${OLD_mytopic1_HASH}\n    update refs/heads/mytopic2 ${NEW_mytopic2_HASH} ${OLD_mytopic2_HASH}\n    update refs/heads/mytopic3 ${NEW_mytopic3_HASH} ${OLD_mytopic3_HASH}\n    update refs/heads/my_tip_topic ${NEW_my_tip_topic_HASH}\n${OLD_my_tip_topic_HASH}\n\nThe option to list several refs provides a natural way of allowing\nusers to control what gets updated without updating all contained\nbranches, though rebase's implicit ranges doesn't really have a nice\nanalogue for you due to its implicit range handling.\n\nRight now, git-replay doesn't handle conflicts (it just die()s).  That\nand a few other thing are still TODO.\n\nHowever, git-replay does already handle basic replaying of merges,\nincluding ones with evil changes (so long as none of the merges\ninvolved have conflicts).  So, I'm hoping git-replay will also useful\nfor stuff like:\n\n   $ git replay --first-parent --interactive --onto next ^next\nspecial_topic seen\n\nwhere special_topic is one of the topics previously merged into seen.\nThis last commit would allow seen to be rebuilt with some changes to\nspecial_topic.  Basically, it makes whatever interactive changes are\nspecified to special_topic followed by replaying all the merges in\nnext..seen (including any necessary conflict resolutions or other\nsemantic changes recorded in the merge commits being replayed), unless\nthose .  However, I do need to figure out some syntax for keeping\nspecial_topic from being transplanted onto next here...\n"},{"id":"456780","messageId":"90ccd923-3552-fe88-5d6d-869def7f1aeb@gmail.com","threadId":"57949","inReplyTo":"3bb8baa2-e3e0-5cf1-aace-b0ba3ec6eb77@github.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-07T10:11:56Z","receivedAt":"2022-06-07T10:12:06Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/06/2022 16:12, Derrick Stolee wrote:\n> On 6/4/2022 11:28 AM, Phillip Wood wrote:\n>> On 03/06/2022 19:27, Taylor Blau wrote:\n>>> On Fri, Jun 03, 2022 at 01:37:48PM +0000, Derrick Stolee via GitGitGadget wrote:\n>>>> This is a feature I've wanted for quite a while. When working on the sparse\n>>>> index topic, I created a long RFC that actually broke into three topics for\n>>>> full review upstream. These topics were sequential, so any feedback on an\n>>>> earlier one required updates to the later ones. I would work on the full\n>>>> feature and use interactive rebase to update the full list of commits.\n>>>> However, I would need to update the branches pointing to those sub-topics.\n>>>\n>>> This is really exciting. I'm glad that you're working on it, because I\n>>> have also wanted this feature a handful of times over the years.\n>>\n>> Yes, thank you Stolee. I agree this will be useful, but I wonder if users\n>> will be confused that --update-refs only updates the branch heads that happen\n>> to be in the todo list, rather than updating all the branches that contain a\n>> rewritten commit. I think the latter is something we should try to add in the\n>> future and so we should take care to design this topic so that is possible.\n> \n> At the moment, the design adds a comment to the TODO list, showing which\n> branches are not possible to move because they are checked out at another\n> worktree (or is currently checked out and will be updated by the rebase\n> itself). That seems like a good place to insert alternative logic in the\n> future if we see a need for better behavior here.\n\nI think the question of whether to update branches that are checked out \nin another worktree is a question of whether it is less inconvenient to \nthe user to skip the ref update and leave the user to manually update \nthe branch or to update the ref and leave the worktree in a potentially \nawkward state if the user was half way through building a commit. The \nanswer probably depends on the preferences of the user.\n\nI've been using a script that updates the refs for all the branches \nbeing rewritten for a while and have found it preferable to always \nupdate the ref rather than have to do it manually. My script also \nupdates the worktree checkout to the new HEAD if there are no \nuncommitted changes which I have found very convenient. My preference is \nprobably because I tend not to have uncommitted changes lying around in \nthe worktrees whose branches get updated.\n\n> Unless: am I misunderstanding something about your concern here? Are you\n> worried about refs outside of refs/heads/*? Are you concerned about it\n> being _all_ refs/heads/* that we find?\n\nMy concerns are primarily about being able to extend the --update-ref \noption in a backwards compatible way.\n\nI'd like to be able to add functionality to rebase all refs/heads/* that \nare descended from the commits that a simple rebase would rewrite. Say I \nwant to edit $commit then I want the rebase to rewrite all the commits \nand update all the branches in in\n\n     git rev-list $(git for-each-ref --contains $commit refs/heads/) \n^$commit^@\n\nIdeally we'd avoid adding a new commandline option when adding that. I \nthink we could use an optional argument as you suggest below (though it \nwould not be a refspec).\n\n> One potential way to extend this (in the future) is to make --update-refs\n> take an optional string argument containing a refspec. This would replace\n> the default refspec of refs/heads/*.\n> [...]  \n>> Instead of using 'label' and 'exec' I'd prefer a new todo list command\n>> ('update-ref' or 'update-branch'?) used in place of 'label' that takes a\n>> branch name and updates the branch ref at the end of the rebase. That\n>> would make it easy to do all the updates in a single transaction as you\n>> suggested. Adding exec lines to do this makes the todo list messy and we\n>> have been trying to stop rebase forking all the time.\n> \n> Thanks. Yes, a new 'update-refs' step at the end would be good to make\n> it clear we want to rewrite the refs in one go without a possible\n> interruption from the user.\n\nThat's great, it is a bit more work for you but I think it gives a much \nnicer UI.\n\nBest Wishes\n\nPhillip\n\n> Thanks,\n> -Stolee\n"},{"id":"456781","messageId":"c9a5369a-8c87-0ee8-bed7-16b2cd58717b@gmail.com","threadId":"57949","inReplyTo":"23fa663886405596370e5fc95f679ef299f0725c.1654263472.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/4] rebase: add --update-refs option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-07T10:25:01Z","receivedAt":"2022-06-07T10:25:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nJust a couple of minor comments.\n\nOn 03/06/2022 14:37, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> [...]\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 262fb01aec0..866554fc978 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -609,6 +609,13 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n>   start would be overridden by the presence of\n>   `rebase.rescheduleFailedExec=true` configuration.\n>   \n> +--update-refs::\n> +--no-update-refs::\n> +\tAutomatically force-update any branches that point to commits that\n> +\tare being rebased. Any branches that are checked out in a worktree\n> +\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n> +\tin this way.\n> +\n>   INCOMPATIBLE OPTIONS\n>   --------------------\n\nWe should add --update-refs to the list of options that are incompatible \nwith --apply.\n\n> diff --git a/sequencer.c b/sequencer.c\n> index 8c3ed3532ac..d6151af9849 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -35,6 +35,8 @@\n>   #include \"commit-reach.h\"\n>   #include \"rebase-interactive.h\"\n>   #include \"reset.h\"\n> +#include \"branch.h\"\n> +#include \"log-tree.h\"\n>   \n>   #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>   \n> @@ -5603,10 +5605,104 @@ static int skip_unnecessary_picks(struct repository *r,\n>   \treturn 0;\n>   }\n>   \n> +struct todo_add_branch_context {\n> +\tstruct todo_list new_list;\n\nRather than using a struct todo_list I think it would be simpler overall \nto add\n\tstruct todo_list_item *item;\n\tsize_t nr, alloc;\n\ninstead, as I found it confusing that we were (correctly) using the \nstrbuf of the old list when adding the update-ref line to the new list.\n\n> +\tstruct strbuf *buf;\n> +\tstruct commit *commit;\n> +};\n> [...]\n> +\tfor (i = 0; i < todo_list->nr; ) {\n> +\t\tstruct todo_item *item = &todo_list->items[i];\n> +\n> +\t\tdo {\n> +\t\t\t/* insert ith item into new list */\n> +\t\t\tALLOC_GROW(ctx.new_list.items,\n> +\t\t\t\t   ctx.new_list.nr + 1,\n> +\t\t\t\t   ctx.new_list.alloc);\n> +\n> +\t\t\tmemcpy(&ctx.new_list.items[ctx.new_list.nr++],\n> +\t\t\t       &todo_list->items[i],\n> +\t\t\t       sizeof(struct todo_item));\n\nMay be\n\tctx.new_list.items[ctx.new_list.nr++] = todo_list->items[i++];\nwould be clearer\n\nBest Wishes\n\nPhillip\n"},{"id":"456817","messageId":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.git.1654263472.gitgitgadget@gmail.com","subject":"[PATCH v2 0/7] rebase: update branches in multi-part topic","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:42Z","receivedAt":"2022-06-08T00:27:58Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This is a feature I've wanted for quite a while. When working on the sparse\nindex topic, I created a long RFC that actually broke into three topics for\nfull review upstream. These topics were sequential, so any feedback on an\nearlier one required updates to the later ones. I would work on the full\nfeature and use interactive rebase to update the full list of commits.\nHowever, I would need to update the branches pointing to those sub-topics.\n\nThis series adds a new --update-refs option to 'git rebase' (along with a\nrebase.updateRefs config option) that adds 'label' and 'update-refs'\ncommands into the TODO list. This is powered by the commit decoration\nmachinery.\n\nAs an example, here is my in-progress bundle URI RFC split into subtopics as\nthey appear during the TODO list of a git rebase -i --update-refs:\n\npick 2d966282ff3 docs: document bundle URI standard\npick 31396e9171a remote-curl: add 'get' capability\npick 54c6ab70f67 bundle-uri: create basic file-copy logic\npick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\npick 6adaf842684 fetch: add --bundle-uri option\npick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\nlabel for-update-refs/refs/heads/bundle-redo/fetch\n\npick 1e3f6546632 clone: add --bundle-uri option\npick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\nlabel for-update-refs/refs/heads/bundle-redo/clone\n\npick 5451cb6599c bundle-uri: create bundle_list struct and helpers\npick 3029c3aca15 bundle-uri: create base key-value pair parsing\npick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\npick 92625a47673 bundle-uri: unit test \"key=value\" parsing\npick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\npick 9d6809a8d53 bundle-uri: parse bundle list in config format\npick 287a732b54c bundle-uri: fetch a list of bundles\nlabel for-update-refs/refs/heads/bundle-redo/list\n\npick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\npick 520204dcd1c bundle-uri client: add minimal NOOP client\npick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\npick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\npick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\npick caf4599a81d bundle-uri: allow relative URLs in bundle lists\npick df255000b7e bundle-uri: download bundles from an advertised list\npick d71beabf199 clone: unbundle the advertised bundles\npick c9578391976 t5601: basic bundle URI tests\n# Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n\nlabel for-update-refs/refs/heads/bundle-redo/advertise\n\nupdate-refs\n\n\n * Patches 1-2 are basic refactors that we will need for our implementation.\n * Patch 3 is a cleanup of an array with indexes given by the todo_command\n   enum.\n * Patches 4-6 implement the feature incrementally. For instance, we can\n   test the edits to the TODO file in patch 5, but we don't actually do\n   anything when seeing the 'update-refs' command until patch 6.\n * Patch 7 adds the rebase.updateRefs config option similar to\n   rebase.autoSquash.\n\n\nUpdates in v2\n=============\n\nAs recommended by the excellent feedback, I have removed the 'exec' commands\nin favor of the 'label' commands and a new 'update-refs' command at the very\nend. This way, there is only one step that updates all of the refs at the\nend instead of updating refs during the rebase. If a user runs 'git rebase\n--abort' in the middle, then their refs are still where they need to be.\n\nBased on some of the discussion, it seemed like one way to do this would be\nto have an 'update-ref ' command that would take the place of these 'label'\ncommands. However, this would require two things that make it a bit awkward:\n\n 1. We would need to replicate the storage of those positions during the\n    rebase. 'label' already does this pretty well. I've added the\n    \"for-update-refs/\" label to help here.\n 2. If we want to close out all of the refs as the rebase is finishing, then\n    that \"step\" becomes invisible to the user (and a bit more complicated to\n    insert). Thus, the 'update-refs' step performs this action. If the user\n    wants to do things after that step, then they can do so by editing the\n    TODO list.\n\nOther updates:\n\n * The 'keep_decorations' parameter was renamed to 'update_refs'.\n * I added tests for --rebase-merges=rebase-cousins to show how these labels\n   interact with other labels and merge commands.\n * I changed the order of the insertion of these update-refs labels to be\n   before the fixups are rearranged. This fixes a bug where the tip commit\n   is a fixup! so its decorations are never inspected (and they would be in\n   the wrong place even if they were). The fixup! commands are properly\n   inserted between a pick and its following label command. Tests\n   demonstrate this is correct.\n * Numerous style choices are updated based on feedback.\n\nThank you for all of the detailed review and ideas in this space. I\nappreciate any more ideas that can make this feature as effective as it can\nbe.\n\nThanks, -Stolee\n\nDerrick Stolee (7):\n  log-tree: create for_each_decoration()\n  branch: add branch_checked_out() helper\n  sequencer: define array with enum values\n  sequencer: add update-refs command\n  rebase: add --update-refs option\n  sequencer: implement 'update-refs' command\n  rebase: add rebase.updateRefs config option\n\n Documentation/config/rebase.txt |   3 +\n Documentation/git-rebase.txt    |  12 ++\n branch.c                        |  24 ++--\n branch.h                        |   8 ++\n builtin/rebase.c                |  10 ++\n log-tree.c                      | 111 +++++++++++------\n log-tree.h                      |   4 +\n sequencer.c                     | 206 +++++++++++++++++++++++++++++---\n sequencer.h                     |   2 +\n t/t3404-rebase-interactive.sh   | 110 +++++++++++++++++\n 10 files changed, 432 insertions(+), 58 deletions(-)\n\n\nbase-commit: 2668e3608e47494f2f10ef2b6e69f08a84816bcb\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1247\n\nRange-diff vs v1:\n\n 1:  4f9f3487641 = 1:  4f9f3487641 log-tree: create for_each_decoration()\n 2:  5f54766e103 = 2:  5f54766e103 branch: add branch_checked_out() helper\n -:  ----------- > 3:  9f261c7df2c sequencer: define array with enum values\n -:  ----------- > 4:  842b2186d25 sequencer: add update-refs command\n 3:  23fa6638864 ! 5:  0a4c110127b rebase: add --update-refs option\n     @@ Commit message\n          reachable from those \"sub branches\". It can take a manual step to update\n          those branches.\n      \n     -    Add a new --update-refs option to 'git rebase -i' that adds 'git\n     -    update-ref' exec steps to the todo file whenever a commit that is being\n     -    rebased is decorated with that <ref>. This allows the user to rebase a\n     -    long list of commits in a multi-part feature and keep all of their\n     -    pointers to those parts.\n     +    Add a new --update-refs option to 'git rebase -i' that adds 'label\n     +    for-update-refs/*' steps to the todo file whenever a commit that is\n     +    being rebased is decorated with that <ref>. At the very end, the\n     +    'update-refs' step is added to update all of the branches referenced by\n     +    the 'label' steps. This allows the user to rebase a long list of commits\n     +    in a multi-part feature and keep all of their pointers to those parts.\n     +\n     +    NOTE: This change only introduce the --update-refs option and implements\n     +    the changes to the todo file. It does _not_ yet implement the action\n     +    taken by the 'update-refs' todo step, which will be implemented and\n     +    tested in a later change.\n      \n          Use the new for_each_decoration() while iterating over the existing todo\n          list. Be sure to iterate after any squashing or fixups are placed.\n     @@ Documentation/git-rebase.txt: provided. Otherwise an explicit `--no-reschedule-f\n       INCOMPATIBLE OPTIONS\n       --------------------\n       \n     +@@ Documentation/git-rebase.txt: are incompatible with the following options:\n     +  * --empty=\n     +  * --reapply-cherry-picks\n     +  * --edit-todo\n     ++ * --update-refs\n     +  * --root when used in combination with --onto\n     + \n     + In addition, the following pairs of options are incompatible:\n      \n       ## builtin/rebase.c ##\n      @@ builtin/rebase.c: struct rebase_options {\n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n       }\n       \n      +struct todo_add_branch_context {\n     -+\tstruct todo_list new_list;\n     ++\tstruct todo_item *items;\n     ++\tsize_t items_nr;\n     ++\tsize_t items_alloc;\n      +\tstruct strbuf *buf;\n      +\tstruct commit *commit;\n      +};\n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n      +{\n      +\tstruct todo_add_branch_context *ctx = data;\n      +\tsize_t base_offset = ctx->buf->len;\n     -+\tint i = ctx->new_list.nr;\n      +\tstruct todo_item *item;\n      +\tchar *path;\n      +\n     -+\tALLOC_GROW(ctx->new_list.items,\n     -+\t\t   ctx->new_list.nr + 1,\n     -+\t\t   ctx->new_list.alloc);\n     -+\titem = &ctx->new_list.items[i];\n     ++\tALLOC_GROW(ctx->items,\n     ++\t\t   ctx->items_nr + 1,\n     ++\t\t   ctx->items_alloc);\n     ++\titem = &ctx->items[ctx->items_nr];\n     ++\tmemset(item, 0, sizeof(*item));\n      +\n      +\t/* If the branch is checked out, then leave a comment instead. */\n      +\tif (branch_checked_out(d->name, &path)) {\n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n      +\t\t\t    d->name, path);\n      +\t\tfree(path);\n      +\t} else {\n     -+\t\titem->command = TODO_EXEC;\n     -+\t\tstrbuf_addf(ctx->buf, \"git update-ref %s HEAD %s\\n\",\n     -+\t\t\t    d->name, oid_to_hex(&ctx->commit->object.oid));\n     ++\t\titem->command = TODO_LABEL;\n     ++\t\tstrbuf_addf(ctx->buf, \"for-update-refs/%s\\n\", d->name);\n      +\t}\n      +\n     -+\titem->commit = NULL;\n      +\titem->offset_in_buf = base_offset;\n      +\titem->arg_offset = base_offset;\n      +\titem->arg_len = ctx->buf->len - base_offset;\n     -+\tctx->new_list.nr++;\n     ++\tctx->items_nr++;\n      +\n      +\treturn 0;\n      +}\n      +\n      +/*\n      + * For each 'pick' command, find out if the commit has a decoration in\n     -+ * refs/heads/. If so, then add a 'git branch -f' exec command after\n     -+ * that 'pick' (plus any following 'squash' or 'fixup' commands).\n     ++ * refs/heads/. If so, then add a 'label for-update-refs/' command.\n      + */\n     -+static int todo_list_add_branch_commands(struct todo_list *todo_list)\n     ++static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n      +{\n      +\tint i;\n      +\tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n      +\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config\n      +\t};\n      +\tstruct todo_add_branch_context ctx = {\n     -+\t\t.new_list = TODO_LIST_INIT,\n      +\t\t.buf = &todo_list->buf,\n      +\t};\n      +\n     ++\tctx.items_alloc = 2 * todo_list->nr + 1;\n     ++\tALLOC_ARRAY(ctx.items, ctx.items_alloc);\n     ++\n      +\tstring_list_append(&decorate_refs_include, \"refs/heads/\");\n      +\tload_ref_decorations(&decoration_filter, 0);\n      +\n      +\tfor (i = 0; i < todo_list->nr; ) {\n      +\t\tstruct todo_item *item = &todo_list->items[i];\n      +\n     -+\t\tdo {\n     -+\t\t\t/* insert ith item into new list */\n     -+\t\t\tALLOC_GROW(ctx.new_list.items,\n     -+\t\t\t\t   ctx.new_list.nr + 1,\n     -+\t\t\t\t   ctx.new_list.alloc);\n     ++\t\t/* insert ith item into new list */\n     ++\t\tALLOC_GROW(ctx.items,\n     ++\t\t\t   ctx.items_nr + 1,\n     ++\t\t\t   ctx.items_alloc);\n      +\n     -+\t\t\tmemcpy(&ctx.new_list.items[ctx.new_list.nr++],\n     -+\t\t\t       &todo_list->items[i],\n     -+\t\t\t       sizeof(struct todo_item));\n     ++\t\tctx.items[ctx.items_nr++] = todo_list->items[i++];\n      +\n     -+\t\t\ti++;\n     -+\t\t} while (i < todo_list->nr &&\n     -+\t\t\t todo_list->items[i].command != TODO_PICK);\n     -+\n     -+\t\tctx.commit = item->commit;\n     -+\t\tfor_each_decoration(item->commit, add_branch_for_decoration, &ctx);\n     ++\t\tif (item->commit) {\n     ++\t\t\tctx.commit = item->commit;\n     ++\t\t\tfor_each_decoration(item->commit,\n     ++\t\t\t\t\t    add_branch_for_decoration,\n     ++\t\t\t\t\t    &ctx);\n     ++\t\t}\n      +\t}\n      +\n     ++\t/* Add the \"update-refs\" step. */\n     ++\tALLOC_GROW(ctx.items,\n     ++\t\t   ctx.items_nr + 1,\n     ++\t\t   ctx.items_alloc);\n     ++\tmemset(&ctx.items[ctx.items_nr], 0, sizeof(struct todo_item));\n     ++\tctx.items[ctx.items_nr].command = TODO_UPDATE_REFS;\n     ++\tctx.items_nr++;\n     ++\n      +\tfree(todo_list->items);\n     -+\ttodo_list->items = ctx.new_list.items;\n     -+\ttodo_list->nr = ctx.new_list.nr;\n     -+\ttodo_list->alloc = ctx.new_list.alloc;\n     ++\ttodo_list->items = ctx.items;\n     ++\ttodo_list->nr = ctx.items_nr;\n     ++\ttodo_list->alloc = ctx.items_alloc;\n      +\n      +\treturn 0;\n      +}\n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n       \t\t    const char *shortrevisions, const char *onto_name,\n       \t\t    struct commit *onto, const struct object_id *orig_head,\n       \t\t    struct string_list *commands, unsigned autosquash,\n     -+\t\t    unsigned keep_decorations,\n     ++\t\t    unsigned update_refs,\n       \t\t    struct todo_list *todo_list)\n       {\n       \tchar shortonto[GIT_MAX_HEXSZ + 1];\n      @@ sequencer.c: int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n     - \tif (autosquash && todo_list_rearrange_squash(todo_list))\n     - \t\treturn -1;\n     + \t\titem->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n     + \t}\n       \n     -+\tif (keep_decorations && todo_list_add_branch_commands(todo_list))\n     ++\tif (update_refs && todo_list_add_update_ref_commands(todo_list))\n      +\t\treturn -1;\n      +\n     - \tif (commands->nr)\n     - \t\ttodo_list_add_exec_commands(todo_list, commands);\n     + \tif (autosquash && todo_list_rearrange_squash(todo_list))\n     + \t\treturn -1;\n       \n      \n       ## sequencer.h ##\n     @@ sequencer.h: int complete_action(struct repository *r, struct replay_opts *opts,\n       \t\t    const char *shortrevisions, const char *onto_name,\n       \t\t    struct commit *onto, const struct object_id *orig_head,\n       \t\t    struct string_list *commands, unsigned autosquash,\n     -+\t\t    unsigned keep_decorations,\n     ++\t\t    unsigned update_refs,\n       \t\t    struct todo_list *todo_list);\n       int todo_list_rearrange_squash(struct todo_list *todo_list);\n       \n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correct\n       \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n       '\n       \n     -+test_expect_success '--update-refs adds git branch commands' '\n     ++test_expect_success '--update-refs adds label and update-ref commands' '\n      +\tgit checkout -b update-refs no-conflict-branch &&\n     -+\ttest_commit extra fileX &&\n     -+\tgit commit --amend --fixup=L &&\n     ++\tgit branch -f base HEAD~4 &&\n     ++\tgit branch -f first HEAD~3 &&\n     ++\tgit branch -f second HEAD~3 &&\n     ++\tgit branch -f third HEAD~1 &&\n     ++\tgit commit --allow-empty --fixup=third &&\n     ++\tgit branch -f shared-tip &&\n      +\t(\n      +\t\tset_cat_todo_editor &&\n     -+\t\tgit branch -f base A &&\n     -+\t\tgit branch -f first J &&\n     -+\t\tgit branch -f second J &&\n     -+\t\tgit branch -f third L &&\n     -+\n     -+\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n      +\n      +\t\tcat >expect <<-EOF &&\n      +\t\tpick $(git log -1 --format=%h J) J\n     -+\t\texec git update-ref refs/heads/second HEAD $(git rev-parse J)\n     -+\t\texec git update-ref refs/heads/first HEAD $(git rev-parse  J)\n     ++\t\tlabel for-update-refs/refs/heads/second\n     ++\t\tlabel for-update-refs/refs/heads/first\n      +\t\tpick $(git log -1 --format=%h K) K\n      +\t\tpick $(git log -1 --format=%h L) L\n     -+\t\tfixup $(git log -1 --format=%h update-refs) fixup! L\n     -+\t\texec git update-ref refs/heads/third HEAD $(git rev-parse L)\n     ++\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n     ++\t\tlabel for-update-refs/refs/heads/third\n      +\t\tpick $(git log -1 --format=%h M) M\n     -+\t\texec git update-ref refs/heads/no-conflict-branch HEAD $(git rev-parse M)\n     ++\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n     ++\t\tlabel for-update-refs/refs/heads/shared-tip\n     ++\t\tupdate-refs\n      +\t\tEOF\n      +\n     ++\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n     ++\t\ttest_cmp expect todo\n     ++\t)\n     ++'\n     ++\n     ++test_expect_success '--update-refs adds commands with --rebase-merges' '\n     ++\tgit checkout -b update-refs-with-merge no-conflict-branch &&\n     ++\tgit branch -f base HEAD~4 &&\n     ++\tgit branch -f first HEAD~3 &&\n     ++\tgit branch -f second HEAD~3 &&\n     ++\tgit branch -f third HEAD~1 &&\n     ++\tgit merge -m merge branch2 &&\n     ++\tgit branch -f merge-branch &&\n     ++\tgit commit --fixup=third --allow-empty &&\n     ++\t(\n     ++\t\tset_cat_todo_editor &&\n     ++\n     ++\t\tcat >expect <<-EOF &&\n     ++\t\tlabel onto\n     ++\t\treset onto\n     ++\t\tpick $(git log -1 --format=%h branch2~1) F\n     ++\t\tpick $(git log -1 --format=%h branch2) I\n     ++\t\tlabel for-update-refs/refs/heads/branch2\n     ++\t\tlabel merge\n     ++\t\treset onto\n     ++\t\tpick $(git log -1 --format=%h refs/heads/second) J\n     ++\t\tlabel for-update-refs/refs/heads/second\n     ++\t\tlabel for-update-refs/refs/heads/first\n     ++\t\tpick $(git log -1 --format=%h refs/heads/third~1) K\n     ++\t\tpick $(git log -1 --format=%h refs/heads/third) L\n     ++\t\tfixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n     ++\t\tlabel for-update-refs/refs/heads/third\n     ++\t\tpick $(git log -1 --format=%h HEAD~2) M\n     ++\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n     ++\t\tmerge -C $(git log -1 --format=%h HEAD~1) merge # merge\n     ++\t\tlabel for-update-refs/refs/heads/merge-branch\n     ++\t\tupdate-refs\n     ++\t\tEOF\n     ++\n     ++\t\ttest_must_fail git rebase -i --autosquash \\\n     ++\t\t\t\t   --rebase-merges=rebase-cousins \\\n     ++\t\t\t\t   --update-refs primary >todo &&\n     ++\n      +\t\ttest_cmp expect todo\n      +\t)\n      +'\n -:  ----------- > 6:  68f8e51b19c sequencer: implement 'update-refs' command\n 4:  b99c5bf34ef ! 7:  3d7d3f656b4 rebase: add rebase.updateRefs config option\n     @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v\n       \t\treturn 0;\n      \n       ## t/t3404-rebase-interactive.sh ##\n     -@@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds git branch commands' '\n     - \t\texec git update-ref refs/heads/no-conflict-branch HEAD $(git rev-parse M)\n     +@@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds label and update-ref commands' '\n       \t\tEOF\n       \n     + \t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n      +\t\ttest_cmp expect todo &&\n      +\n      +\t\ttest_must_fail git -c rebase.autosquash=true \\\n      +\t\t\t\t   -c rebase.updaterefs=true \\\n      +\t\t\t\t   rebase -i primary >todo &&\n     ++\n     + \t\ttest_cmp expect todo\n     + \t)\n     + '\n     +@@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands with --rebase-merges' '\n     + \t\t\t\t   --rebase-merges=rebase-cousins \\\n     + \t\t\t\t   --update-refs primary >todo &&\n     + \n     ++\t\ttest_cmp expect todo &&\n     ++\n     ++\t\ttest_must_fail git -c rebase.autosquash=true \\\n     ++\t\t\t\t   -c rebase.updaterefs=true \\\n     ++\t\t\t\t   rebase -i \\\n     ++\t\t\t\t   --rebase-merges=rebase-cousins \\\n     ++\t\t\t\t   primary >todo &&\n     ++\n       \t\ttest_cmp expect todo\n       \t)\n       '\n\n-- \ngitgitgadget\n"},{"id":"456830","messageId":"4f9f34876413927d819313a70fcdefcad5b35689.1654634569.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v2 1/7] log-tree: create for_each_decoration()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:43Z","receivedAt":"2022-06-08T00:33:40Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nIt can be helpful to iterate through all the decorations on a commit\nwithout necessarily writing them to a stream. Implement\nfor_each_decoration() and reimplement format_decorations_extended() to\nuse that iterator.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n log-tree.c | 111 ++++++++++++++++++++++++++++++++++++-----------------\n log-tree.h |   4 ++\n 2 files changed, 80 insertions(+), 35 deletions(-)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex d0ac0a6327a..b15a7c9db22 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -282,6 +282,54 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio\n \t\tstrbuf_addstr(sb, decoration->name);\n }\n \n+struct format_decorations_context {\n+\tstruct strbuf *sb;\n+\tint use_color;\n+\tconst char *prefix;\n+\tconst char *separator;\n+\tconst char *suffix;\n+\tconst char *color_commit;\n+\tconst char *color_reset;\n+\tconst struct name_decoration *current_and_HEAD;\n+};\n+\n+static int append_decoration(const struct name_decoration *d,\n+\t\t\t     void *data)\n+{\n+\tstruct format_decorations_context *ctx = data;\n+\t/*\n+\t * When both current and HEAD are there, only\n+\t * show HEAD->current where HEAD would have\n+\t * appeared, skipping the entry for current.\n+\t */\n+\tif (d != ctx->current_and_HEAD) {\n+\t\tstrbuf_addstr(ctx->sb, ctx->color_commit);\n+\t\tstrbuf_addstr(ctx->sb, ctx->prefix);\n+\t\tstrbuf_addstr(ctx->sb, ctx->color_reset);\n+\t\tstrbuf_addstr(ctx->sb, decorate_get_color(ctx->use_color, d->type));\n+\t\tif (d->type == DECORATION_REF_TAG)\n+\t\t\tstrbuf_addstr(ctx->sb, \"tag: \");\n+\n+\t\tshow_name(ctx->sb, d);\n+\n+\t\tif (ctx->current_and_HEAD &&\n+\t\t    d->type == DECORATION_REF_HEAD) {\n+\t\t\tstrbuf_addstr(ctx->sb, \" -> \");\n+\t\t\tstrbuf_addstr(ctx->sb, ctx->color_reset);\n+\t\t\tstrbuf_addstr(ctx->sb,\n+\t\t\t\t      decorate_get_color(\n+\t\t\t\t\t\tctx->use_color,\n+\t\t\t\t\t\tctx->current_and_HEAD->type));\n+\t\t\tshow_name(ctx->sb, ctx->current_and_HEAD);\n+\t\t}\n+\t\tstrbuf_addstr(ctx->sb, ctx->color_reset);\n+\n+\t\tctx->prefix = ctx->separator;\n+\t}\n+\n+\treturn 0;\n+}\n+\n /*\n  * The caller makes sure there is no funny color before calling.\n  * format_decorations_extended makes sure the same after return.\n@@ -294,49 +342,42 @@ void format_decorations_extended(struct strbuf *sb,\n \t\t\tconst char *suffix)\n {\n \tconst struct name_decoration *decoration;\n-\tconst struct name_decoration *current_and_HEAD;\n-\tconst char *color_commit =\n-\t\tdiff_get_color(use_color, DIFF_COMMIT);\n-\tconst char *color_reset =\n-\t\tdecorate_get_color(use_color, DECORATION_NONE);\n+\tstruct format_decorations_context ctx = {\n+\t\t.sb = sb,\n+\t\t.use_color = use_color,\n+\t\t.prefix = prefix,\n+\t\t.separator = separator,\n+\t\t.suffix = suffix,\n+\t\t.color_commit =\tdiff_get_color(use_color, DIFF_COMMIT),\n+\t\t.color_reset = decorate_get_color(use_color, DECORATION_NONE),\n+\t};\n \n \tdecoration = get_name_decoration(&commit->object);\n \tif (!decoration)\n \t\treturn;\n \n-\tcurrent_and_HEAD = current_pointed_by_HEAD(decoration);\n-\twhile (decoration) {\n-\t\t/*\n-\t\t * When both current and HEAD are there, only\n-\t\t * show HEAD->current where HEAD would have\n-\t\t * appeared, skipping the entry for current.\n-\t\t */\n-\t\tif (decoration != current_and_HEAD) {\n-\t\t\tstrbuf_addstr(sb, color_commit);\n-\t\t\tstrbuf_addstr(sb, prefix);\n-\t\t\tstrbuf_addstr(sb, color_reset);\n-\t\t\tstrbuf_addstr(sb, decorate_get_color(use_color, decoration->type));\n-\t\t\tif (decoration->type == DECORATION_REF_TAG)\n-\t\t\t\tstrbuf_addstr(sb, \"tag: \");\n-\n-\t\t\tshow_name(sb, decoration);\n-\n-\t\t\tif (current_and_HEAD &&\n-\t\t\t    decoration->type == DECORATION_REF_HEAD) {\n-\t\t\t\tstrbuf_addstr(sb, \" -> \");\n-\t\t\t\tstrbuf_addstr(sb, color_reset);\n-\t\t\t\tstrbuf_addstr(sb, decorate_get_color(use_color, current_and_HEAD->type));\n-\t\t\t\tshow_name(sb, current_and_HEAD);\n-\t\t\t}\n-\t\t\tstrbuf_addstr(sb, color_reset);\n+\tctx.current_and_HEAD = current_pointed_by_HEAD(decoration);\n \n-\t\t\tprefix = separator;\n-\t\t}\n+\tfor_each_decoration(commit, append_decoration, &ctx);\n+\n+\tstrbuf_addstr(sb, ctx.color_commit);\n+\tstrbuf_addstr(sb, ctx.suffix);\n+\tstrbuf_addstr(sb, ctx.color_reset);\n+}\n+\n+int for_each_decoration(const struct commit *c, decoration_fn fn, void *data)\n+{\n+\tconst struct name_decoration *decoration;\n+\n+\tdecoration = get_name_decoration(&c->object);\n+\twhile (decoration) {\n+\t\tint res;\n+\t\tif ((res = fn(decoration, data)))\n+\t\t\treturn res;\n \t\tdecoration = decoration->next;\n \t}\n-\tstrbuf_addstr(sb, color_commit);\n-\tstrbuf_addstr(sb, suffix);\n-\tstrbuf_addstr(sb, color_reset);\n+\n+\treturn 0;\n }\n \n void show_decorations(struct rev_info *opt, struct commit *commit)\ndiff --git a/log-tree.h b/log-tree.h\nindex e7e4641cf83..ea07da2625b 100644\n--- a/log-tree.h\n+++ b/log-tree.h\n@@ -35,4 +35,8 @@ void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);\n void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *);\n void fmt_output_email_subject(struct strbuf *, struct rev_info *);\n \n+typedef int decoration_fn(const struct name_decoration *d,\n+\t\t\t  void *data);\n+int for_each_decoration(const struct commit *c, decoration_fn fn, void *data);\n+\n #endif\n-- \ngitgitgadget\n\n"},{"id":"456836","messageId":"xmqqmteot98j.fsf@gitster.g","threadId":"57949","inReplyTo":"9f261c7df2c0a433c6acc46ffefd8d2633fdc3d4.1654634569.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/7] sequencer: define array with enum values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-07T22:11:08Z","receivedAt":"2022-06-08T00:33:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The todo_command_info array defines which strings match with which\n> todo_command enum values. The array is defined in the same order as the\n> enum values, but if one changed without the other, then we would have\n> unexpected results.\n>\n> Make it easier to see changes to the enum and this array by using the\n> enum values as the indices of the array.\n>\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>  sequencer.c | 28 ++++++++++++++--------------\n>  1 file changed, 14 insertions(+), 14 deletions(-)\n\nGood thinking.\n\nI love seeing a future-proofing change like this.\n\nThanks.\n\n> diff --git a/sequencer.c b/sequencer.c\n> index 8c3ed3532ac..8e26c9a6261 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -1685,20 +1685,20 @@ static struct {\n>  \tchar c;\n>  \tconst char *str;\n>  } todo_command_info[] = {\n> -\t{ 'p', \"pick\" },\n> -\t{ 0,   \"revert\" },\n> -\t{ 'e', \"edit\" },\n> -\t{ 'r', \"reword\" },\n> -\t{ 'f', \"fixup\" },\n> -\t{ 's', \"squash\" },\n> -\t{ 'x', \"exec\" },\n> -\t{ 'b', \"break\" },\n> -\t{ 'l', \"label\" },\n> -\t{ 't', \"reset\" },\n> -\t{ 'm', \"merge\" },\n> -\t{ 0,   \"noop\" },\n> -\t{ 'd', \"drop\" },\n> -\t{ 0,   NULL }\n> +\t[TODO_PICK] = { 'p', \"pick\" },\n> +\t[TODO_REVERT] = { 0,   \"revert\" },\n> +\t[TODO_EDIT] = { 'e', \"edit\" },\n> +\t[TODO_REWORD] = { 'r', \"reword\" },\n> +\t[TODO_FIXUP] = { 'f', \"fixup\" },\n> +\t[TODO_SQUASH] = { 's', \"squash\" },\n> +\t[TODO_EXEC] = { 'x', \"exec\" },\n> +\t[TODO_BREAK] = { 'b', \"break\" },\n> +\t[TODO_LABEL] = { 'l', \"label\" },\n> +\t[TODO_RESET] = { 't', \"reset\" },\n> +\t[TODO_MERGE] = { 'm', \"merge\" },\n> +\t[TODO_NOOP] = { 0,   \"noop\" },\n> +\t[TODO_DROP] = { 'd', \"drop\" },\n> +\t[TODO_COMMENT] = { 0,   NULL },\n>  };\n>  \n>  static const char *command_to_string(const enum todo_command command)\n"},{"id":"456837","messageId":"9f261c7df2c0a433c6acc46ffefd8d2633fdc3d4.1654634569.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v2 3/7] sequencer: define array with enum values","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:45Z","receivedAt":"2022-06-08T01:20:39Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe todo_command_info array defines which strings match with which\ntodo_command enum values. The array is defined in the same order as the\nenum values, but if one changed without the other, then we would have\nunexpected results.\n\nMake it easier to see changes to the enum and this array by using the\nenum values as the indices of the array.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 8c3ed3532ac..8e26c9a6261 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1685,20 +1685,20 @@ static struct {\n \tchar c;\n \tconst char *str;\n } todo_command_info[] = {\n-\t{ 'p', \"pick\" },\n-\t{ 0,   \"revert\" },\n-\t{ 'e', \"edit\" },\n-\t{ 'r', \"reword\" },\n-\t{ 'f', \"fixup\" },\n-\t{ 's', \"squash\" },\n-\t{ 'x', \"exec\" },\n-\t{ 'b', \"break\" },\n-\t{ 'l', \"label\" },\n-\t{ 't', \"reset\" },\n-\t{ 'm', \"merge\" },\n-\t{ 0,   \"noop\" },\n-\t{ 'd', \"drop\" },\n-\t{ 0,   NULL }\n+\t[TODO_PICK] = { 'p', \"pick\" },\n+\t[TODO_REVERT] = { 0,   \"revert\" },\n+\t[TODO_EDIT] = { 'e', \"edit\" },\n+\t[TODO_REWORD] = { 'r', \"reword\" },\n+\t[TODO_FIXUP] = { 'f', \"fixup\" },\n+\t[TODO_SQUASH] = { 's', \"squash\" },\n+\t[TODO_EXEC] = { 'x', \"exec\" },\n+\t[TODO_BREAK] = { 'b', \"break\" },\n+\t[TODO_LABEL] = { 'l', \"label\" },\n+\t[TODO_RESET] = { 't', \"reset\" },\n+\t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_NOOP] = { 0,   \"noop\" },\n+\t[TODO_DROP] = { 'd', \"drop\" },\n+\t[TODO_COMMENT] = { 0,   NULL },\n };\n \n static const char *command_to_string(const enum todo_command command)\n-- \ngitgitgadget\n\n"},{"id":"456838","messageId":"5f54766e1032ebf3a331516a6dd696b997bdfdd8.1654634569.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v2 2/7] branch: add branch_checked_out() helper","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:44Z","receivedAt":"2022-06-08T01:20:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe validate_new_branchname() method contains a check to see if a branch\nis checked out in any non-bare worktree. This is intended to prevent a\nforce push that will mess up an existing checkout. This helper is not\nsuitable to performing just that check, because the method will die()\nwhen the branch is checked out instead of returning an error code.\n\nExtract branch_checked_out() and use it within\nvalidate_new_branchname(). Another caller will be added in a coming\nchange.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n branch.c | 24 ++++++++++++++++--------\n branch.h |  8 ++++++++\n 2 files changed, 24 insertions(+), 8 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 2d6569b0c62..2e6419cdfa5 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -369,6 +369,19 @@ int validate_branchname(const char *name, struct strbuf *ref)\n \treturn ref_exists(ref->buf);\n }\n \n+int branch_checked_out(const char *refname, char **path)\n+{\n+\tstruct worktree **worktrees = get_worktrees();\n+\tconst struct worktree *wt = find_shared_symref(worktrees, \"HEAD\", refname);\n+\tint result = wt && !wt->is_bare;\n+\n+\tif (result && path)\n+\t\t*path = xstrdup(wt->path);\n+\n+\tfree_worktrees(worktrees);\n+\treturn result;\n+}\n+\n /*\n  * Check if a branch 'name' can be created as a new branch; die otherwise.\n  * 'force' can be used when it is OK for the named branch already exists.\n@@ -377,9 +390,7 @@ int validate_branchname(const char *name, struct strbuf *ref)\n  */\n int validate_new_branchname(const char *name, struct strbuf *ref, int force)\n {\n-\tstruct worktree **worktrees;\n-\tconst struct worktree *wt;\n-\n+\tchar *path;\n \tif (!validate_branchname(name, ref))\n \t\treturn 0;\n \n@@ -387,13 +398,10 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force)\n \t\tdie(_(\"a branch named '%s' already exists\"),\n \t\t    ref->buf + strlen(\"refs/heads/\"));\n \n-\tworktrees = get_worktrees();\n-\twt = find_shared_symref(worktrees, \"HEAD\", ref->buf);\n-\tif (wt && !wt->is_bare)\n+\tif (branch_checked_out(ref->buf, &path))\n \t\tdie(_(\"cannot force update the branch '%s' \"\n \t\t      \"checked out at '%s'\"),\n-\t\t    ref->buf + strlen(\"refs/heads/\"), wt->path);\n-\tfree_worktrees(worktrees);\n+\t\t    ref->buf + strlen(\"refs/heads/\"), path);\n \n \treturn 1;\n }\ndiff --git a/branch.h b/branch.h\nindex 560b6b96a8f..5ea93d217b1 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -101,6 +101,14 @@ void create_branches_recursively(struct repository *r, const char *name,\n \t\t\t\t const char *tracking_name, int force,\n \t\t\t\t int reflog, int quiet, enum branch_track track,\n \t\t\t\t int dry_run);\n+\n+/*\n+ * Returns true if the branch at 'refname' is checked out at any\n+ * non-bare worktree. The path of the worktree is stored in the\n+ * given 'path', if provided.\n+ */\n+int branch_checked_out(const char *refname, char **path);\n+\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\n-- \ngitgitgadget\n\n"},{"id":"456841","messageId":"9354d1d3-c1b7-3baf-215f-30659ad48b22@github.com","threadId":"57949","inReplyTo":"90ccd923-3552-fe88-5d6d-869def7f1aeb@gmail.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-07T19:39:37Z","receivedAt":"2022-06-08T01:31:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/7/2022 6:11 AM, Phillip Wood wrote:\n> On 06/06/2022 16:12, Derrick Stolee wrote:\n>> On 6/4/2022 11:28 AM, Phillip Wood wrote:\n>>> On 03/06/2022 19:27, Taylor Blau wrote:\n>>>> On Fri, Jun 03, 2022 at 01:37:48PM +0000, Derrick Stolee via GitGitGadget wrote:\n>>>>> This is a feature I've wanted for quite a while. When working on the sparse\n>>>>> index topic, I created a long RFC that actually broke into three topics for\n>>>>> full review upstream. These topics were sequential, so any feedback on an\n>>>>> earlier one required updates to the later ones. I would work on the full\n>>>>> feature and use interactive rebase to update the full list of commits.\n>>>>> However, I would need to update the branches pointing to those sub-topics.\n>>>>\n>>>> This is really exciting. I'm glad that you're working on it, because I\n>>>> have also wanted this feature a handful of times over the years.\n>>>\n>>> Yes, thank you Stolee. I agree this will be useful, but I wonder if users\n>>> will be confused that --update-refs only updates the branch heads that happen\n>>> to be in the todo list, rather than updating all the branches that contain a\n>>> rewritten commit. I think the latter is something we should try to add in the\n>>> future and so we should take care to design this topic so that is possible.\n>>\n>> At the moment, the design adds a comment to the TODO list, showing which\n>> branches are not possible to move because they are checked out at another\n>> worktree (or is currently checked out and will be updated by the rebase\n>> itself). That seems like a good place to insert alternative logic in the\n>> future if we see a need for better behavior here.\n> \n> I think the question of whether to update branches that are checked out in\n> another worktree is a question of whether it is less inconvenient to the user\n> to skip the ref update and leave the user to manually update the branch or to> update the ref and leave the worktree in a potentially awkward state if the\n> user was half way through building a commit. The answer probably depends on\n> the preferences of the user.\n\nI think that their 'git status' will look strange no matter what: their\nworking directory and index could look significantly different from what\nthe branch at HEAD is reporting. For this situation, I would rather continue\npreventing these ref updates from underneath worktrees.\n \n> I've been using a script that updates the refs for all the branches being\n> rewritten for a while and have found it preferable to always update the ref\n> rather than have to do it manually. My script also updates the worktree\n> checkout to the new HEAD if there are no uncommitted changes which I have\n> found very convenient. My preference is probably because I tend not to have\n> uncommitted changes lying around in the worktrees whose branches get updated.\n\nActually updating the worktree to match seems like an interesting twist, which\nwe would want to consider if we go this route in the future.\n \n>> Unless: am I misunderstanding something about your concern here? Are you\n>> worried about refs outside of refs/heads/*? Are you concerned about it\n>> being _all_ refs/heads/* that we find?\n> \n> My concerns are primarily about being able to extend the --update-ref\n> option in a backwards compatible way.\n> \n> I'd like to be able to add functionality to rebase all refs/heads/* that\n> are descended from the commits that a simple rebase would rewrite. Say I want\n> to edit $commit then I want the rebase to rewrite all the commits and update\n> all the branches in in\n> \n>     git rev-list $(git for-each-ref --contains $commit refs/heads/) ^$commit^@\n> \n> Ideally we'd avoid adding a new commandline option when adding that. I think\n> we could use an optional argument as you suggest below (though it would not be\n> a refspec).\n\nThis is sort of the opposite of what my series is doing: you want to find all\nrefs that contain the current commits and make sure that they are _also_\nrebased as we rebase the current set of commits. That seems like an interesting\ndirection, with a very different set of complexities (ignoring other worktrees\nseems like much more of a blocker here).\n\nI would consider this to be _yet another mode_ that is separate from\n--update-refs, though it could share some underlying logic. It gets more\ncomplicated if there are merge commits involved (do we have a --rebase-merges\noption?).\n\nThis makes me think of earlier discussions around a \"git evolve\" command\n(based on Mercurial's evolve command). The idea is to have a different\nworkflow than I am presenting: instead of creating `fixup!` commits at the\ntop of a multi-part topic, you could check out a branch on a middle part\nand work forward from there. Then, you could \"rebase every branch with a\nrewritten commit\" to get things back into a nice line.\n\nIn conclusion: I don't think we should go down this rabbit hole right now.\nI think that --update-refs would be something we want to enable by default\nif we have such a mode, though.\n \n>> One potential way to extend this (in the future) is to make --update-refs\n>> take an optional string argument containing a refspec. This would replace\n>> the default refspec of refs/heads/*.\n>> [...] \n>>> Instead of using 'label' and 'exec' I'd prefer a new todo list command\n>>> ('update-ref' or 'update-branch'?) used in place of 'label' that takes a\n>>> branch name and updates the branch ref at the end of the rebase. That\n>>> would make it easy to do all the updates in a single transaction as you\n>>> suggested. Adding exec lines to do this makes the todo list messy and we\n>>> have been trying to stop rebase forking all the time.\n>>\n>> Thanks. Yes, a new 'update-refs' step at the end would be good to make\n>> it clear we want to rewrite the refs in one go without a possible\n>> interruption from the user.\n> \n> That's great, it is a bit more work for you but I think it gives a much\n> nicer UI.\n\nThanks. After some hurdles on my end (and some additional complexities\ndiscovered in the process) I will have v2 ready soon.\n\nThanks,\n-Stolee\n"},{"id":"456842","messageId":"xmqqr140t9am.fsf@gitster.g","threadId":"57949","inReplyTo":"5f54766e1032ebf3a331516a6dd696b997bdfdd8.1654634569.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/7] branch: add branch_checked_out() helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-07T22:09:53Z","receivedAt":"2022-06-08T01:42:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +int branch_checked_out(const char *refname, char **path)\n> +{\n> +\tstruct worktree **worktrees = get_worktrees();\n> +\tconst struct worktree *wt = find_shared_symref(worktrees, \"HEAD\", refname);\n> +\tint result = wt && !wt->is_bare;\n> +\n> +\tif (result && path)\n> +\t\t*path = xstrdup(wt->path);\n> +\n> +\tfree_worktrees(worktrees);\n> +\treturn result;\n> +}\n\nDon't you plan to call this repeatedly from the for_each_deco\niteration?  I am wondering if it should take the result of\nget_worktrees() and reuse the result of get_worktrees(), instead of\nenumerating the list of worktrees and freeing for each of the\nbranches you need to inspect.\n\nThere also was another topic\n\nhttps://lore.kernel.org/git/pull.1266.v2.git.git.1652690501963.gitgitgadget@gmail.com/\n\nthat was triggered by find_shared_symref() being relatively\nheavy-weight, which suggests a more involved refactoring.\n\nI wonder if we rather want to rewrite find_shared_symref() *not* to\ntake the target parameter at all, and instead introduce a new\nfunction that takes a worktree, and report the branch that is\nchecked out (or being operated on via rebase or bisect).  Then we\ncan\n\n - create a strset out of its result, i.e. set of branches that\n   should not be touched;\n\n - iterate over refs that point into the history being rebased\n   (using for_each_decoration()), and consult that strset to see if\n   any of them is being rewritten.\n\nWith the API of find_shared_symref(), we'd need to iterate over all\nworktrees for each decoration.  With such a restructuring, we can\niterate over all worktrees just once, and match the result with\ndecoration, so the problem becomes O(N)+O(M) and not O(N*M) for\nnumber of worktrees N and number of decorations M.\n"},{"id":"456843","messageId":"xmqqilpct8oj.fsf@gitster.g","threadId":"57949","inReplyTo":"68f8e51b19c024e1cb075dffd07f81cceaba5957.1654634569.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 6/7] sequencer: implement 'update-refs' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-07T22:23:08Z","receivedAt":"2022-06-08T02:15:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The previous change allowed 'git rebase --update-refs' to create 'label'\n> commands for each branch  among the commits being rewritten and add an\n> 'update-refs' command at the end of the todo list. Now, teach Git to\n> update the refs during that final 'update-refs' command.\n>\n> We need to create an array of new and old OIDs for each ref by iterating\n> over the refs/rewritten/for-update-refs/ namespace. We cannot update the\n> refs in-place since this will confuse the refs iterator.\n\nIn other words, grab everything we need to do and then use that\nin-core information to tell refs API what refs to change to what\nvalues?  That dounds like a quite reasonable thing to do.\n\nLooking at the patch text, the only thing that stands out at me is\nthat this does not seem to perform the updates in a single\ntransaction, which may often not matter in practice, but may be a\nprudent thing to do anyway at philosophical level.\n\nThanks, will queue.\n"},{"id":"456845","messageId":"842b2186d250aca367e9680105d4d15f8cbb098e.1654634569.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v2 4/7] sequencer: add update-refs command","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:46Z","receivedAt":"2022-06-08T02:16:17Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nAdd the boilerplat for an \"update-refs\" command in the sequencer. This\nconnects to the current no-op do_update_refs() which will be filled in\nafter more connections are created.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c | 13 ++++++++++++-\n sequencer.h |  1 +\n 2 files changed, 13 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 8e26c9a6261..68f7c76e896 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1696,6 +1696,7 @@ static struct {\n \t[TODO_LABEL] = { 'l', \"label\" },\n \t[TODO_RESET] = { 't', \"reset\" },\n \t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_UPDATE_REFS] = { 'u', \"update-refs\" },\n \t[TODO_NOOP] = { 0,   \"noop\" },\n \t[TODO_DROP] = { 'd', \"drop\" },\n \t[TODO_COMMENT] = { 0,   NULL },\n@@ -2442,7 +2443,9 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,\n \tpadding = strspn(bol, \" \\t\");\n \tbol += padding;\n \n-\tif (item->command == TODO_NOOP || item->command == TODO_BREAK) {\n+\tif (item->command == TODO_NOOP ||\n+\t    item->command == TODO_BREAK ||\n+\t    item->command == TODO_UPDATE_REFS) {\n \t\tif (bol != eol)\n \t\t\treturn error(_(\"%s does not accept arguments: '%s'\"),\n \t\t\t\t     command_to_string(item->command), bol);\n@@ -4056,6 +4059,11 @@ leave_merge:\n \treturn ret;\n }\n \n+static int do_update_refs(struct repository *r)\n+{\n+\treturn 0;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -4431,6 +4439,9 @@ static int pick_commits(struct repository *r,\n \t\t\t\treturn error_with_patch(r, item->commit,\n \t\t\t\t\t\t\targ, item->arg_len,\n \t\t\t\t\t\t\topts, res, 0);\n+\t\t} else if (item->command == TODO_UPDATE_REFS) {\n+\t\t\tif ((res = do_update_refs(r)))\n+\t\t\t\treschedule = 1;\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex da64473636b..c2b4e148d8f 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -95,6 +95,7 @@ enum todo_command {\n \tTODO_LABEL,\n \tTODO_RESET,\n \tTODO_MERGE,\n+\tTODO_UPDATE_REFS,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n-- \ngitgitgadget\n\n"},{"id":"456846","messageId":"68f8e51b19c024e1cb075dffd07f81cceaba5957.1654634569.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v2 6/7] sequencer: implement 'update-refs' command","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:48Z","receivedAt":"2022-06-08T02:46:50Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change allowed 'git rebase --update-refs' to create 'label'\ncommands for each branch  among the commits being rewritten and add an\n'update-refs' command at the end of the todo list. Now, teach Git to\nupdate the refs during that final 'update-refs' command.\n\nWe need to create an array of new and old OIDs for each ref by iterating\nover the refs/rewritten/for-update-refs/ namespace. We cannot update the\nrefs in-place since this will confuse the refs iterator.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c                   | 62 ++++++++++++++++++++++++++++++++++-\n t/t3404-rebase-interactive.sh | 24 ++++++++++++++\n 2 files changed, 85 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 94f8d52e041..a8f62ce8e5f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4061,11 +4061,71 @@ leave_merge:\n \treturn ret;\n }\n \n-static int do_update_refs(struct repository *r)\n+struct update_refs_context {\n+\tstruct ref_store *refs;\n+\tchar **ref_names;\n+\tstruct object_id *old;\n+\tstruct object_id *new;\n+\tsize_t nr;\n+\tsize_t alloc;\n+};\n+\n+static int add_ref_to_context(const char *refname,\n+\t\t\t      const struct object_id *oid,\n+\t\t\t      int flags,\n+\t\t\t      void *data)\n {\n+\tint f = 0;\n+\tconst char *name;\n+\tstruct update_refs_context *ctx = data;\n+\n+\tALLOC_GROW(ctx->ref_names, ctx->nr + 1, ctx->alloc);\n+\tALLOC_GROW(ctx->old, ctx->nr + 1, ctx->alloc);\n+\tALLOC_GROW(ctx->new, ctx->nr + 1, ctx->alloc);\n+\n+\tif (!skip_prefix(refname, \"refs/rewritten/for-update-refs/\", &name))\n+\t\treturn 1;\n+\n+\tctx->ref_names[ctx->nr] = xstrdup(name);\n+\toidcpy(&ctx->new[ctx->nr], oid);\n+\tif (!refs_resolve_ref_unsafe(ctx->refs, name, 0,\n+\t\t\t\t     &ctx->old[ctx->nr], &f))\n+\t\treturn 1;\n+\n+\tctx->nr++;\n \treturn 0;\n }\n \n+static int do_update_refs(struct repository *r)\n+{\n+\tint i, res;\n+\tstruct update_refs_context ctx = {\n+\t\t.refs = get_main_ref_store(r),\n+\t\t.alloc = 16,\n+\t};\n+\tALLOC_ARRAY(ctx.ref_names, ctx.alloc);\n+\tALLOC_ARRAY(ctx.old, ctx.alloc);\n+\tALLOC_ARRAY(ctx.new, ctx.alloc);\n+\n+\tres = refs_for_each_fullref_in(ctx.refs,\n+\t\t\t\t       \"refs/rewritten/for-update-refs/\",\n+\t\t\t\t       add_ref_to_context,\n+\t\t\t\t       &ctx);\n+\n+\tfor (i = 0; !res && i < ctx.nr; i++)\n+\t\tres = refs_update_ref(ctx.refs, \"rewritten during rebase\",\n+\t\t\t\tctx.ref_names[i],\n+\t\t\t\t&ctx.new[i], &ctx.old[i],\n+\t\t\t\t0, UPDATE_REFS_MSG_ON_ERR);\n+\n+\tfor (i = 0; i < ctx.nr; i++)\n+\t\tfree(ctx.ref_names[i]);\n+\tfree(ctx.ref_names);\n+\tfree(ctx.old);\n+\tfree(ctx.new);\n+\treturn res;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 5e99ad7f3b6..72711efec28 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1815,6 +1815,30 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t)\n '\n \n+compare_two_refs () {\n+\tgit rev-parse $1 >expect &&\n+\tgit rev-parse $2 >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success '--update-refs updates refs correctly' '\n+\tgit checkout -B update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\ttest_commit extra2 fileX &&\n+\tgit commit --amend --fixup=L &&\n+\t(\n+\t\tgit rebase -i --autosquash --update-refs primary &&\n+\n+\t\tcompare_two_refs HEAD~3 refs/heads/first &&\n+\t\tcompare_two_refs HEAD~3 refs/heads/second &&\n+\t\tcompare_two_refs HEAD~1 refs/heads/third &&\n+\t\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n+\t)\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"456847","messageId":"0a4c110127b7628d3bc5b04534a4ea67fe46ef80.1654634569.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v2 5/7] rebase: add --update-refs option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:47Z","receivedAt":"2022-06-08T02:47:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen working on a large feature, it can be helpful to break that feature\ninto multiple smaller parts that become reviewed in sequence. During\ndevelopment or during review, a change to one part of the feature could\naffect multiple of these parts. An interactive rebase can help adjust\nthe multi-part \"story\" of the branch.\n\nHowever, if there are branches tracking the different parts of the\nfeature, then rebasing the entire list of commits can create commits not\nreachable from those \"sub branches\". It can take a manual step to update\nthose branches.\n\nAdd a new --update-refs option to 'git rebase -i' that adds 'label\nfor-update-refs/*' steps to the todo file whenever a commit that is\nbeing rebased is decorated with that <ref>. At the very end, the\n'update-refs' step is added to update all of the branches referenced by\nthe 'label' steps. This allows the user to rebase a long list of commits\nin a multi-part feature and keep all of their pointers to those parts.\n\nNOTE: This change only introduce the --update-refs option and implements\nthe changes to the todo file. It does _not_ yet implement the action\ntaken by the 'update-refs' todo step, which will be implemented and\ntested in a later change.\n\nUse the new for_each_decoration() while iterating over the existing todo\nlist. Be sure to iterate after any squashing or fixups are placed.\nUpdate the branch only after those squashes and fixups are complete.\nThis allows a --fixup commit at the tip of the feature to apply\ncorrectly to the sub branch, even if it is fixing up the most-recent\ncommit in that part.\n\nOne potential problem here is that refs decorating commits that are\nalready marked as \"fixup!\" or \"squash!\" will not be included in this\nlist. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\nto change the relative order of these refs, so it is not recommended.\nThe workflow here is intended to allow these kinds of commits at the tip\nof the rebased branch while the other sub branches come along for the\nride without intervention.\n\nBe careful to not attempt updating any branch that is checked out. The\nmost common example is the branch being rebased is checked out and\ndecorates the tip commit. If the user is rebasing commits reachable from\na different branch that is checked out in a different worktree, then\nthey may be surprised to not see that ref update. However, it's probably\nbest to not optimize for this scenario and do the safest thing that will\nresult in a successful rebase. A comment is left in the TODO list that\nsignals that these refs are currently checked out.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/git-rebase.txt  |   8 +++\n builtin/rebase.c              |   5 ++\n sequencer.c                   | 105 ++++++++++++++++++++++++++++++++++\n sequencer.h                   |   1 +\n t/t3404-rebase-interactive.sh |  72 +++++++++++++++++++++++\n 5 files changed, 191 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 262fb01aec0..e7611b4089c 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -609,6 +609,13 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n start would be overridden by the presence of\n `rebase.rescheduleFailedExec=true` configuration.\n \n+--update-refs::\n+--no-update-refs::\n+\tAutomatically force-update any branches that point to commits that\n+\tare being rebased. Any branches that are checked out in a worktree\n+\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n+\tin this way.\n+\n INCOMPATIBLE OPTIONS\n --------------------\n \n@@ -632,6 +639,7 @@ are incompatible with the following options:\n  * --empty=\n  * --reapply-cherry-picks\n  * --edit-todo\n+ * --update-refs\n  * --root when used in combination with --onto\n \n In addition, the following pairs of options are incompatible:\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7ab50cda2ad..56d82a52106 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -102,6 +102,7 @@ struct rebase_options {\n \tint reschedule_failed_exec;\n \tint reapply_cherry_picks;\n \tint fork_point;\n+\tint update_refs;\n };\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n@@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \t\tret = complete_action(the_repository, &replay, flags,\n \t\t\tshortrevisions, opts->onto_name, opts->onto,\n \t\t\t&opts->orig_head, &commands, opts->autosquash,\n+\t\t\topts->update_refs,\n \t\t\t&todo_list);\n \t}\n \n@@ -1124,6 +1126,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n+\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n+\t\t\t N_(\"update local refs that point to commits \"\n+\t\t\t    \"that are being rebased\")),\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n \t\t\tN_(\"GPG-sign commits\"),\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\ndiff --git a/sequencer.c b/sequencer.c\nindex 68f7c76e896..94f8d52e041 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -35,6 +35,8 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"branch.h\"\n+#include \"log-tree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -5614,10 +5616,110 @@ static int skip_unnecessary_picks(struct repository *r,\n \treturn 0;\n }\n \n+struct todo_add_branch_context {\n+\tstruct todo_item *items;\n+\tsize_t items_nr;\n+\tsize_t items_alloc;\n+\tstruct strbuf *buf;\n+\tstruct commit *commit;\n+};\n+\n+static int add_branch_for_decoration(const struct name_decoration *d, void *data)\n+{\n+\tstruct todo_add_branch_context *ctx = data;\n+\tsize_t base_offset = ctx->buf->len;\n+\tstruct todo_item *item;\n+\tchar *path;\n+\n+\tALLOC_GROW(ctx->items,\n+\t\t   ctx->items_nr + 1,\n+\t\t   ctx->items_alloc);\n+\titem = &ctx->items[ctx->items_nr];\n+\tmemset(item, 0, sizeof(*item));\n+\n+\t/* If the branch is checked out, then leave a comment instead. */\n+\tif (branch_checked_out(d->name, &path)) {\n+\t\titem->command = TODO_COMMENT;\n+\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n+\t\t\t    d->name, path);\n+\t\tfree(path);\n+\t} else {\n+\t\titem->command = TODO_LABEL;\n+\t\tstrbuf_addf(ctx->buf, \"for-update-refs/%s\\n\", d->name);\n+\t}\n+\n+\titem->offset_in_buf = base_offset;\n+\titem->arg_offset = base_offset;\n+\titem->arg_len = ctx->buf->len - base_offset;\n+\tctx->items_nr++;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * For each 'pick' command, find out if the commit has a decoration in\n+ * refs/heads/. If so, then add a 'label for-update-refs/' command.\n+ */\n+static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n+{\n+\tint i;\n+\tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n+\tstruct decoration_filter decoration_filter = {\n+\t\t.include_ref_pattern = &decorate_refs_include,\n+\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n+\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config\n+\t};\n+\tstruct todo_add_branch_context ctx = {\n+\t\t.buf = &todo_list->buf,\n+\t};\n+\n+\tctx.items_alloc = 2 * todo_list->nr + 1;\n+\tALLOC_ARRAY(ctx.items, ctx.items_alloc);\n+\n+\tstring_list_append(&decorate_refs_include, \"refs/heads/\");\n+\tload_ref_decorations(&decoration_filter, 0);\n+\n+\tfor (i = 0; i < todo_list->nr; ) {\n+\t\tstruct todo_item *item = &todo_list->items[i];\n+\n+\t\t/* insert ith item into new list */\n+\t\tALLOC_GROW(ctx.items,\n+\t\t\t   ctx.items_nr + 1,\n+\t\t\t   ctx.items_alloc);\n+\n+\t\tctx.items[ctx.items_nr++] = todo_list->items[i++];\n+\n+\t\tif (item->commit) {\n+\t\t\tctx.commit = item->commit;\n+\t\t\tfor_each_decoration(item->commit,\n+\t\t\t\t\t    add_branch_for_decoration,\n+\t\t\t\t\t    &ctx);\n+\t\t}\n+\t}\n+\n+\t/* Add the \"update-refs\" step. */\n+\tALLOC_GROW(ctx.items,\n+\t\t   ctx.items_nr + 1,\n+\t\t   ctx.items_alloc);\n+\tmemset(&ctx.items[ctx.items_nr], 0, sizeof(struct todo_item));\n+\tctx.items[ctx.items_nr].command = TODO_UPDATE_REFS;\n+\tctx.items_nr++;\n+\n+\tfree(todo_list->items);\n+\ttodo_list->items = ctx.items;\n+\ttodo_list->nr = ctx.items_nr;\n+\ttodo_list->alloc = ctx.items_alloc;\n+\n+\treturn 0;\n+}\n+\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list)\n {\n \tchar shortonto[GIT_MAX_HEXSZ + 1];\n@@ -5636,6 +5738,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\titem->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n \t}\n \n+\tif (update_refs && todo_list_add_update_ref_commands(todo_list))\n+\t\treturn -1;\n+\n \tif (autosquash && todo_list_rearrange_squash(todo_list))\n \t\treturn -1;\n \ndiff --git a/sequencer.h b/sequencer.h\nindex c2b4e148d8f..e268208b315 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -167,6 +167,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list);\n int todo_list_rearrange_squash(struct todo_list *todo_list);\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f31afd4a547..5e99ad7f3b6 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1743,6 +1743,78 @@ test_expect_success 'ORIG_HEAD is updated correctly' '\n \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n '\n \n+test_expect_success '--update-refs adds label and update-ref commands' '\n+\tgit checkout -b update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit commit --allow-empty --fixup=third &&\n+\tgit branch -f shared-tip &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tpick $(git log -1 --format=%h J) J\n+\t\tlabel for-update-refs/refs/heads/second\n+\t\tlabel for-update-refs/refs/heads/first\n+\t\tpick $(git log -1 --format=%h K) K\n+\t\tpick $(git log -1 --format=%h L) L\n+\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n+\t\tlabel for-update-refs/refs/heads/third\n+\t\tpick $(git log -1 --format=%h M) M\n+\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n+\t\tlabel for-update-refs/refs/heads/shared-tip\n+\t\tupdate-refs\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n+test_expect_success '--update-refs adds commands with --rebase-merges' '\n+\tgit checkout -b update-refs-with-merge no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit merge -m merge branch2 &&\n+\tgit branch -f merge-branch &&\n+\tgit commit --fixup=third --allow-empty &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tlabel onto\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h branch2~1) F\n+\t\tpick $(git log -1 --format=%h branch2) I\n+\t\tlabel for-update-refs/refs/heads/branch2\n+\t\tlabel merge\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h refs/heads/second) J\n+\t\tlabel for-update-refs/refs/heads/second\n+\t\tlabel for-update-refs/refs/heads/first\n+\t\tpick $(git log -1 --format=%h refs/heads/third~1) K\n+\t\tpick $(git log -1 --format=%h refs/heads/third) L\n+\t\tfixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n+\t\tlabel for-update-refs/refs/heads/third\n+\t\tpick $(git log -1 --format=%h HEAD~2) M\n+\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n+\t\tmerge -C $(git log -1 --format=%h HEAD~1) merge # merge\n+\t\tlabel for-update-refs/refs/heads/merge-branch\n+\t\tupdate-refs\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   --update-refs primary >todo &&\n+\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"456850","messageId":"3d7d3f656b4e93e8caa0d18d29c318ede956d1d7.1654634569.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v2 7/7] rebase: add rebase.updateRefs config option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-07T20:42:49Z","receivedAt":"2022-06-08T02:55:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change added the --update-refs command-line option.  For\nusers who always want this mode, create the rebase.updateRefs config\noption which behaves the same way as rebase.autoSquash does with the\n--autosquash option.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/config/rebase.txt |  3 +++\n Documentation/git-rebase.txt    |  4 ++++\n builtin/rebase.c                |  5 +++++\n t/t3404-rebase-interactive.sh   | 14 ++++++++++++++\n 4 files changed, 26 insertions(+)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex 8c979cb20f2..f19bd0e0407 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -21,6 +21,9 @@ rebase.autoStash::\n \t`--autostash` options of linkgit:git-rebase[1].\n \tDefaults to false.\n \n+rebase.updateRefs::\n+\tIf set to true enable `--update-refs` option by default.\n+\n rebase.missingCommitsCheck::\n \tIf set to \"warn\", git rebase -i will print a warning if some\n \tcommits are removed (e.g. a line was deleted), however the\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex e7611b4089c..7a56dbb9bec 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -615,6 +615,10 @@ start would be overridden by the presence of\n \tare being rebased. Any branches that are checked out in a worktree\n \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n \tin this way.\n++\n+If the `--update-refs` option is enabled by default using the\n+configuration variable `rebase.updateRefs`, this option can be\n+used to override and disable this setting.\n \n INCOMPATIBLE OPTIONS\n --------------------\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 56d82a52106..8ebc98ea505 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -802,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"rebase.updaterefs\")) {\n+\t\topts->update_refs = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"rebase.reschedulefailedexec\")) {\n \t\topts->reschedule_failed_exec = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 72711efec28..f580afd8723 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1769,6 +1769,12 @@ test_expect_success '--update-refs adds label and update-ref commands' '\n \t\tEOF\n \n \t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n@@ -1811,6 +1817,14 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t\t\t\t   --rebase-merges=rebase-cousins \\\n \t\t\t\t   --update-refs primary >todo &&\n \n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n-- \ngitgitgadget\n"},{"id":"456856","messageId":"e84b8f19-4fb4-77cd-7e56-087e84e59817@github.com","threadId":"57949","inReplyTo":"xmqqr140t9am.fsf@gitster.g","subject":"Re: [PATCH v2 2/7] branch: add branch_checked_out() helper","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-08T02:14:16Z","receivedAt":"2022-06-08T05:17:19Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/7/2022 6:09 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> +int branch_checked_out(const char *refname, char **path)\n>> +{\n>> +\tstruct worktree **worktrees = get_worktrees();\n>> +\tconst struct worktree *wt = find_shared_symref(worktrees, \"HEAD\", refname);\n>> +\tint result = wt && !wt->is_bare;\n>> +\n>> +\tif (result && path)\n>> +\t\t*path = xstrdup(wt->path);\n>> +\n>> +\tfree_worktrees(worktrees);\n>> +\treturn result;\n>> +}\n> \n> Don't you plan to call this repeatedly from the for_each_deco\n> iteration?  I am wondering if it should take the result of\n> get_worktrees() and reuse the result of get_worktrees(), instead of\n> enumerating the list of worktrees and freeing for each of the\n> branches you need to inspect.\n\nSorry, you had mentioned this in v1 and I just forgot to fix this.\n\n> There also was another topic\n> \n> https://lore.kernel.org/git/pull.1266.v2.git.git.1652690501963.gitgitgadget@gmail.com/\n> \n> that was triggered by find_shared_symref() being relatively\n> heavy-weight, which suggests a more involved refactoring.\n> \n> I wonder if we rather want to rewrite find_shared_symref() *not* to\n> take the target parameter at all, and instead introduce a new\n> function that takes a worktree, and report the branch that is\n> checked out (or being operated on via rebase or bisect).  Then we\n> can\n> \n>  - create a strset out of its result, i.e. set of branches that\n>    should not be touched;\n> \n>  - iterate over refs that point into the history being rebased\n>    (using for_each_decoration()), and consult that strset to see if\n>    any of them is being rewritten.\n> \n> With the API of find_shared_symref(), we'd need to iterate over all\n> worktrees for each decoration.  With such a restructuring, we can\n> iterate over all worktrees just once, and match the result with\n> decoration, so the problem becomes O(N)+O(M) and not O(N*M) for\n> number of worktrees N and number of decorations M.\n\nYes, that's a good plan. I'll take a look.\n\nThanks,\n-Stolee\n"},{"id":"456857","messageId":"74d5f74b-c95f-6892-6a66-3e646b1f9e53@github.com","threadId":"57949","inReplyTo":"e84b8f19-4fb4-77cd-7e56-087e84e59817@github.com","subject":"Re: [PATCH v2 2/7] branch: add branch_checked_out() helper","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-08T02:43:00Z","receivedAt":"2022-06-08T05:29:58Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/7/2022 10:14 PM, Derrick Stolee wrote:\n> On 6/7/2022 6:09 PM, Junio C Hamano wrote:\n>>\n>> I wonder if we rather want to rewrite find_shared_symref() *not* to\n>> take the target parameter at all, and instead introduce a new\n>> function that takes a worktree, and report the branch that is\n>> checked out (or being operated on via rebase or bisect).  Then we\n>> can\n>>\n>>  - create a strset out of its result, i.e. set of branches that\n>>    should not be touched;\n>>\n>>  - iterate over refs that point into the history being rebased\n>>    (using for_each_decoration()), and consult that strset to see if\n>>    any of them is being rewritten.\n>>\n>> With the API of find_shared_symref(), we'd need to iterate over all\n>> worktrees for each decoration.  With such a restructuring, we can\n>> iterate over all worktrees just once, and match the result with\n>> decoration, so the problem becomes O(N)+O(M) and not O(N*M) for\n>> number of worktrees N and number of decorations M.\n> \n> Yes, that's a good plan. I'll take a look.\n\nHere is a fixup for this patch that should work. I'll put it in v3.\n\ndiff --git a/branch.c b/branch.c\nindex 2e6419cdfa5..514212f5619 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -10,6 +10,7 @@\n #include \"worktree.h\"\n #include \"submodule-config.h\"\n #include \"run-command.h\"\n+#include \"strmap.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -369,17 +370,44 @@ int validate_branchname(const char *name, struct strbuf *ref)\n \treturn ref_exists(ref->buf);\n }\n \n-int branch_checked_out(const char *refname, char **path)\n+static int initialized_checked_out_branches;\n+static struct strmap current_checked_out_branches = STRMAP_INIT;\n+\n+static void prepare_checked_out_branches(void)\n {\n-\tstruct worktree **worktrees = get_worktrees();\n-\tconst struct worktree *wt = find_shared_symref(worktrees, \"HEAD\", refname);\n-\tint result = wt && !wt->is_bare;\n+\tint i = 0;\n+\tstruct worktree **worktrees;\n+\n+\tif (initialized_checked_out_branches)\n+\t\treturn;\n+\tinitialized_checked_out_branches = 1;\n+\n+\tworktrees = get_worktrees();\n+\n+\twhile (worktrees[i]) {\n+\t\tstruct worktree *wt = worktrees[i];\n \n-\tif (result && path)\n-\t\t*path = xstrdup(wt->path);\n+\t\tif (!wt->is_bare && wt->head_ref)\n+\t\t\tstrmap_put(&current_checked_out_branches,\n+\t\t\t\t   wt->head_ref,\n+\t\t\t\t   wt->path);\n+\n+\t\ti++;\n+\t}\n \n \tfree_worktrees(worktrees);\n-\treturn result;\n+}\n+\n+int branch_checked_out(const char *refname, char **path)\n+{\n+\tconst char *path_in_set;\n+\tprepare_checked_out_branches();\n+\n+\tpath_in_set = strmap_get(&current_checked_out_branches, refname);\n+\tif (path_in_set && path)\n+\t\t*path = xstrdup(path_in_set);\n+\n+\treturn !!path_in_set;\n }\n \n /*\n\n>> There also was another topic\n>>\n>> https://lore.kernel.org/git/pull.1266.v2.git.git.1652690501963.gitgitgadget@gmail.com/\n>>\n>> that was triggered by find_shared_symref() being relatively\n>> heavy-weight, which suggests a more involved refactoring.\n\nThe patch there was this:\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex e3791f09ed5..eeee5ac8f15 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1440,6 +1440,7 @@ static void check_not_current_branch(struct ref *ref_map,\n \tconst struct worktree *wt;\n \tfor (; ref_map; ref_map = ref_map->next)\n \t\tif (ref_map->peer_ref &&\n+\t\t    starts_with(ref_map->peer_ref->name, \"refs/heads/\") &&\n \t\t    (wt = find_shared_symref(worktrees, \"HEAD\",\n \t\t\t\t\t     ref_map->peer_ref->name)) &&\n \t\t    !wt->is_bare)\n\nAnd there is another use of find_shared_symref() in the same file, allowing\nus to do the following:\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ac29c2b1ae3..3933c482839 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -885,7 +885,7 @@ static int update_local_ref(struct ref *ref,\n \t\t\t    struct worktree **worktrees)\n {\n \tstruct commit *current = NULL, *updated;\n-\tconst struct worktree *wt;\n+\tchar *path = NULL;\n \tconst char *pretty_ref = prettify_refname(ref->name);\n \tint fast_forward = 0;\n \n@@ -900,17 +900,17 @@ static int update_local_ref(struct ref *ref,\n \t}\n \n \tif (!update_head_ok &&\n-\t    (wt = find_shared_symref(worktrees, \"HEAD\", ref->name)) &&\n-\t    !wt->is_bare && !is_null_oid(&ref->old_oid)) {\n+\t    !is_null_oid(&ref->old_oid) &&\n+\t    branch_checked_out(ref->name, &path)) {\n \t\t/*\n \t\t * If this is the head, and it's not okay to update\n \t\t * the head, and the old value of the head isn't empty...\n \t\t */\n \t\tformat_display(display, '!', _(\"[rejected]\"),\n-\t\t\t       wt->is_current ?\n-\t\t\t\t       _(\"can't fetch in current branch\") :\n-\t\t\t\t       _(\"checked out in another worktree\"),\n+\t\t\t       path ? _(\"can't fetch in current branch\") :\n+\t\t\t\t      _(\"checked out in another worktree\"),\n \t\t\t       remote, pretty_ref, summary_width);\n+\t\tfree(path);\n \t\treturn 1;\n \t}\n \n@@ -1437,16 +1437,14 @@ static int prune_refs(struct refspec *rs,\n static void check_not_current_branch(struct ref *ref_map,\n \t\t\t\t     struct worktree **worktrees)\n {\n-\tconst struct worktree *wt;\n+\tchar *path;\n \tfor (; ref_map; ref_map = ref_map->next)\n \t\tif (ref_map->peer_ref &&\n \t\t    starts_with(ref_map->peer_ref->name, \"refs/heads/\") &&\n-\t\t    (wt = find_shared_symref(worktrees, \"HEAD\",\n-\t\t\t\t\t     ref_map->peer_ref->name)) &&\n-\t\t    !wt->is_bare)\n+\t\t    branch_checked_out(ref_map->peer_ref->name, &path))\n \t\t\tdie(_(\"refusing to fetch into branch '%s' \"\n \t\t\t      \"checked out at '%s'\"),\n-\t\t\t    ref_map->peer_ref->name, wt->path);\n+\t\t\t    ref_map->peer_ref->name, path);\n }\n \n static int truncate_fetch_head(void)\n\nI can extract these as patches in their own series if we think\nthat's something we worth fast-tracking.\n\nThanks,\n-Stolee\n"},{"id":"456858","messageId":"xmqqleu7sqnt.fsf@gitster.g","threadId":"57949","inReplyTo":"74d5f74b-c95f-6892-6a66-3e646b1f9e53@github.com","subject":"Re: [PATCH v2 2/7] branch: add branch_checked_out() helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-08T04:52:22Z","receivedAt":"2022-06-08T06:55:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n>> Yes, that's a good plan. I'll take a look.\n>\n> Here is a fixup for this patch that should work. I'll put it in v3.\n>\n> diff --git a/branch.c b/branch.c\n> index 2e6419cdfa5..514212f5619 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -10,6 +10,7 @@\n>  #include \"worktree.h\"\n>  #include \"submodule-config.h\"\n>  #include \"run-command.h\"\n> +#include \"strmap.h\"\n>  \n>  struct tracking {\n>  \tstruct refspec_item spec;\n> @@ -369,17 +370,44 @@ int validate_branchname(const char *name, struct strbuf *ref)\n>  \treturn ref_exists(ref->buf);\n>  }\n>  \n> -int branch_checked_out(const char *refname, char **path)\n> +static int initialized_checked_out_branches;\n> +static struct strmap current_checked_out_branches = STRMAP_INIT;\n> +\n> +static void prepare_checked_out_branches(void)\n>  {\n> +\tint i = 0;\n> +\tstruct worktree **worktrees;\n> +\n> +\tif (initialized_checked_out_branches)\n> +\t\treturn;\n> +\tinitialized_checked_out_branches = 1;\n> +\n> +\tworktrees = get_worktrees();\n> +\n> +\twhile (worktrees[i]) {\n> +\t\tstruct worktree *wt = worktrees[i];\n>  \n> +\t\tif (!wt->is_bare && wt->head_ref)\n> +\t\t\tstrmap_put(&current_checked_out_branches,\n> +\t\t\t\t   wt->head_ref,\n> +\t\t\t\t   wt->path);\n> +\n> +\t\ti++;\n> +\t}\n>  \tfree_worktrees(worktrees);\n> -\treturn result;\n> +}\n\nYeah, the above illustrates what I had in mind pretty closely.  The\nabove outline only checks where the HEAD symref points at, and it\ndoes not protect a branch that is in the middle of getting bisected\nor rebased, which find_shared_symref() tries to do, IIRC, though.\n\nIt should not be too hard to add that to the above to allow us to\nachieve feature parity with the original.\n\n> +\n> +int branch_checked_out(const char *refname, char **path)\n> +{\n> +\tconst char *path_in_set;\n> +\tprepare_checked_out_branches();\n> +\n> +\tpath_in_set = strmap_get(&current_checked_out_branches, refname);\n> +\tif (path_in_set && path)\n> +\t\t*path = xstrdup(path_in_set);\n> +\n> +\treturn !!path_in_set;\n>  }\n\nThis one looks quite straight-forward.\n\n> And there is another use of find_shared_symref() in the same file, allowing\n> us to do the following:\n>\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index ac29c2b1ae3..3933c482839 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -885,7 +885,7 @@ static int update_local_ref(struct ref *ref,\n>  \t\t\t    struct worktree **worktrees)\n>  {\n>  \tstruct commit *current = NULL, *updated;\n> -\tconst struct worktree *wt;\n> +\tchar *path = NULL;\n>  \tconst char *pretty_ref = prettify_refname(ref->name);\n>  \tint fast_forward = 0;\n>  \n> @@ -900,17 +900,17 @@ static int update_local_ref(struct ref *ref,\n>  \t}\n>  \n>  \tif (!update_head_ok &&\n> -\t    (wt = find_shared_symref(worktrees, \"HEAD\", ref->name)) &&\n> -\t    !wt->is_bare && !is_null_oid(&ref->old_oid)) {\n> +\t    !is_null_oid(&ref->old_oid) &&\n> +\t    branch_checked_out(ref->name, &path)) {\n\nYes.  It looks like a good direction to go in.\n\nThanks.\n"},{"id":"456863","messageId":"ac135ece-a901-5868-f28c-2161d7a62e97@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/7] rebase: update branches in multi-part topic","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-08T14:32:46Z","receivedAt":"2022-06-08T14:32:56Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 07/06/2022 21:42, Derrick Stolee via GitGitGadget wrote:\n> [...]\n> As an example, here is my in-progress bundle URI RFC split into subtopics as\n> they appear during the TODO list of a git rebase -i --update-refs:\n> \n> pick 2d966282ff3 docs: document bundle URI standard\n> pick 31396e9171a remote-curl: add 'get' capability\n> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n> pick 6adaf842684 fetch: add --bundle-uri option\n> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n> label for-update-refs/refs/heads/bundle-redo/fetch\n> [...] \n> update-refs\n> [...]\n> Based on some of the discussion, it seemed like one way to do this would be\n> to have an 'update-ref ' command that would take the place of these 'label'\n> commands. However, this would require two things that make it a bit awkward:\n> \n>   1. We would need to replicate the storage of those positions during the\n>      rebase. 'label' already does this pretty well. I've added the\n>      \"for-update-refs/\" label to help here.\n\nI'm afraid I don't think that using a label with a name constructed from \na magic prefix and the full refname is a good user interface.\n\n(i) Having labels behave differently based on their name is confusing.\n\n(ii) The length of the label string is cumbersome for users. Rebase \nalready has a reputation for being unfriendly and hard to use, this will \nnot improve that. \"update-ref bundle-redo/fetch\" is much simpler.\n\n(iii) It means that we no longer store the oid of each branch when \ncreating the todo list and so cannot check if it is safe to update it at \nthe end of the rebase (just using the current value of the branch ref at \nthe end of a long running operation like rebase is not sufficient to be \nsafe).\n\n>   2. If we want to close out all of the refs as the rebase is finishing, then\n>      that \"step\" becomes invisible to the user (and a bit more complicated to\n>      insert). Thus, the 'update-refs' step performs this action. If the user\n>      wants to do things after that step, then they can do so by editing the\n>      TODO list.\n\nI'm not sure what the use case is for making the update-refs step \nvisible to the user - it seems to be added complexity for them to deal \nwith. We don't do that for the branch that is being rebased so why do we \nneed to do it for the other branches? As for the implementation we could \njust update the branches at the end of the loop in pick_commits() where \nwe update the branch and run the post-rewrite hook etc. there is no need \nfor an entry in the todo list.\n\nBest Wishes\n\nPhillip\n"},{"id":"456869","messageId":"xmqqbkv3rvlj.fsf@gitster.g","threadId":"57949","inReplyTo":"9354d1d3-c1b7-3baf-215f-30659ad48b22@github.com","subject":"Re: [PATCH 0/4] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-08T16:03:20Z","receivedAt":"2022-06-08T16:03:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n>> I think the question of whether to update branches that are checked out in\n>> another worktree is a question of whether it is less inconvenient to the user\n>> to skip the ref update and leave the user to manually update the branch or to\n>> update the ref and leave the worktree in a potentially awkward state if the\n>> user was half way through building a commit. The answer probably depends on\n>> the preferences of the user.\n\nTo some degree, it might be true, but I think the recent thinking is\nthat by default any refs being worked on by the user must be kept\nintact.  \"switch\" does not let you check out a branch that is\nalready checked out elsewhere, \"fetch\" does not let you overwrite\nthe branch that is currently checked out without \"--update-head-ok\",\netc.\n\n> I think that their 'git status' will look strange no matter what: their\n> working directory and index could look significantly different from what\n> the branch at HEAD is reporting. For this situation, I would rather continue\n> preventing these ref updates from underneath worktrees.\n>  \n>> I've been using a script that updates the refs for all the branches being\n>> rewritten for a while and have found it preferable to always update the ref\n>> rather than have to do it manually. My script also updates the worktree\n>> checkout to the new HEAD if there are no uncommitted changes which I have\n>> found very convenient. My preference is probably because I tend not to have\n>> uncommitted changes lying around in the worktrees whose branches get updated.\n>\n> Actually updating the worktree to match seems like an interesting twist, which\n> we would want to consider if we go this route in the future.\n\nUsually I caution against adding \"features\" that can complicate the\nend-user experience like this, but in this case, the potential for\nextra conflicts coming from such updates to the working trees may\nnot be too bad.\n\nIt all depends on why the user has these intermediate branches and\nhow they are used, but in order to see any conflicts happen after\nrebasing a branch here, before you start that rebase, you need to\nhave in different working trees that had these intermediate branches\nchecked out and had local modifications on top of them.\n\nI would say that you deserve it if conflicts resulting from such a\nset-up hurts you ;-)  If you are using these intermediate branches\nso that you can work on steps in the middle of the larger whole,\nthen the proposed \"rebase --update-refs\" is not for you.  Having\nlocal modification in these separate working trees that check out\nthe intermediate branches is OK, but once you create a commit on\nsuch a branch, the commit that is in the larger topic is no longer\nat the tip, so \"rebase --update-refs\" would not notice it anyway.\n\nRather, if you have the intermediate branches, you'd probably want\nto work on each of them to make them stable, and rebase them on top\nof each other in an appropriate order.  \"rebase --update-refs\" that\nruns on the larger topic does not have enough information to rebuild\nit with tips of intermediate branches that are updated elsewhere.\n\nWhich means that anybody sane who uses \"rebase --update-refs\" would\nnot modify these intermediate branches outside the context of the\nlarger topic, and those who do not follow this rule can keep both\nhalves of their history ;-)\n\nFWIW a complementary tool that would work in the other direction, to\nexpect the user to have worked on smaller branches and rebuild the\nlarger branch that contains them, is also missing from our tool set.\nThose who check out these smaller branches in separate working trees\nwould want to use such a tool, not \"rebase --update-ref\".\n\nThanks.\n"},{"id":"456879","messageId":"34264915-8a37-62db-f954-0b5297639b34@github.com","threadId":"57949","inReplyTo":"ac135ece-a901-5868-f28c-2161d7a62e97@gmail.com","subject":"Re: [PATCH v2 0/7] rebase: update branches in multi-part topic","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-08T18:09:01Z","receivedAt":"2022-06-08T18:09:11Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/8/2022 10:32 AM, Phillip Wood wrote:\n> Hi Stolee\n> \n> On 07/06/2022 21:42, Derrick Stolee via GitGitGadget wrote:\n>> [...]\n>> As an example, here is my in-progress bundle URI RFC split into subtopics as\n>> they appear during the TODO list of a git rebase -i --update-refs:\n>>\n>> pick 2d966282ff3 docs: document bundle URI standard\n>> pick 31396e9171a remote-curl: add 'get' capability\n>> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n>> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n>> pick 6adaf842684 fetch: add --bundle-uri option\n>> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n>> label for-update-refs/refs/heads/bundle-redo/fetch\n>> [...] update-refs\n>> [...]\n>> Based on some of the discussion, it seemed like one way to do this would be\n>> to have an 'update-ref ' command that would take the place of these 'label'\n>> commands. However, this would require two things that make it a bit awkward:\n>>\n>>   1. We would need to replicate the storage of those positions during the\n>>      rebase. 'label' already does this pretty well. I've added the\n>>      \"for-update-refs/\" label to help here.\n> \n> I'm afraid I don't think that using a label with a name constructed from\n> a magic prefix and the full refname is a good user interface.\n> \n> (i) Having labels behave differently based on their name is confusing.\n\nThe label commands do as advertised, but the 'update-refs' command does\nsomething with the set of refs based on their names, yes. We would need to\nstore this information during the rebase _somewhere_, and refs/rewritten/\nseems natural.\n \n> (ii) The length of the label string is cumbersome for users. Rebase already\n> has a reputation for being unfriendly and hard to use, this will not improve\n> that. \"update-ref bundle-redo/fetch\" is much simpler.\n\nI agree that your proposed replacement for \"label for-update-refs/...\" would\nlook cleaner. I don't think that that is enough to justify hiding information\nfrom the user.\n\n> (iii) It means that we no longer store the oid of each branch when creating> the todo list and so cannot check if it is safe to update it at the end of the\n> rebase (just using the current value of the branch ref at the end of a long\n> running operation like rebase is not sufficient to be safe).\n\nThe operation we are doing necessarily requires a force-update, since we are\nmodifying history. The safety you are caring about here is about the case where\nthe user modifies one of these refs between the initial 'git rebase --update-refs'\nand the final 'git rebase --continue' that actually writes the refs. You want to\nprevent that final update from succeeding because another change to those refs\ncould be lost.\n\nI'm less concerned about this case, because the user is requesting that we\nupdate the refs pointing to this set of commits. But, I'm glad you brought it\nup.\n\nOne way to prevent this situation would be to extend the idea of \"what branch\nis being rebased?\" (REBASE_HEAD) to \"which branches are being rewritten?\"\n(REBASE_HEADS?). If the worktree kept the full list somewhere in the $GIT_DIR,\nthen other Git commands could notice that those refs are currently being\noverwritten and those updates should fail (similarly to how we currently fail\nto update a branch being rebased). This ties into your later point:\n\n>>   2. If we want to close out all of the refs as the rebase is finishing, then\n>>      that \"step\" becomes invisible to the user (and a bit more complicated to\n>>      insert). Thus, the 'update-refs' step performs this action. If the user\n>>      wants to do things after that step, then they can do so by editing the\n>>      TODO list.\n> \n> I'm not sure what the use case is for making the update-refs step visible to\n> the user - it seems to be added complexity for them to deal with. We don't do\n> that for the branch that is being rebased so why do we need to do it for the\n> other branches? As for the implementation we could just update the branches\n> at the end of the loop in pick_commits() where we update the branch and run the\n> post-rewrite hook etc. there is no need for an entry in the todo list.\n\nI concede that allowing the user to move the 'update-refs' command around is\nnot super important.\n\nThe biggest concern I originally had with this idea was that there was no\nconnection between the \"--update-refs\" option in the first \"git rebase\"\ncommand and the final \"git rebase --continue\" command. That is, other than\nwhich refs are in refs/rewritten/*.\n\nHOWEVER, using refs/rewritten/* is likely buggy: if we had two rebases\nhappening at the same time (operating on a disjoint set of refs), then how\ndo we differentiate which refs make sense for us to update as we complete\nthis rebase?\n\nSo, I'm coming to the conclusion that using refs/rewritten/* is problematic\nand I should use something closer to REBASE_HEAD as a way to describe which\nrefs are being updated. In that context, your 'update-ref <ref>' command\nmakes a lot more sense. The user can decide to move those around _or_ remove\nthem; it won't change their protection under \"REBASE_HEADS\" but would\nprevent them from being rewritten.\n\nWhile I think on this, I'll send my branch_checked_out() patches in a\nseparate thread, since those have grown in complexity since this version.\n\nThanks,\n-Stolee\n"},{"id":"456930","messageId":"2e6affba-f9ba-56da-20d6-009c5f2fbfd7@gmail.com","threadId":"57949","inReplyTo":"34264915-8a37-62db-f954-0b5297639b34@github.com","subject":"Re: [PATCH v2 0/7] rebase: update branches in multi-part topic","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-09T10:04:07Z","receivedAt":"2022-06-09T10:04:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 08/06/2022 19:09, Derrick Stolee wrote:\n> On 6/8/2022 10:32 AM, Phillip Wood wrote:\n>> Hi Stolee\n>>\n>> On 07/06/2022 21:42, Derrick Stolee via GitGitGadget wrote:\n>>> [...]\n>>> As an example, here is my in-progress bundle URI RFC split into subtopics as\n>>> they appear during the TODO list of a git rebase -i --update-refs:\n>>>\n>>> pick 2d966282ff3 docs: document bundle URI standard\n>>> pick 31396e9171a remote-curl: add 'get' capability\n>>> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n>>> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n>>> pick 6adaf842684 fetch: add --bundle-uri option\n>>> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n>>> label for-update-refs/refs/heads/bundle-redo/fetch\n>>> [...] update-refs\n>>> [...]\n>>> Based on some of the discussion, it seemed like one way to do this would be\n>>> to have an 'update-ref ' command that would take the place of these 'label'\n>>> commands. However, this would require two things that make it a bit awkward:\n>>>\n>>>    1. We would need to replicate the storage of those positions during the\n>>>       rebase. 'label' already does this pretty well. I've added the\n>>>       \"for-update-refs/\" label to help here.\n>>\n>> I'm afraid I don't think that using a label with a name constructed from\n>> a magic prefix and the full refname is a good user interface.\n>>\n>> (i) Having labels behave differently based on their name is confusing.\n> \n> The label commands do as advertised, but the 'update-refs' command does\n> something with the set of refs based on their names, yes. We would need to\n> store this information during the rebase _somewhere_, and refs/rewritten/\n> seems natural.\n>   \n>> (ii) The length of the label string is cumbersome for users. Rebase already\n>> has a reputation for being unfriendly and hard to use, this will not improve\n>> that. \"update-ref bundle-redo/fetch\" is much simpler.\n> \n> I agree that your proposed replacement for \"label for-update-refs/...\" would\n> look cleaner. I don't think that that is enough to justify hiding information\n> from the user.\n\nIn general I think it is better not to burden the user with unnecessary \ninformation - it makes the ui more complicated and exposes \nimplementation details which makes it harder to change the implementation.\n\n>> (iii) It means that we no longer store the oid of each branch when creating> the todo list and so cannot check if it is safe to update it at the end of the\n>> rebase (just using the current value of the branch ref at the end of a long\n>> running operation like rebase is not sufficient to be safe).\n> \n> The operation we are doing necessarily requires a force-update, since we are\n> modifying history. The safety you are caring about here is about the case where\n> the user modifies one of these refs between the initial 'git rebase --update-refs'\n> and the final 'git rebase --continue' that actually writes the refs. You want to\n> prevent that final update from succeeding because another change to those refs\n> could be lost.\n> \n> I'm less concerned about this case, because the user is requesting that we\n> update the refs pointing to this set of commits. But, I'm glad you brought it\n> up.\n\nAt the end of the rebase we pass the oid of the branch that we store at \nthe start of the rebase when updating the ref to avoid nasty surprises. \nI think it makes sense to do the same for the other branches being \nrewritten - it is easy to get stuck on a conflict resolution and go and \ndo something else for a while and forget a branch is being rebased. If \nwe cannot update the ref at the end of the rebase we should print the \nnew oid with instructions to run \"git reset --hard\" or \"git update-ref\" \nif the user wants to update the branch.\n\n> One way to prevent this situation would be to extend the idea of \"what branch\n> is being rebased?\" (REBASE_HEAD) to \"which branches are being rewritten?\"\n> (REBASE_HEADS?). \n\nJust to clarify REBASE_HEAD points to the commit we're currently trying \nto pick, the branch ref is stored it .git/rebase-merge/head-name and its \noid in .git/rebase-merge/orig-head\n\n> If the worktree kept the full list somewhere in the $GIT_DIR,\n> then other Git commands could notice that those refs are currently being\n> overwritten and those updates should fail (similarly to how we currently fail\n> to update a branch being rebased).\n\nThat's a good point and one that I should have remembered as I \nimplemented that check in my script. I agree that we should stop the \nuser starting a rebase in a worktree whose branch is being updated \nelsewhere. If we write a list of the refs we want to update under \n.git/rebase-merge before walking the other worktrees to see what's being \nrebased elsewhere that avoids a race where two processes start that try \nto rebase the same branch in different worktrees at the same time. It \nwould be easy to store the original oids with the ref names.\n\nAn added complexity is that we would have to check the todo list for any \nnew update-ref commands and check those refs are not being rebased \nelsewhere each time the list is edited. In the future we should update \n\"git status\" to read that file but I don't think that needs to be part \nof the initial implementation.\n\n> This ties into your later point:\n> \n>>>    2. If we want to close out all of the refs as the rebase is finishing, then\n>>>       that \"step\" becomes invisible to the user (and a bit more complicated to\n>>>       insert). Thus, the 'update-refs' step performs this action. If the user\n>>>       wants to do things after that step, then they can do so by editing the\n>>>       TODO list.\n>>\n>> I'm not sure what the use case is for making the update-refs step visible to\n>> the user - it seems to be added complexity for them to deal with. We don't do\n>> that for the branch that is being rebased so why do we need to do it for the\n>> other branches? As for the implementation we could just update the branches\n>> at the end of the loop in pick_commits() where we update the branch and run the\n>> post-rewrite hook etc. there is no need for an entry in the todo list.\n> \n> I concede that allowing the user to move the 'update-refs' command around is\n> not super important.\n> \n> The biggest concern I originally had with this idea was that there was no\n> connection between the \"--update-refs\" option in the first \"git rebase\"\n> command and the final \"git rebase --continue\" command. That is, other than\n> which refs are in refs/rewritten/*.\n> \n> HOWEVER, using refs/rewritten/* is likely buggy: if we had two rebases\n> happening at the same time (operating on a disjoint set of refs), then how\n> do we differentiate which refs make sense for us to update as we complete\n> this rebase?\n >> So, I'm coming to the conclusion that using refs/rewritten/* is \nproblematic\n> and I should use something closer to REBASE_HEAD as a way to describe which\n> refs are being updated. In that context, your 'update-ref <ref>' command\n> makes a lot more sense. The user can decide to move those around _or_ remove\n> them; it won't change their protection under \"REBASE_HEADS\" but would\n> prevent them from being rewritten.\n\nWhenever one adds a new option to rebase it almost always involves \nwriting more state to .git/rebase-merge, in this case I think we want to \nstore the branches from the update-ref commands and their original oids \nthere. I'm not sure that we want to expose the file to the user though \nso we can change the implementation in the future if we need to (e.g. we \nmay decide it is better to store this information for all worktrees \nunder a single file in $GIT_COMMON_DIR).\n\n> While I think on this, I'll send my branch_checked_out() patches in a\n> separate thread, since those have grown in complexity since this version.\n\nIf you want to discuss any ideas face-to-face drop me a email and we can \narrange a time to chat if that would be useful.\n\nBest Wishes\n\nPhillip\n\n> -Stolee\n"},{"id":"458035","messageId":"fbaedc7f1f02efec251cb4df665fd3cb71f1d3b3.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 1/8] t2407: test branches currently using apply backend","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:52Z","receivedAt":"2022-06-28T13:28:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe tests in t2407 that verify the branch_checked_out() helper in the\ncase of bisects and rebases were added by 9347303db89 (branch: check for\nbisects and rebases, 2022-06-08). However, that commit failed to check\nfor rebases that are using the 'apply' backend.\n\nAdd a test that checks the apply backend. The implementation was already\ncorrect here, but it is good to have regression tests before modifying\nthe implementation further.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n t/t2407-worktree-heads.sh | 13 ++++++++++++-\n 1 file changed, 12 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex b6be42f74a2..4f59bc21303 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -54,7 +54,18 @@ test_expect_success 'refuse to overwrite: worktree in bisect' '\n \tgrep \"cannot force update the branch '\\''fake-2'\\'' checked out at.*wt-4\" err\n '\n \n-test_expect_success 'refuse to overwrite: worktree in rebase' '\n+test_expect_success 'refuse to overwrite: worktree in rebase (apply)' '\n+\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-apply &&\n+\n+\tmkdir -p .git/worktrees/wt-3/rebase-apply &&\n+\techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-apply/head-name &&\n+\techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-apply/onto &&\n+\n+\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n+\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n+'\n+\n+test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n \ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n \n \tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n-- \ngitgitgadget\n\n"},{"id":"458036","messageId":"d2cfdbfc4319081d548d13bbd675c4385b1a5ba6.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 8/8] rebase: add rebase.updateRefs config option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:59Z","receivedAt":"2022-06-28T13:28:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change added the --update-refs command-line option.  For\nusers who always want this mode, create the rebase.updateRefs config\noption which behaves the same way as rebase.autoSquash does with the\n--autosquash option.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/config/rebase.txt |  3 +++\n Documentation/git-rebase.txt    |  3 +++\n builtin/rebase.c                |  5 +++++\n t/t3404-rebase-interactive.sh   | 14 ++++++++++++++\n 4 files changed, 25 insertions(+)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex 8c979cb20f2..f19bd0e0407 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -21,6 +21,9 @@ rebase.autoStash::\n \t`--autostash` options of linkgit:git-rebase[1].\n \tDefaults to false.\n \n+rebase.updateRefs::\n+\tIf set to true enable `--update-refs` option by default.\n+\n rebase.missingCommitsCheck::\n \tIf set to \"warn\", git rebase -i will print a warning if some\n \tcommits are removed (e.g. a line was deleted), however the\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex e7611b4089c..1249f9ed617 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -615,6 +615,9 @@ start would be overridden by the presence of\n \tare being rebased. Any branches that are checked out in a worktree\n \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n \tin this way.\n++\n+If the configuration variable `rebase.updateRefs` is set, then this option\n+can be used to override and disable this setting.\n \n INCOMPATIBLE OPTIONS\n --------------------\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 56d82a52106..8ebc98ea505 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -802,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"rebase.updaterefs\")) {\n+\t\topts->update_refs = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"rebase.reschedulefailedexec\")) {\n \t\topts->reschedule_failed_exec = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 63a69bc073e..77478d94f3b 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1768,6 +1768,12 @@ test_expect_success '--update-refs adds label and update-ref commands' '\n \t\tEOF\n \n \t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n@@ -1809,6 +1815,14 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t\t\t\t   --rebase-merges=rebase-cousins \\\n \t\t\t\t   --update-refs primary >todo &&\n \n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n-- \ngitgitgadget\n"},{"id":"458037","messageId":"918b398d6a2264a99acadd319f780c796bbafc18.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 6/8] rebase: add --update-refs option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:57Z","receivedAt":"2022-06-28T13:28:17Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen working on a large feature, it can be helpful to break that feature\ninto multiple smaller parts that become reviewed in sequence. During\ndevelopment or during review, a change to one part of the feature could\naffect multiple of these parts. An interactive rebase can help adjust\nthe multi-part \"story\" of the branch.\n\nHowever, if there are branches tracking the different parts of the\nfeature, then rebasing the entire list of commits can create commits not\nreachable from those \"sub branches\". It can take a manual step to update\nthose branches.\n\nAdd a new --update-refs option to 'git rebase -i' that adds 'update-ref\n<ref>' steps to the todo file whenever a commit that is being rebased is\ndecorated with that <ref>. At the very end, the rebase process updates\nall of the listed refs to the values stored during the rebase operation.\n\nBe sure to iterate after any squashing or fixups are placed. Update the\nbranch only after those squashes and fixups are complete. This allows a\n--fixup commit at the tip of the feature to apply correctly to the sub\nbranch, even if it is fixing up the most-recent commit in that part.\n\nOne potential problem here is that refs decorating commits that are\nalready marked as \"fixup!\" or \"squash!\" will not be included in this\nlist. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\nto change the relative order of these refs, so it is not recommended.\nThe workflow here is intended to allow these kinds of commits at the tip\nof the rebased branch while the other sub branches come along for the\nride without intervention.\n\nThis change update the documentation and builtin to accept the\n--update-refs option as well as updating the todo file with the\n'update-ref' commands. Tests are added to ensure that these todo\ncommands are added in the correct locations.\n\nA future change will update the behavior to actually update the refs\nat the end of the rebase sequence.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/git-rebase.txt  |   8 +++\n builtin/rebase.c              |   5 ++\n sequencer.c                   | 107 ++++++++++++++++++++++++++++++++++\n sequencer.h                   |   1 +\n t/t2407-worktree-heads.sh     |  17 ++++++\n t/t3404-rebase-interactive.sh |  70 ++++++++++++++++++++++\n 6 files changed, 208 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 262fb01aec0..e7611b4089c 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -609,6 +609,13 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n start would be overridden by the presence of\n `rebase.rescheduleFailedExec=true` configuration.\n \n+--update-refs::\n+--no-update-refs::\n+\tAutomatically force-update any branches that point to commits that\n+\tare being rebased. Any branches that are checked out in a worktree\n+\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n+\tin this way.\n+\n INCOMPATIBLE OPTIONS\n --------------------\n \n@@ -632,6 +639,7 @@ are incompatible with the following options:\n  * --empty=\n  * --reapply-cherry-picks\n  * --edit-todo\n+ * --update-refs\n  * --root when used in combination with --onto\n \n In addition, the following pairs of options are incompatible:\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7ab50cda2ad..56d82a52106 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -102,6 +102,7 @@ struct rebase_options {\n \tint reschedule_failed_exec;\n \tint reapply_cherry_picks;\n \tint fork_point;\n+\tint update_refs;\n };\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n@@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \t\tret = complete_action(the_repository, &replay, flags,\n \t\t\tshortrevisions, opts->onto_name, opts->onto,\n \t\t\t&opts->orig_head, &commands, opts->autosquash,\n+\t\t\topts->update_refs,\n \t\t\t&todo_list);\n \t}\n \n@@ -1124,6 +1126,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n+\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n+\t\t\t N_(\"update local refs that point to commits \"\n+\t\t\t    \"that are being rebased\")),\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n \t\t\tN_(\"GPG-sign commits\"),\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\ndiff --git a/sequencer.c b/sequencer.c\nindex 0b61835d441..915d87a0336 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -35,6 +35,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"branch.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -5615,10 +5616,113 @@ static int skip_unnecessary_picks(struct repository *r,\n \treturn 0;\n }\n \n+struct todo_add_branch_context {\n+\tstruct todo_item *items;\n+\tsize_t items_nr;\n+\tsize_t items_alloc;\n+\tstruct strbuf *buf;\n+\tstruct commit *commit;\n+\tstruct string_list refs_to_oids;\n+};\n+\n+static int add_decorations_to_list(const struct commit *commit,\n+\t\t\t\t   struct todo_add_branch_context *ctx)\n+{\n+\tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n+\n+\twhile (decoration) {\n+\t\tstruct todo_item *item;\n+\t\tconst char *path;\n+\t\tsize_t base_offset = ctx->buf->len;\n+\n+\t\tALLOC_GROW(ctx->items,\n+\t\t\tctx->items_nr + 1,\n+\t\t\tctx->items_alloc);\n+\t\titem = &ctx->items[ctx->items_nr];\n+\t\tmemset(item, 0, sizeof(*item));\n+\n+\t\t/* If the branch is checked out, then leave a comment instead. */\n+\t\tif ((path = branch_checked_out(decoration->name))) {\n+\t\t\titem->command = TODO_COMMENT;\n+\t\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n+\t\t\t\t    decoration->name, path);\n+\t\t} else {\n+\t\t\tstruct string_list_item *sti;\n+\t\t\titem->command = TODO_UPDATE_REF;\n+\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n+\n+\t\t\tsti = string_list_append(&ctx->refs_to_oids,\n+\t\t\t\t\t\t decoration->name);\n+\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n+\t\t}\n+\n+\t\titem->offset_in_buf = base_offset;\n+\t\titem->arg_offset = base_offset;\n+\t\titem->arg_len = ctx->buf->len - base_offset;\n+\t\tctx->items_nr++;\n+\n+\t\tdecoration = decoration->next;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * For each 'pick' command, find out if the commit has a decoration in\n+ * refs/heads/. If so, then add a 'label for-update-refs/' command.\n+ */\n+static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n+{\n+\tint i;\n+\tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n+\tstruct decoration_filter decoration_filter = {\n+\t\t.include_ref_pattern = &decorate_refs_include,\n+\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n+\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config,\n+\t};\n+\tstruct todo_add_branch_context ctx = {\n+\t\t.buf = &todo_list->buf,\n+\t\t.refs_to_oids = STRING_LIST_INIT_DUP,\n+\t};\n+\n+\tctx.items_alloc = 2 * todo_list->nr + 1;\n+\tALLOC_ARRAY(ctx.items, ctx.items_alloc);\n+\n+\tstring_list_append(&decorate_refs_include, \"refs/heads/\");\n+\tload_ref_decorations(&decoration_filter, 0);\n+\n+\tfor (i = 0; i < todo_list->nr; ) {\n+\t\tstruct todo_item *item = &todo_list->items[i];\n+\n+\t\t/* insert ith item into new list */\n+\t\tALLOC_GROW(ctx.items,\n+\t\t\t   ctx.items_nr + 1,\n+\t\t\t   ctx.items_alloc);\n+\n+\t\tctx.items[ctx.items_nr++] = todo_list->items[i++];\n+\n+\t\tif (item->commit) {\n+\t\t\tctx.commit = item->commit;\n+\t\t\tadd_decorations_to_list(item->commit, &ctx);\n+\t\t}\n+\t}\n+\n+\tstring_list_clear(&ctx.refs_to_oids, 1);\n+\tfree(todo_list->items);\n+\ttodo_list->items = ctx.items;\n+\ttodo_list->nr = ctx.items_nr;\n+\ttodo_list->alloc = ctx.items_alloc;\n+\n+\treturn 0;\n+}\n+\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list)\n {\n \tchar shortonto[GIT_MAX_HEXSZ + 1];\n@@ -5637,6 +5741,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\titem->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n \t}\n \n+\tif (update_refs && todo_list_add_update_ref_commands(todo_list))\n+\t\treturn -1;\n+\n \tif (autosquash && todo_list_rearrange_squash(todo_list))\n \t\treturn -1;\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 2cf5c1b9a38..e671d7e0743 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -167,6 +167,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list);\n int todo_list_rearrange_squash(struct todo_list *todo_list);\n \ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex f1e9e172a0c..203997d92c6 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -151,4 +151,21 @@ test_expect_success 'refuse to overwrite when in error states' '\n \tdone\n '\n \n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite during rebase with --update-refs' '\n+\tgit commit --fixup HEAD~2 --allow-empty &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n+\t\t! grep \"update-refs\" todo\n+\t) &&\n+\tgit branch -f allow-update HEAD~2 &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n+\t\tgrep \"update-ref refs/heads/allow-update\" todo\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f31afd4a547..3cd20733bc8 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1743,6 +1743,76 @@ test_expect_success 'ORIG_HEAD is updated correctly' '\n \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n '\n \n+test_expect_success '--update-refs adds label and update-ref commands' '\n+\tgit checkout -b update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit commit --allow-empty --fixup=third &&\n+\tgit branch -f shared-tip &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tpick $(git log -1 --format=%h J) J\n+\t\tupdate-ref refs/heads/second\n+\t\tupdate-ref refs/heads/first\n+\t\tpick $(git log -1 --format=%h K) K\n+\t\tpick $(git log -1 --format=%h L) L\n+\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n+\t\tupdate-ref refs/heads/third\n+\t\tpick $(git log -1 --format=%h M) M\n+\t\tupdate-ref refs/heads/no-conflict-branch\n+\t\tupdate-ref refs/heads/shared-tip\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n+test_expect_success '--update-refs adds commands with --rebase-merges' '\n+\tgit checkout -b update-refs-with-merge no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit merge -m merge branch2 &&\n+\tgit branch -f merge-branch &&\n+\tgit commit --fixup=third --allow-empty &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tlabel onto\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h branch2~1) F\n+\t\tpick $(git log -1 --format=%h branch2) I\n+\t\tupdate-ref refs/heads/branch2\n+\t\tlabel merge\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h refs/heads/second) J\n+\t\tupdate-ref refs/heads/second\n+\t\tupdate-ref refs/heads/first\n+\t\tpick $(git log -1 --format=%h refs/heads/third~1) K\n+\t\tpick $(git log -1 --format=%h refs/heads/third) L\n+\t\tfixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n+\t\tupdate-ref refs/heads/third\n+\t\tpick $(git log -1 --format=%h HEAD~2) M\n+\t\tupdate-ref refs/heads/no-conflict-branch\n+\t\tmerge -C $(git log -1 --format=%h HEAD~1) merge # merge\n+\t\tupdate-ref refs/heads/merge-branch\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   --update-refs primary >todo &&\n+\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"458038","messageId":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v2.git.1654634569.gitgitgadget@gmail.com","subject":"[PATCH v3 0/8] rebase: update branches in multi-part topic","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:51Z","receivedAt":"2022-06-28T13:28:20Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"(Update: This is now based on ds/branch-checked-out.)\n\nThis is a feature I've wanted for quite a while. When working on the sparse\nindex topic, I created a long RFC that actually broke into three topics for\nfull review upstream. These topics were sequential, so any feedback on an\nearlier one required updates to the later ones. I would work on the full\nfeature and use interactive rebase to update the full list of commits.\nHowever, I would need to update the branches pointing to those sub-topics.\n\nThis series adds a new --update-refs option to 'git rebase' (along with a\nrebase.updateRefs config option) that adds 'update-ref' commands into the\nTODO list. This is powered by the commit decoration machinery.\n\nAs an example, here is my in-progress bundle URI RFC split into subtopics as\nthey appear during the TODO list of a git rebase -i --update-refs:\n\npick 2d966282ff3 docs: document bundle URI standard\npick 31396e9171a remote-curl: add 'get' capability\npick 54c6ab70f67 bundle-uri: create basic file-copy logic\npick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\npick 6adaf842684 fetch: add --bundle-uri option\npick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\nupdate-ref refs/heads/bundle-redo/fetch\n\npick 1e3f6546632 clone: add --bundle-uri option\npick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\nupdate-ref refs/heads/bundle-redo/clone\n\npick 5451cb6599c bundle-uri: create bundle_list struct and helpers\npick 3029c3aca15 bundle-uri: create base key-value pair parsing\npick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\npick 92625a47673 bundle-uri: unit test \"key=value\" parsing\npick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\npick 9d6809a8d53 bundle-uri: parse bundle list in config format\npick 287a732b54c bundle-uri: fetch a list of bundles\nupdate-ref refs/heads/bundle-redo/list\n\npick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\npick 520204dcd1c bundle-uri client: add minimal NOOP client\npick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\npick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\npick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\npick caf4599a81d bundle-uri: allow relative URLs in bundle lists\npick df255000b7e bundle-uri: download bundles from an advertised list\npick d71beabf199 clone: unbundle the advertised bundles\npick c9578391976 t5601: basic bundle URI tests\n# Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n\nupdate-ref refs/heads/bundle-redo/advertise\n\n\nHere is an outline of the series:\n\n * Patch 1 updates some tests for branch_checked_out() for the 'apply'\n   backend.\n * Patch 2 updates branch_checked_out() to parse the\n   rebase-merge/update-refs file to block concurrent ref updates and\n   checkouts on branches selected by --update-refs.\n * Patch 3 updates the todo list documentation to remove some unnecessary\n   dots in the 'merge' command. This makes it consistent with the 'fixup'\n   command before we document the 'update-ref' command.\n * Patch 4 updates the definition of todo_command_info to use enum values as\n   array indices.\n * Patches 5-7 implement the --update-refs logic itself.\n * Patch 8 adds the rebase.updateRefs config option similar to\n   rebase.autoSquash.\n\n\nUpdates in v3\n=============\n\n * The branch_checked_out() API was extracted to its own topic and is now\n   the ds/branch-checked-out branch. This series is now based on that one.\n * The for_each_decoration() API was removed, since it became trivial once\n   it did not take a commit directly.\n * The branch_checked_out() tests did not verify the rebase-apply data (for\n   the apply backend), so that is fixed.\n * Instead of using the 'label' command and a final 'update-refs' command in\n   the todo list, use a new 'update-ref ' command. This command updates the\n   rebase-merge/update-refs file with the OID of HEAD at these steps. At the\n   very end of the rebase sequence, those refs are updated to the stored OID\n   values (assuming that they were not removed by the user, in which case we\n   notice that the OID is the null OID and we do nothing).\n * New tests are added.\n * The todo-list comment documentation has some new formatting updates, but\n   also includes a description of 'update-refs' in this version.\n\n\nUpdates in v2\n=============\n\nAs recommended by the excellent feedback, I have removed the 'exec' commands\nin favor of the 'label' commands and a new 'update-refs' command at the very\nend. This way, there is only one step that updates all of the refs at the\nend instead of updating refs during the rebase. If a user runs 'git rebase\n--abort' in the middle, then their refs are still where they need to be.\n\nBased on some of the discussion, it seemed like one way to do this would be\nto have an 'update-ref ' command that would take the place of these 'label'\ncommands. However, this would require two things that make it a bit awkward:\n\n 1. We would need to replicate the storage of those positions during the\n    rebase. 'label' already does this pretty well. I've added the\n    \"for-update-refs/\" label to help here.\n 2. If we want to close out all of the refs as the rebase is finishing, then\n    that \"step\" becomes invisible to the user (and a bit more complicated to\n    insert). Thus, the 'update-refs' step performs this action. If the user\n    wants to do things after that step, then they can do so by editing the\n    TODO list.\n\nOther updates:\n\n * The 'keep_decorations' parameter was renamed to 'update_refs'.\n * I added tests for --rebase-merges=rebase-cousins to show how these labels\n   interact with other labels and merge commands.\n * I changed the order of the insertion of these update-refs labels to be\n   before the fixups are rearranged. This fixes a bug where the tip commit\n   is a fixup! so its decorations are never inspected (and they would be in\n   the wrong place even if they were). The fixup! commands are properly\n   inserted between a pick and its following label command. Tests\n   demonstrate this is correct.\n * Numerous style choices are updated based on feedback.\n\nThank you for all of the detailed review and ideas in this space. I\nappreciate any more ideas that can make this feature as effective as it can\nbe.\n\nThanks, -Stolee\n\nDerrick Stolee (8):\n  t2407: test branches currently using apply backend\n  branch: consider refs under 'update-refs'\n  rebase-interactive: update 'merge' description\n  sequencer: define array with enum values\n  sequencer: add update-ref command\n  rebase: add --update-refs option\n  rebase: update refs from 'update-ref' commands\n  rebase: add rebase.updateRefs config option\n\n Documentation/config/rebase.txt |   3 +\n Documentation/git-rebase.txt    |  11 ++\n branch.c                        |  13 ++\n builtin/rebase.c                |  10 ++\n rebase-interactive.c            |   9 +-\n sequencer.c                     | 303 ++++++++++++++++++++++++++++++--\n sequencer.h                     |  11 ++\n t/t2407-worktree-heads.sh       |  52 +++++-\n t/t3404-rebase-interactive.sh   | 107 +++++++++++\n 9 files changed, 496 insertions(+), 23 deletions(-)\n\n\nbase-commit: 9bef0b1e6ec371e786c2fba3edcc06ad040a536c\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1247\n\nRange-diff vs v2:\n\n 1:  4f9f3487641 < -:  ----------- log-tree: create for_each_decoration()\n 2:  5f54766e103 < -:  ----------- branch: add branch_checked_out() helper\n -:  ----------- > 1:  fbaedc7f1f0 t2407: test branches currently using apply backend\n -:  ----------- > 2:  2bc647b6fcd branch: consider refs under 'update-refs'\n -:  ----------- > 3:  669f4abd59e rebase-interactive: update 'merge' description\n 3:  9f261c7df2c = 4:  6528a50343f sequencer: define array with enum values\n 4:  842b2186d25 ! 5:  e95ad41d355 sequencer: add update-refs command\n     @@ Metadata\n      Author: Derrick Stolee <derrickstolee@github.com>\n      \n       ## Commit message ##\n     -    sequencer: add update-refs command\n     +    sequencer: add update-ref command\n      \n     -    Add the boilerplat for an \"update-refs\" command in the sequencer. This\n     -    connects to the current no-op do_update_refs() which will be filled in\n     +    Add the boilerplate for an \"update-ref\" command in the sequencer. This\n     +    connects to the current no-op do_update_ref() which will be filled in\n          after more connections are created.\n      \n     +    The syntax in the todo list will be \"update-ref <ref-name>\" to signal\n     +    that we should store the current commit as the value for updating\n     +    <ref-name> at the end of the rebase.\n     +\n          Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n      \n     + ## rebase-interactive.c ##\n     +@@ rebase-interactive.c: void append_todo_help(int command_count,\n     + \"        create a merge commit using the original merge commit's\\n\"\n     + \"        message (or the oneline, if no original merge commit was\\n\"\n     + \"        specified); use -c <commit> to reword the commit message\\n\"\n     ++\"u, update-ref <ref> = track a placeholder for the <ref> to be updated\\n\"\n     ++\"                      to this position in the new commits. The <ref> is\\n\"\n     ++\"                      updated at the end of the rebase\\n\"\n     + \"\\n\"\n     + \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n     + \tunsigned edit_todo = !(shortrevisions && shortonto);\n     +\n       ## sequencer.c ##\n      @@ sequencer.c: static struct {\n       \t[TODO_LABEL] = { 'l', \"label\" },\n       \t[TODO_RESET] = { 't', \"reset\" },\n       \t[TODO_MERGE] = { 'm', \"merge\" },\n     -+\t[TODO_UPDATE_REFS] = { 'u', \"update-refs\" },\n     ++\t[TODO_UPDATE_REF] = { 'u', \"update-ref\" },\n       \t[TODO_NOOP] = { 0,   \"noop\" },\n       \t[TODO_DROP] = { 'd', \"drop\" },\n       \t[TODO_COMMENT] = { 0,   NULL },\n      @@ sequencer.c: static int parse_insn_line(struct repository *r, struct todo_item *item,\n     - \tpadding = strspn(bol, \" \\t\");\n     - \tbol += padding;\n     + \t\t\t     command_to_string(item->command));\n       \n     --\tif (item->command == TODO_NOOP || item->command == TODO_BREAK) {\n     -+\tif (item->command == TODO_NOOP ||\n     -+\t    item->command == TODO_BREAK ||\n     -+\t    item->command == TODO_UPDATE_REFS) {\n     - \t\tif (bol != eol)\n     - \t\t\treturn error(_(\"%s does not accept arguments: '%s'\"),\n     - \t\t\t\t     command_to_string(item->command), bol);\n     + \tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n     +-\t    item->command == TODO_RESET) {\n     ++\t    item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {\n     + \t\titem->commit = NULL;\n     + \t\titem->arg_offset = bol - buf;\n     + \t\titem->arg_len = (int)(eol - bol);\n      @@ sequencer.c: leave_merge:\n       \treturn ret;\n       }\n       \n     -+static int do_update_refs(struct repository *r)\n     ++static int do_update_ref(struct repository *r, const char *ref_name)\n      +{\n      +\treturn 0;\n      +}\n     @@ sequencer.c: static int pick_commits(struct repository *r,\n       \t\t\t\treturn error_with_patch(r, item->commit,\n       \t\t\t\t\t\t\targ, item->arg_len,\n       \t\t\t\t\t\t\topts, res, 0);\n     -+\t\t} else if (item->command == TODO_UPDATE_REFS) {\n     -+\t\t\tif ((res = do_update_refs(r)))\n     ++\t\t} else if (item->command == TODO_UPDATE_REF) {\n     ++\t\t\tstruct strbuf ref = STRBUF_INIT;\n     ++\t\t\tstrbuf_add(&ref, arg, item->arg_len);\n     ++\t\t\tif ((res = do_update_ref(r, ref.buf)))\n      +\t\t\t\treschedule = 1;\n     ++\t\t\tstrbuf_release(&ref);\n       \t\t} else if (!is_noop(item->command))\n       \t\t\treturn error(_(\"unknown command %d\"), item->command);\n       \n     @@ sequencer.h: enum todo_command {\n       \tTODO_LABEL,\n       \tTODO_RESET,\n       \tTODO_MERGE,\n     -+\tTODO_UPDATE_REFS,\n     ++\tTODO_UPDATE_REF,\n       \t/* commands that do nothing but are counted for reporting progress */\n       \tTODO_NOOP,\n       \tTODO_DROP,\n 5:  0a4c110127b ! 6:  918b398d6a2 rebase: add --update-refs option\n     @@ Commit message\n          reachable from those \"sub branches\". It can take a manual step to update\n          those branches.\n      \n     -    Add a new --update-refs option to 'git rebase -i' that adds 'label\n     -    for-update-refs/*' steps to the todo file whenever a commit that is\n     -    being rebased is decorated with that <ref>. At the very end, the\n     -    'update-refs' step is added to update all of the branches referenced by\n     -    the 'label' steps. This allows the user to rebase a long list of commits\n     -    in a multi-part feature and keep all of their pointers to those parts.\n     +    Add a new --update-refs option to 'git rebase -i' that adds 'update-ref\n     +    <ref>' steps to the todo file whenever a commit that is being rebased is\n     +    decorated with that <ref>. At the very end, the rebase process updates\n     +    all of the listed refs to the values stored during the rebase operation.\n      \n     -    NOTE: This change only introduce the --update-refs option and implements\n     -    the changes to the todo file. It does _not_ yet implement the action\n     -    taken by the 'update-refs' todo step, which will be implemented and\n     -    tested in a later change.\n     -\n     -    Use the new for_each_decoration() while iterating over the existing todo\n     -    list. Be sure to iterate after any squashing or fixups are placed.\n     -    Update the branch only after those squashes and fixups are complete.\n     -    This allows a --fixup commit at the tip of the feature to apply\n     -    correctly to the sub branch, even if it is fixing up the most-recent\n     -    commit in that part.\n     +    Be sure to iterate after any squashing or fixups are placed. Update the\n     +    branch only after those squashes and fixups are complete. This allows a\n     +    --fixup commit at the tip of the feature to apply correctly to the sub\n     +    branch, even if it is fixing up the most-recent commit in that part.\n      \n          One potential problem here is that refs decorating commits that are\n          already marked as \"fixup!\" or \"squash!\" will not be included in this\n     @@ Commit message\n          of the rebased branch while the other sub branches come along for the\n          ride without intervention.\n      \n     -    Be careful to not attempt updating any branch that is checked out. The\n     -    most common example is the branch being rebased is checked out and\n     -    decorates the tip commit. If the user is rebasing commits reachable from\n     -    a different branch that is checked out in a different worktree, then\n     -    they may be surprised to not see that ref update. However, it's probably\n     -    best to not optimize for this scenario and do the safest thing that will\n     -    result in a successful rebase. A comment is left in the TODO list that\n     -    signals that these refs are currently checked out.\n     +    This change update the documentation and builtin to accept the\n     +    --update-refs option as well as updating the todo file with the\n     +    'update-ref' commands. Tests are added to ensure that these todo\n     +    commands are added in the correct locations.\n     +\n     +    A future change will update the behavior to actually update the refs\n     +    at the end of the rebase sequence.\n      \n          Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n      \n     @@ sequencer.c\n       #include \"rebase-interactive.h\"\n       #include \"reset.h\"\n      +#include \"branch.h\"\n     -+#include \"log-tree.h\"\n       \n       #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n       \n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n      +\tsize_t items_alloc;\n      +\tstruct strbuf *buf;\n      +\tstruct commit *commit;\n     ++\tstruct string_list refs_to_oids;\n      +};\n      +\n     -+static int add_branch_for_decoration(const struct name_decoration *d, void *data)\n     ++static int add_decorations_to_list(const struct commit *commit,\n     ++\t\t\t\t   struct todo_add_branch_context *ctx)\n      +{\n     -+\tstruct todo_add_branch_context *ctx = data;\n     -+\tsize_t base_offset = ctx->buf->len;\n     -+\tstruct todo_item *item;\n     -+\tchar *path;\n     ++\tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n      +\n     -+\tALLOC_GROW(ctx->items,\n     -+\t\t   ctx->items_nr + 1,\n     -+\t\t   ctx->items_alloc);\n     -+\titem = &ctx->items[ctx->items_nr];\n     -+\tmemset(item, 0, sizeof(*item));\n     ++\twhile (decoration) {\n     ++\t\tstruct todo_item *item;\n     ++\t\tconst char *path;\n     ++\t\tsize_t base_offset = ctx->buf->len;\n      +\n     -+\t/* If the branch is checked out, then leave a comment instead. */\n     -+\tif (branch_checked_out(d->name, &path)) {\n     -+\t\titem->command = TODO_COMMENT;\n     -+\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n     -+\t\t\t    d->name, path);\n     -+\t\tfree(path);\n     -+\t} else {\n     -+\t\titem->command = TODO_LABEL;\n     -+\t\tstrbuf_addf(ctx->buf, \"for-update-refs/%s\\n\", d->name);\n     -+\t}\n     ++\t\tALLOC_GROW(ctx->items,\n     ++\t\t\tctx->items_nr + 1,\n     ++\t\t\tctx->items_alloc);\n     ++\t\titem = &ctx->items[ctx->items_nr];\n     ++\t\tmemset(item, 0, sizeof(*item));\n     ++\n     ++\t\t/* If the branch is checked out, then leave a comment instead. */\n     ++\t\tif ((path = branch_checked_out(decoration->name))) {\n     ++\t\t\titem->command = TODO_COMMENT;\n     ++\t\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n     ++\t\t\t\t    decoration->name, path);\n     ++\t\t} else {\n     ++\t\t\tstruct string_list_item *sti;\n     ++\t\t\titem->command = TODO_UPDATE_REF;\n     ++\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n     ++\n     ++\t\t\tsti = string_list_append(&ctx->refs_to_oids,\n     ++\t\t\t\t\t\t decoration->name);\n     ++\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n     ++\t\t}\n     ++\n     ++\t\titem->offset_in_buf = base_offset;\n     ++\t\titem->arg_offset = base_offset;\n     ++\t\titem->arg_len = ctx->buf->len - base_offset;\n     ++\t\tctx->items_nr++;\n      +\n     -+\titem->offset_in_buf = base_offset;\n     -+\titem->arg_offset = base_offset;\n     -+\titem->arg_len = ctx->buf->len - base_offset;\n     -+\tctx->items_nr++;\n     ++\t\tdecoration = decoration->next;\n     ++\t}\n      +\n      +\treturn 0;\n      +}\n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n      +\tstruct decoration_filter decoration_filter = {\n      +\t\t.include_ref_pattern = &decorate_refs_include,\n      +\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n     -+\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config\n     ++\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config,\n      +\t};\n      +\tstruct todo_add_branch_context ctx = {\n      +\t\t.buf = &todo_list->buf,\n     ++\t\t.refs_to_oids = STRING_LIST_INIT_DUP,\n      +\t};\n      +\n      +\tctx.items_alloc = 2 * todo_list->nr + 1;\n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n      +\n      +\t\tif (item->commit) {\n      +\t\t\tctx.commit = item->commit;\n     -+\t\t\tfor_each_decoration(item->commit,\n     -+\t\t\t\t\t    add_branch_for_decoration,\n     -+\t\t\t\t\t    &ctx);\n     ++\t\t\tadd_decorations_to_list(item->commit, &ctx);\n      +\t\t}\n      +\t}\n      +\n     -+\t/* Add the \"update-refs\" step. */\n     -+\tALLOC_GROW(ctx.items,\n     -+\t\t   ctx.items_nr + 1,\n     -+\t\t   ctx.items_alloc);\n     -+\tmemset(&ctx.items[ctx.items_nr], 0, sizeof(struct todo_item));\n     -+\tctx.items[ctx.items_nr].command = TODO_UPDATE_REFS;\n     -+\tctx.items_nr++;\n     -+\n     ++\tstring_list_clear(&ctx.refs_to_oids, 1);\n      +\tfree(todo_list->items);\n      +\ttodo_list->items = ctx.items;\n      +\ttodo_list->nr = ctx.items_nr;\n     @@ sequencer.h: int complete_action(struct repository *r, struct replay_opts *opts,\n       int todo_list_rearrange_squash(struct todo_list *todo_list);\n       \n      \n     + ## t/t2407-worktree-heads.sh ##\n     +@@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite when in error states' '\n     + \tdone\n     + '\n     + \n     ++. \"$TEST_DIRECTORY\"/lib-rebase.sh\n     ++\n     ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite during rebase with --update-refs' '\n     ++\tgit commit --fixup HEAD~2 --allow-empty &&\n     ++\t(\n     ++\t\tset_cat_todo_editor &&\n     ++\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n     ++\t\t! grep \"update-refs\" todo\n     ++\t) &&\n     ++\tgit branch -f allow-update HEAD~2 &&\n     ++\t(\n     ++\t\tset_cat_todo_editor &&\n     ++\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n     ++\t\tgrep \"update-ref refs/heads/allow-update\" todo\n     ++\t)\n     ++'\n     ++\n     + test_done\n     +\n       ## t/t3404-rebase-interactive.sh ##\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correctly' '\n       \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correct\n      +\n      +\t\tcat >expect <<-EOF &&\n      +\t\tpick $(git log -1 --format=%h J) J\n     -+\t\tlabel for-update-refs/refs/heads/second\n     -+\t\tlabel for-update-refs/refs/heads/first\n     ++\t\tupdate-ref refs/heads/second\n     ++\t\tupdate-ref refs/heads/first\n      +\t\tpick $(git log -1 --format=%h K) K\n      +\t\tpick $(git log -1 --format=%h L) L\n      +\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n     -+\t\tlabel for-update-refs/refs/heads/third\n     ++\t\tupdate-ref refs/heads/third\n      +\t\tpick $(git log -1 --format=%h M) M\n     -+\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n     -+\t\tlabel for-update-refs/refs/heads/shared-tip\n     -+\t\tupdate-refs\n     ++\t\tupdate-ref refs/heads/no-conflict-branch\n     ++\t\tupdate-ref refs/heads/shared-tip\n      +\t\tEOF\n      +\n      +\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correct\n      +\t\treset onto\n      +\t\tpick $(git log -1 --format=%h branch2~1) F\n      +\t\tpick $(git log -1 --format=%h branch2) I\n     -+\t\tlabel for-update-refs/refs/heads/branch2\n     ++\t\tupdate-ref refs/heads/branch2\n      +\t\tlabel merge\n      +\t\treset onto\n      +\t\tpick $(git log -1 --format=%h refs/heads/second) J\n     -+\t\tlabel for-update-refs/refs/heads/second\n     -+\t\tlabel for-update-refs/refs/heads/first\n     ++\t\tupdate-ref refs/heads/second\n     ++\t\tupdate-ref refs/heads/first\n      +\t\tpick $(git log -1 --format=%h refs/heads/third~1) K\n      +\t\tpick $(git log -1 --format=%h refs/heads/third) L\n      +\t\tfixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n     -+\t\tlabel for-update-refs/refs/heads/third\n     ++\t\tupdate-ref refs/heads/third\n      +\t\tpick $(git log -1 --format=%h HEAD~2) M\n     -+\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n     ++\t\tupdate-ref refs/heads/no-conflict-branch\n      +\t\tmerge -C $(git log -1 --format=%h HEAD~1) merge # merge\n     -+\t\tlabel for-update-refs/refs/heads/merge-branch\n     -+\t\tupdate-refs\n     ++\t\tupdate-ref refs/heads/merge-branch\n      +\t\tEOF\n      +\n      +\t\ttest_must_fail git rebase -i --autosquash \\\n 6:  68f8e51b19c ! 7:  72e0481b643 sequencer: implement 'update-refs' command\n     @@ Metadata\n      Author: Derrick Stolee <derrickstolee@github.com>\n      \n       ## Commit message ##\n     -    sequencer: implement 'update-refs' command\n     +    rebase: update refs from 'update-ref' commands\n      \n     -    The previous change allowed 'git rebase --update-refs' to create 'label'\n     -    commands for each branch  among the commits being rewritten and add an\n     -    'update-refs' command at the end of the todo list. Now, teach Git to\n     -    update the refs during that final 'update-refs' command.\n     +    The previous change introduced the 'git rebase --update-refs' option\n     +    which added 'update-ref <ref>' commands to the todo list of an\n     +    interactive rebase.\n      \n     -    We need to create an array of new and old OIDs for each ref by iterating\n     -    over the refs/rewritten/for-update-refs/ namespace. We cannot update the\n     -    refs in-place since this will confuse the refs iterator.\n     +    Teach Git to record the HEAD position when reaching these 'update-ref'\n     +    commands. The ref/OID pair is stored in the\n     +    $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n     +    file to avoid having other processes updating the refs in that file\n     +    while the rebase is in progress.\n     +\n     +    Not only do we update the file when the sequencer reaches these\n     +    'update-ref' commands, we then update the refs themselves at the end of\n     +    the rebase sequence. If the rebase is aborted before this final step,\n     +    then the refs are not updated.\n      \n          Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n      \n       ## sequencer.c ##\n     +@@\n     + #include \"rebase-interactive.h\"\n     + #include \"reset.h\"\n     + #include \"branch.h\"\n     ++#include \"log-tree.h\"\n     + \n     + #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n     + \n     +@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n     +  */\n     + static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n     + \n     ++/*\n     ++ * The update-refs file stores a list of refs that will be updated at the end\n     ++ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n     ++ * update the OIDs for the refs in this file, but the refs are not updated\n     ++ * until the end of the rebase sequence.\n     ++ */\n     ++static GIT_PATH_FUNC(rebase_path_update_refs, \"rebase-merge/update-refs\")\n     ++\n     + /*\n     +  * The following files are written by git-rebase just after parsing the\n     +  * command-line.\n      @@ sequencer.c: leave_merge:\n       \treturn ret;\n       }\n       \n     --static int do_update_refs(struct repository *r)\n     -+struct update_refs_context {\n     -+\tstruct ref_store *refs;\n     -+\tchar **ref_names;\n     -+\tstruct object_id *old;\n     -+\tstruct object_id *new;\n     -+\tsize_t nr;\n     -+\tsize_t alloc;\n     -+};\n     -+\n     -+static int add_ref_to_context(const char *refname,\n     -+\t\t\t      const struct object_id *oid,\n     -+\t\t\t      int flags,\n     -+\t\t\t      void *data)\n     +-static int do_update_ref(struct repository *r, const char *ref_name)\n     ++static int write_update_refs_state(struct string_list *refs_to_oids)\n     ++{\n     ++\tint result = 0;\n     ++\tFILE *fp = NULL;\n     ++\tstruct string_list_item *item;\n     ++\tchar *path = xstrdup(rebase_path_update_refs());\n     ++\n     ++\tif (safe_create_leading_directories(path)) {\n     ++\t\tresult = error(_(\"unable to create leading directories of %s\"),\n     ++\t\t\t       path);\n     ++\t\tgoto cleanup;\n     ++\t}\n     ++\n     ++\tfp = fopen(path, \"w\");\n     ++\tif (!fp) {\n     ++\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n     ++\t\tgoto cleanup;\n     ++\t}\n     ++\n     ++\tfor_each_string_list_item(item, refs_to_oids)\n     ++\t\tfprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n     ++\n     ++cleanup:\n     ++\tif (fp)\n     ++\t\tfclose(fp);\n     ++\treturn result;\n     ++}\n     ++\n     ++static int do_update_ref(struct repository *r, const char *refname)\n       {\n     -+\tint f = 0;\n     -+\tconst char *name;\n     -+\tstruct update_refs_context *ctx = data;\n     ++\tstruct string_list_item *item;\n     ++\tstruct string_list list = STRING_LIST_INIT_DUP;\n     ++\tint found = 0;\n     ++\n     ++\tsequencer_get_update_refs_state(r->gitdir, &list);\n      +\n     -+\tALLOC_GROW(ctx->ref_names, ctx->nr + 1, ctx->alloc);\n     -+\tALLOC_GROW(ctx->old, ctx->nr + 1, ctx->alloc);\n     -+\tALLOC_GROW(ctx->new, ctx->nr + 1, ctx->alloc);\n     ++\tfor_each_string_list_item(item, &list) {\n     ++\t\tif (!strcmp(item->string, refname)) {\n     ++\t\t\tstruct object_id oid;\n     ++\t\t\tfree(item->util);\n     ++\t\t\tfound = 1;\n      +\n     -+\tif (!skip_prefix(refname, \"refs/rewritten/for-update-refs/\", &name))\n     -+\t\treturn 1;\n     ++\t\t\tif (!read_ref(\"HEAD\", &oid)) {\n     ++\t\t\t\titem->util = oiddup(&oid);\n     ++\t\t\t\tbreak;\n     ++\t\t\t}\n     ++\t\t}\n     ++\t}\n      +\n     -+\tctx->ref_names[ctx->nr] = xstrdup(name);\n     -+\toidcpy(&ctx->new[ctx->nr], oid);\n     -+\tif (!refs_resolve_ref_unsafe(ctx->refs, name, 0,\n     -+\t\t\t\t     &ctx->old[ctx->nr], &f))\n     -+\t\treturn 1;\n     ++\tif (!found) {\n     ++\t\tstruct object_id oid;\n     ++\t\titem = string_list_append(&list, refname);\n      +\n     -+\tctx->nr++;\n     ++\t\tif (!read_ref(\"HEAD\", &oid))\n     ++\t\t\titem->util = oiddup(&oid);\n     ++\t\telse\n     ++\t\t\titem->util = oiddup(the_hash_algo->null_oid);\n     ++\t}\n     ++\n     ++\twrite_update_refs_state(&list);\n     ++\tstring_list_clear(&list, 1);\n       \treturn 0;\n       }\n       \n      +static int do_update_refs(struct repository *r)\n      +{\n     -+\tint i, res;\n     -+\tstruct update_refs_context ctx = {\n     -+\t\t.refs = get_main_ref_store(r),\n     -+\t\t.alloc = 16,\n     -+\t};\n     -+\tALLOC_ARRAY(ctx.ref_names, ctx.alloc);\n     -+\tALLOC_ARRAY(ctx.old, ctx.alloc);\n     -+\tALLOC_ARRAY(ctx.new, ctx.alloc);\n     -+\n     -+\tres = refs_for_each_fullref_in(ctx.refs,\n     -+\t\t\t\t       \"refs/rewritten/for-update-refs/\",\n     -+\t\t\t\t       add_ref_to_context,\n     -+\t\t\t\t       &ctx);\n     -+\n     -+\tfor (i = 0; !res && i < ctx.nr; i++)\n     -+\t\tres = refs_update_ref(ctx.refs, \"rewritten during rebase\",\n     -+\t\t\t\tctx.ref_names[i],\n     -+\t\t\t\t&ctx.new[i], &ctx.old[i],\n     ++\tint res = 0;\n     ++\tstruct string_list_item *item;\n     ++\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n     ++\tstruct ref_store *refs = get_main_ref_store(r);\n     ++\n     ++\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n     ++\n     ++\tfor_each_string_list_item(item, &refs_to_oids) {\n     ++\t\tstruct object_id *oid_to = item->util;\n     ++\t\tstruct object_id oid_from;\n     ++\n     ++\t\tif (oideq(oid_to, the_hash_algo->null_oid)) {\n     ++\t\t\t/*\n     ++\t\t\t * Ref was not updated. User may have deleted the\n     ++\t\t\t * 'update-ref' step.\n     ++\t\t\t */\n     ++\t\t\tcontinue;\n     ++\t\t}\n     ++\n     ++\t\tif (read_ref(item->string, &oid_from)) {\n     ++\t\t\t/*\n     ++\t\t\t * The ref does not exist. The user probably\n     ++\t\t\t * inserted a new 'update-ref' step with a new\n     ++\t\t\t * branch name.\n     ++\t\t\t */\n     ++\t\t\toidcpy(&oid_from, the_hash_algo->null_oid);\n     ++\t\t}\n     ++\n     ++\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n     ++\t\t\t\titem->string,\n     ++\t\t\t\toid_to, &oid_from,\n      +\t\t\t\t0, UPDATE_REFS_MSG_ON_ERR);\n     ++\t}\n      +\n     -+\tfor (i = 0; i < ctx.nr; i++)\n     -+\t\tfree(ctx.ref_names[i]);\n     -+\tfree(ctx.ref_names);\n     -+\tfree(ctx.old);\n     -+\tfree(ctx.new);\n     ++\tstring_list_clear(&refs_to_oids, 1);\n      +\treturn res;\n      +}\n      +\n       static int is_final_fixup(struct todo_list *todo_list)\n       {\n       \tint i = todo_list->current;\n     +@@ sequencer.c: cleanup_head_ref:\n     + \t\tstrbuf_release(&head_ref);\n     + \t}\n     + \n     ++\tdo_update_refs(r);\n     ++\n     + \t/*\n     + \t * Sequence of picks finished successfully; cleanup by\n     + \t * removing the .git/sequencer directory\n     +@@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n     + \t\t}\n     + \t}\n     + \n     ++\twrite_update_refs_state(&ctx.refs_to_oids);\n     ++\n     + \tstring_list_clear(&ctx.refs_to_oids, 1);\n     + \tfree(todo_list->items);\n     + \ttodo_list->items = ctx.items;\n      \n       ## t/t3404-rebase-interactive.sh ##\n      @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands with --rebase-merges' '\n     @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands\n      +\tgit branch -f third HEAD~1 &&\n      +\ttest_commit extra2 fileX &&\n      +\tgit commit --amend --fixup=L &&\n     -+\t(\n     -+\t\tgit rebase -i --autosquash --update-refs primary &&\n     -+\n     -+\t\tcompare_two_refs HEAD~3 refs/heads/first &&\n     -+\t\tcompare_two_refs HEAD~3 refs/heads/second &&\n     -+\t\tcompare_two_refs HEAD~1 refs/heads/third &&\n     -+\t\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n     -+\t)\n     ++\n     ++\tgit rebase -i --autosquash --update-refs primary &&\n     ++\n     ++\tcompare_two_refs HEAD~3 refs/heads/first &&\n     ++\tcompare_two_refs HEAD~3 refs/heads/second &&\n     ++\tcompare_two_refs HEAD~1 refs/heads/third &&\n     ++\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n      +'\n      +\n       # This must be the last test in this file\n 7:  3d7d3f656b4 ! 8:  d2cfdbfc431 rebase: add rebase.updateRefs config option\n     @@ Documentation/git-rebase.txt: start would be overridden by the presence of\n       \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n       \tin this way.\n      ++\n     -+If the `--update-refs` option is enabled by default using the\n     -+configuration variable `rebase.updateRefs`, this option can be\n     -+used to override and disable this setting.\n     ++If the configuration variable `rebase.updateRefs` is set, then this option\n     ++can be used to override and disable this setting.\n       \n       INCOMPATIBLE OPTIONS\n       --------------------\n\n-- \ngitgitgadget\n"},{"id":"458039","messageId":"72e0481b643e98c5670eee0bf5c199c4fb693b16.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:58Z","receivedAt":"2022-06-28T13:28:22Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change introduced the 'git rebase --update-refs' option\nwhich added 'update-ref <ref>' commands to the todo list of an\ninteractive rebase.\n\nTeach Git to record the HEAD position when reaching these 'update-ref'\ncommands. The ref/OID pair is stored in the\n$GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\nfile to avoid having other processes updating the refs in that file\nwhile the rebase is in progress.\n\nNot only do we update the file when the sequencer reaches these\n'update-ref' commands, we then update the refs themselves at the end of\nthe rebase sequence. If the rebase is aborted before this final step,\nthen the refs are not updated.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c                   | 114 +++++++++++++++++++++++++++++++++-\n t/t3404-rebase-interactive.sh |  23 +++++++\n 2 files changed, 136 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 915d87a0336..4fd1c0b5bce 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -36,6 +36,7 @@\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n #include \"branch.h\"\n+#include \"log-tree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -148,6 +149,14 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n  */\n static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n \n+/*\n+ * The update-refs file stores a list of refs that will be updated at the end\n+ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n+ * update the OIDs for the refs in this file, but the refs are not updated\n+ * until the end of the rebase sequence.\n+ */\n+static GIT_PATH_FUNC(rebase_path_update_refs, \"rebase-merge/update-refs\")\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line.\n@@ -4058,11 +4067,110 @@ leave_merge:\n \treturn ret;\n }\n \n-static int do_update_ref(struct repository *r, const char *ref_name)\n+static int write_update_refs_state(struct string_list *refs_to_oids)\n+{\n+\tint result = 0;\n+\tFILE *fp = NULL;\n+\tstruct string_list_item *item;\n+\tchar *path = xstrdup(rebase_path_update_refs());\n+\n+\tif (safe_create_leading_directories(path)) {\n+\t\tresult = error(_(\"unable to create leading directories of %s\"),\n+\t\t\t       path);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfp = fopen(path, \"w\");\n+\tif (!fp) {\n+\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfor_each_string_list_item(item, refs_to_oids)\n+\t\tfprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n+\n+cleanup:\n+\tif (fp)\n+\t\tfclose(fp);\n+\treturn result;\n+}\n+\n+static int do_update_ref(struct repository *r, const char *refname)\n {\n+\tstruct string_list_item *item;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tint found = 0;\n+\n+\tsequencer_get_update_refs_state(r->gitdir, &list);\n+\n+\tfor_each_string_list_item(item, &list) {\n+\t\tif (!strcmp(item->string, refname)) {\n+\t\t\tstruct object_id oid;\n+\t\t\tfree(item->util);\n+\t\t\tfound = 1;\n+\n+\t\t\tif (!read_ref(\"HEAD\", &oid)) {\n+\t\t\t\titem->util = oiddup(&oid);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (!found) {\n+\t\tstruct object_id oid;\n+\t\titem = string_list_append(&list, refname);\n+\n+\t\tif (!read_ref(\"HEAD\", &oid))\n+\t\t\titem->util = oiddup(&oid);\n+\t\telse\n+\t\t\titem->util = oiddup(the_hash_algo->null_oid);\n+\t}\n+\n+\twrite_update_refs_state(&list);\n+\tstring_list_clear(&list, 1);\n \treturn 0;\n }\n \n+static int do_update_refs(struct repository *r)\n+{\n+\tint res = 0;\n+\tstruct string_list_item *item;\n+\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(r);\n+\n+\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n+\n+\tfor_each_string_list_item(item, &refs_to_oids) {\n+\t\tstruct object_id *oid_to = item->util;\n+\t\tstruct object_id oid_from;\n+\n+\t\tif (oideq(oid_to, the_hash_algo->null_oid)) {\n+\t\t\t/*\n+\t\t\t * Ref was not updated. User may have deleted the\n+\t\t\t * 'update-ref' step.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (read_ref(item->string, &oid_from)) {\n+\t\t\t/*\n+\t\t\t * The ref does not exist. The user probably\n+\t\t\t * inserted a new 'update-ref' step with a new\n+\t\t\t * branch name.\n+\t\t\t */\n+\t\t\toidcpy(&oid_from, the_hash_algo->null_oid);\n+\t\t}\n+\n+\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n+\t\t\t\titem->string,\n+\t\t\t\toid_to, &oid_from,\n+\t\t\t\t0, UPDATE_REFS_MSG_ON_ERR);\n+\t}\n+\n+\tstring_list_clear(&refs_to_oids, 1);\n+\treturn res;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -4580,6 +4688,8 @@ cleanup_head_ref:\n \t\tstrbuf_release(&head_ref);\n \t}\n \n+\tdo_update_refs(r);\n+\n \t/*\n \t * Sequence of picks finished successfully; cleanup by\n \t * removing the .git/sequencer directory\n@@ -5709,6 +5819,8 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n \t\t}\n \t}\n \n+\twrite_update_refs_state(&ctx.refs_to_oids);\n+\n \tstring_list_clear(&ctx.refs_to_oids, 1);\n \tfree(todo_list->items);\n \ttodo_list->items = ctx.items;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3cd20733bc8..63a69bc073e 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1813,6 +1813,29 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t)\n '\n \n+compare_two_refs () {\n+\tgit rev-parse $1 >expect &&\n+\tgit rev-parse $2 >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success '--update-refs updates refs correctly' '\n+\tgit checkout -B update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\ttest_commit extra2 fileX &&\n+\tgit commit --amend --fixup=L &&\n+\n+\tgit rebase -i --autosquash --update-refs primary &&\n+\n+\tcompare_two_refs HEAD~3 refs/heads/first &&\n+\tcompare_two_refs HEAD~3 refs/heads/second &&\n+\tcompare_two_refs HEAD~1 refs/heads/third &&\n+\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"458040","messageId":"e95ad41d3551244ea98c43145a089ecfd2536e54.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 5/8] sequencer: add update-ref command","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:56Z","receivedAt":"2022-06-28T13:28:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nAdd the boilerplate for an \"update-ref\" command in the sequencer. This\nconnects to the current no-op do_update_ref() which will be filled in\nafter more connections are created.\n\nThe syntax in the todo list will be \"update-ref <ref-name>\" to signal\nthat we should store the current commit as the value for updating\n<ref-name> at the end of the rebase.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c |  3 +++\n sequencer.c          | 14 +++++++++++++-\n sequencer.h          |  1 +\n 3 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 22394224faa..1ff07647af3 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -57,6 +57,9 @@ void append_todo_help(int command_count,\n \"        create a merge commit using the original merge commit's\\n\"\n \"        message (or the oneline, if no original merge commit was\\n\"\n \"        specified); use -c <commit> to reword the commit message\\n\"\n+\"u, update-ref <ref> = track a placeholder for the <ref> to be updated\\n\"\n+\"                      to this position in the new commits. The <ref> is\\n\"\n+\"                      updated at the end of the rebase\\n\"\n \"\\n\"\n \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n \tunsigned edit_todo = !(shortrevisions && shortonto);\ndiff --git a/sequencer.c b/sequencer.c\nindex 2ffee79619c..0b61835d441 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1696,6 +1696,7 @@ static struct {\n \t[TODO_LABEL] = { 'l', \"label\" },\n \t[TODO_RESET] = { 't', \"reset\" },\n \t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_UPDATE_REF] = { 'u', \"update-ref\" },\n \t[TODO_NOOP] = { 0,   \"noop\" },\n \t[TODO_DROP] = { 'd', \"drop\" },\n \t[TODO_COMMENT] = { 0,   NULL },\n@@ -2457,7 +2458,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,\n \t\t\t     command_to_string(item->command));\n \n \tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n-\t    item->command == TODO_RESET) {\n+\t    item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {\n \t\titem->commit = NULL;\n \t\titem->arg_offset = bol - buf;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -4056,6 +4057,11 @@ leave_merge:\n \treturn ret;\n }\n \n+static int do_update_ref(struct repository *r, const char *ref_name)\n+{\n+\treturn 0;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -4431,6 +4437,12 @@ static int pick_commits(struct repository *r,\n \t\t\t\treturn error_with_patch(r, item->commit,\n \t\t\t\t\t\t\targ, item->arg_len,\n \t\t\t\t\t\t\topts, res, 0);\n+\t\t} else if (item->command == TODO_UPDATE_REF) {\n+\t\t\tstruct strbuf ref = STRBUF_INIT;\n+\t\t\tstrbuf_add(&ref, arg, item->arg_len);\n+\t\t\tif ((res = do_update_ref(r, ref.buf)))\n+\t\t\t\treschedule = 1;\n+\t\t\tstrbuf_release(&ref);\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 3ae541bb145..2cf5c1b9a38 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -95,6 +95,7 @@ enum todo_command {\n \tTODO_LABEL,\n \tTODO_RESET,\n \tTODO_MERGE,\n+\tTODO_UPDATE_REF,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n-- \ngitgitgadget\n\n"},{"id":"458041","messageId":"6528a50343f0fc3c0b2134f1af29cc30fab4f81f.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 4/8] sequencer: define array with enum values","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:55Z","receivedAt":"2022-06-28T13:28:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe todo_command_info array defines which strings match with which\ntodo_command enum values. The array is defined in the same order as the\nenum values, but if one changed without the other, then we would have\nunexpected results.\n\nMake it easier to see changes to the enum and this array by using the\nenum values as the indices of the array.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1094e146b99..2ffee79619c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1685,20 +1685,20 @@ static struct {\n \tchar c;\n \tconst char *str;\n } todo_command_info[] = {\n-\t{ 'p', \"pick\" },\n-\t{ 0,   \"revert\" },\n-\t{ 'e', \"edit\" },\n-\t{ 'r', \"reword\" },\n-\t{ 'f', \"fixup\" },\n-\t{ 's', \"squash\" },\n-\t{ 'x', \"exec\" },\n-\t{ 'b', \"break\" },\n-\t{ 'l', \"label\" },\n-\t{ 't', \"reset\" },\n-\t{ 'm', \"merge\" },\n-\t{ 0,   \"noop\" },\n-\t{ 'd', \"drop\" },\n-\t{ 0,   NULL }\n+\t[TODO_PICK] = { 'p', \"pick\" },\n+\t[TODO_REVERT] = { 0,   \"revert\" },\n+\t[TODO_EDIT] = { 'e', \"edit\" },\n+\t[TODO_REWORD] = { 'r', \"reword\" },\n+\t[TODO_FIXUP] = { 'f', \"fixup\" },\n+\t[TODO_SQUASH] = { 's', \"squash\" },\n+\t[TODO_EXEC] = { 'x', \"exec\" },\n+\t[TODO_BREAK] = { 'b', \"break\" },\n+\t[TODO_LABEL] = { 'l', \"label\" },\n+\t[TODO_RESET] = { 't', \"reset\" },\n+\t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_NOOP] = { 0,   \"noop\" },\n+\t[TODO_DROP] = { 'd', \"drop\" },\n+\t[TODO_COMMENT] = { 0,   NULL },\n };\n \n static const char *command_to_string(const enum todo_command command)\n-- \ngitgitgadget\n\n"},{"id":"458043","messageId":"2bc647b6fcd6032a1e97e67739ced31117bfbfce.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:53Z","receivedAt":"2022-06-28T13:28:28Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe branch_checked_out() helper helps commands like 'git branch' and\n'git fetch' from overwriting refs that are currently checked out in\nother worktrees.\n\nA future update to 'git rebase' will introduce a new '--update-refs'\noption which will update the local refs that point to commits that are\nbeing rebased. To avoid collisions as the rebase completes, we want to\nmake the future data store for these refs to be considered by\nbranch_checked_out().\n\nThe data store is a plaintext file inside the 'rebase-merge' directory\nfor that worktree. The file alternates refnames and OIDs. The OIDs will\nbe used to store the to-be-written values as the rebase progresses, but\ncan be ignored at the moment.\n\nCreate a new sequencer_get_update_refs_state() method that parses this\nfile and populates a struct string_list with the ref-OID pairs. We can\nthen use this list to add to the current_checked_out_branches strmap\nused by branch_checked_out().\n\nTo properly navigate to the rebase directory for a given worktree,\nextract the static strbuf_worktree_gitdir() method to a public API\nmethod.\n\nWe can test that this works without having Git write this file by\nartificially creating one in our test script.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n branch.c                  | 13 ++++++++++++\n sequencer.c               | 42 +++++++++++++++++++++++++++++++++++++++\n sequencer.h               |  9 +++++++++\n t/t2407-worktree-heads.sh | 22 ++++++++++++++++----\n 4 files changed, 82 insertions(+), 4 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 526e8237673..f252c4eef6c 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -365,6 +365,7 @@ static void prepare_checked_out_branches(void)\n \t\tchar *old;\n \t\tstruct wt_status_state state = { 0 };\n \t\tstruct worktree *wt = worktrees[i++];\n+\t\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n \n \t\tif (wt->is_bare)\n \t\t\tcontinue;\n@@ -400,6 +401,18 @@ static void prepare_checked_out_branches(void)\n \t\t\tstrbuf_release(&ref);\n \t\t}\n \t\twt_status_state_free_buffers(&state);\n+\n+\t\tif (!sequencer_get_update_refs_state(get_worktree_git_dir(wt),\n+\t\t\t\t\t\t     &update_refs)) {\n+\t\t\tstruct string_list_item *item;\n+\t\t\tfor_each_string_list_item(item, &update_refs) {\n+\t\t\t\told = strmap_put(&current_checked_out_branches,\n+\t\t\t\t\t\t item->string,\n+\t\t\t\t\t\t xstrdup(wt->path));\n+\t\t\t\tfree(old);\n+\t\t\t}\n+\t\t\tstring_list_clear(&update_refs, 1);\n+\t\t}\n \t}\n \n \tfree_worktrees(worktrees);\ndiff --git a/sequencer.c b/sequencer.c\nindex 8c3ed3532ac..1094e146b99 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5901,3 +5901,45 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)\n \n \treturn 0;\n }\n+\n+int sequencer_get_update_refs_state(const char *wt_dir,\n+\t\t\t\t    struct string_list *refs)\n+{\n+\tint result = 0;\n+\tstruct stat st;\n+\tFILE *fp = NULL;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf hash = STRBUF_INIT;\n+\tchar *path = xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n+\n+\tif (stat(path, &st))\n+\t\tgoto cleanup;\n+\n+\tfp = fopen(path, \"r\");\n+\tif (!fp)\n+\t\tgoto cleanup;\n+\n+\twhile (strbuf_getline(&ref, fp) != EOF) {\n+\t\tstruct object_id oid;\n+\t\tstruct string_list_item *item;\n+\n+\t\tif (strbuf_getline(&hash, fp) == EOF ||\n+\t\t    get_oid_hex(hash.buf, &oid)) {\n+\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n+\t\t\t\t  path);\n+\t\t\tresult = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\titem = string_list_append(refs, ref.buf);\n+\t\titem->util = oiddup(&oid);\n+\t}\n+\n+cleanup:\n+\tif (fp)\n+\t\tfclose(fp);\n+\tfree(path);\n+\tstrbuf_release(&ref);\n+\tstrbuf_release(&hash);\n+\treturn result;\n+}\ndiff --git a/sequencer.h b/sequencer.h\nindex da64473636b..3ae541bb145 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -232,4 +232,13 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose);\n int sequencer_get_last_command(struct repository* r,\n \t\t\t       enum replay_action *action);\n int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);\n+\n+/**\n+ * Append the set of ref-OID pairs that are currently stored for the 'git\n+ * rebase --update-refs' feature if such a rebase is currently happening.\n+ *\n+ * Localized to a worktree's git dir.\n+ */\n+int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs);\n+\n #endif /* SEQUENCER_H */\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex 4f59bc21303..f1e9e172a0c 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -7,8 +7,11 @@ TEST_PASSES_SANITIZE_LEAK=true\n \n test_expect_success 'setup' '\n \ttest_commit init &&\n-\tgit branch -f fake-1 &&\n-\tgit branch -f fake-2 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit branch -f fake-$i || return 1\n+\tdone &&\n \n \tfor i in 1 2 3 4\n \tdo\n@@ -73,8 +76,19 @@ test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n \techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&\n \techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&\n \n-\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n-\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n+\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n+\trefs/heads/fake-3\n+\t$(git rev-parse HEAD~1)\n+\trefs/heads/fake-4\n+\t$(git rev-parse HEAD)\n+\tEOF\n+\n+\tfor i in 1 3 4\n+\tdo\n+\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n+\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n+\t\t\treturn 1\n+\tdone\n '\n \n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n-- \ngitgitgadget\n\n"},{"id":"458042","messageId":"669f4abd59e7dbb13281e85144d085180934fd15.1656422759.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v3 3/8] rebase-interactive: update 'merge' description","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-28T13:25:54Z","receivedAt":"2022-06-28T13:28:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe 'merge' command description for the todo list documentation in an\ninteractive rebase has multiple lines. The lines other than the first\none start with dots ('.') while the similar multi-line documentation for\n'fixup' does not.\n\nThe 'merge' command was documented when interactive rebase was first\nported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n2018-08-10). These dots might have been carried over from the previous\nshell implementation.\n\nThe 'fixup' command was documented more recently in 9e3cebd97cb (rebase\n-i: add fixup [-C | -c] command, 2021-01-29).\n\nLooking at the output in an editor, my personal opinion is that the dots\nare unnecessary and noisy. Remove them now before adding more commands\nwith multi-line documentation.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 87649d0c016..22394224faa 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -54,9 +54,9 @@ void append_todo_help(int command_count,\n \"l, label <label> = label current HEAD with a name\\n\"\n \"t, reset <label> = reset HEAD to a label\\n\"\n \"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\\n\"\n-\".       create a merge commit using the original merge commit's\\n\"\n-\".       message (or the oneline, if no original merge commit was\\n\"\n-\".       specified); use -c <commit> to reword the commit message\\n\"\n+\"        create a merge commit using the original merge commit's\\n\"\n+\"        message (or the oneline, if no original merge commit was\\n\"\n+\"        specified); use -c <commit> to reword the commit message\\n\"\n \"\\n\"\n \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n \tunsigned edit_todo = !(shortrevisions && shortonto);\n-- \ngitgitgadget\n\n"},{"id":"458067","messageId":"xmqqtu84iklu.fsf@gitster.g","threadId":"57949","inReplyTo":"fbaedc7f1f02efec251cb4df665fd3cb71f1d3b3.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/8] t2407: test branches currently using apply backend","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-28T20:44:29Z","receivedAt":"2022-06-28T20:44:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The tests in t2407 that verify the branch_checked_out() helper in the\n> case of bisects and rebases were added by 9347303db89 (branch: check for\n> bisects and rebases, 2022-06-08). However, that commit failed to check\n> for rebases that are using the 'apply' backend.\n>\n> Add a test that checks the apply backend. The implementation was already\n> correct here, but it is good to have regression tests before modifying\n> the implementation further.\n>\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>  t/t2407-worktree-heads.sh | 13 ++++++++++++-\n>  1 file changed, 12 insertions(+), 1 deletion(-)\n>\n> diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\n> index b6be42f74a2..4f59bc21303 100755\n> --- a/t/t2407-worktree-heads.sh\n> +++ b/t/t2407-worktree-heads.sh\n> @@ -54,7 +54,18 @@ test_expect_success 'refuse to overwrite: worktree in bisect' '\n>  \tgrep \"cannot force update the branch '\\''fake-2'\\'' checked out at.*wt-4\" err\n>  '\n>  \n> -test_expect_success 'refuse to overwrite: worktree in rebase' '\n> +test_expect_success 'refuse to overwrite: worktree in rebase (apply)' '\n> +\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-apply &&\n> +\n> +\tmkdir -p .git/worktrees/wt-3/rebase-apply &&\n> +\techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-apply/head-name &&\n> +\techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-apply/onto &&\n> +\n> +\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n> +\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n> +'\n> +\n> +test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n>  \ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n>  \n>  \tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n\nThis is not the first offender, since the other existing one is\ndoing the same, but it is a bit sad that this makes it worse to\nexpose and depend on the details of the way the rebase happens to be\ncurrently implemented.\n\nPerhaps a more kosher way to do this is to find an commit that\nsurely would not allow fake-1 branch to be cleanly rebased onto and\nactually start (and cause it to stop) a rebase.\n\nI notice that the original offence was committed fairly recently, by\nd2ba271a (branch: check for bisects and rebases, 2022-06-14) that we\ncan easily eject out of the 'next' branch when we rewind and rebuild\nit, if we wanted to.\n\nAnyway, let's read on.\n\nThanks.\n"},{"id":"458068","messageId":"xmqqpmisikex.fsf@gitster.g","threadId":"57949","inReplyTo":"2bc647b6fcd6032a1e97e67739ced31117bfbfce.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-28T20:48:38Z","receivedAt":"2022-06-28T20:48:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The branch_checked_out() helper helps commands like 'git branch' and\n> 'git fetch' from overwriting refs that are currently checked out in\n> other worktrees.\n>\n> A future update to 'git rebase' will introduce a new '--update-refs'\n> option which will update the local refs that point to commits that are\n> being rebased. To avoid collisions as the rebase completes, we want to\n> make the future data store for these refs to be considered by\n> branch_checked_out().\n>\n> The data store is a plaintext file inside the 'rebase-merge' directory\n> for that worktree. The file alternates refnames and OIDs. The OIDs will\n> be used to store the to-be-written values as the rebase progresses, but\n> can be ignored at the moment.\n>\n> Create a new sequencer_get_update_refs_state() method that parses this\n> file and populates a struct string_list with the ref-OID pairs. We can\n> then use this list to add to the current_checked_out_branches strmap\n> used by branch_checked_out().\n>\n> To properly navigate to the rebase directory for a given worktree,\n> extract the static strbuf_worktree_gitdir() method to a public API\n> method.\n>\n> We can test that this works without having Git write this file by\n> artificially creating one in our test script.\n\nHmph, I am not thrilled to see an ad-hoc text file for things like\nthis.  Do the objects that appear in this file otherwise already\nanchored by existing refs, or can some of them be \"floating\" without\nany refs pointing at them, i.e. making the \"connected\" and \"fsck\"\nmachinery also being aware of them to protect them from collected as\ngarbage and reported as dangling/unreachable?\n\n"},{"id":"458069","messageId":"xmqqletgijul.fsf@gitster.g","threadId":"57949","inReplyTo":"669f4abd59e7dbb13281e85144d085180934fd15.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/8] rebase-interactive: update 'merge' description","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-28T21:00:50Z","receivedAt":"2022-06-28T21:00:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The 'merge' command description for the todo list documentation in an\n> interactive rebase has multiple lines. The lines other than the first\n> one start with dots ('.') while the similar multi-line documentation for\n> 'fixup' does not.\n>\n> The 'merge' command was documented when interactive rebase was first\n> ported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n> 2018-08-10). These dots might have been carried over from the previous\n> shell implementation.\n\nThe text indeed does come literally from the block removed by that\ncommit.  I wondered if the shell \"gettext\" in git-i18n.sh had some\nmagic in it, but it does not seem to have anything, and po/git.pot\naround that time has these lines with leading dots, so I suspect\nthat they were quite deliberately added, not for the reasons of\nformatting machinery (e.g. preventing somebody in the dataflow from\nlosing leading indentation), but to show them to the end users.\n\nUnfortunately, the offending commit 4c68e7dd (sequencer: introduce\nthe `merge` command, 2018-04-25) does not justify them X-<.\n\n> Looking at the output in an editor, my personal opinion is that the dots\n> are unnecessary and noisy. Remove them now before adding more commands\n> with multi-line documentation.\n\nI personally do not mind having them in the UI, but I can also be\nhappy to see them go.  It is unlikely that any program is consuming\nthese strings, so I would say this is a fairly safe clean-up.\n\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>  rebase-interactive.c | 6 +++---\n>  1 file changed, 3 insertions(+), 3 deletions(-)\n>\n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index 87649d0c016..22394224faa 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -54,9 +54,9 @@ void append_todo_help(int command_count,\n>  \"l, label <label> = label current HEAD with a name\\n\"\n>  \"t, reset <label> = reset HEAD to a label\\n\"\n>  \"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\\n\"\n> -\".       create a merge commit using the original merge commit's\\n\"\n> -\".       message (or the oneline, if no original merge commit was\\n\"\n> -\".       specified); use -c <commit> to reword the commit message\\n\"\n> +\"        create a merge commit using the original merge commit's\\n\"\n> +\"        message (or the oneline, if no original merge commit was\\n\"\n> +\"        specified); use -c <commit> to reword the commit message\\n\"\n>  \"\\n\"\n>  \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n>  \tunsigned edit_todo = !(shortrevisions && shortonto);\n"},{"id":"458070","messageId":"xmqqh744ijsk.fsf@gitster.g","threadId":"57949","inReplyTo":"6528a50343f0fc3c0b2134f1af29cc30fab4f81f.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 4/8] sequencer: define array with enum values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-28T21:02:03Z","receivedAt":"2022-06-28T21:02:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The todo_command_info array defines which strings match with which\n> todo_command enum values. The array is defined in the same order as the\n> enum values, but if one changed without the other, then we would have\n> unexpected results.\n>\n> Make it easier to see changes to the enum and this array by using the\n> enum values as the indices of the array.\n\nOK.  It is a bit of shame that we cannot define this array and enum\nat the same time in a single construct, but this is much safer than\nthe status quo.  Thanks.\n\n>\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>  sequencer.c | 28 ++++++++++++++--------------\n>  1 file changed, 14 insertions(+), 14 deletions(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index 1094e146b99..2ffee79619c 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -1685,20 +1685,20 @@ static struct {\n>  \tchar c;\n>  \tconst char *str;\n>  } todo_command_info[] = {\n> -\t{ 'p', \"pick\" },\n> -\t{ 0,   \"revert\" },\n> -\t{ 'e', \"edit\" },\n> -\t{ 'r', \"reword\" },\n> -\t{ 'f', \"fixup\" },\n> -\t{ 's', \"squash\" },\n> -\t{ 'x', \"exec\" },\n> -\t{ 'b', \"break\" },\n> -\t{ 'l', \"label\" },\n> -\t{ 't', \"reset\" },\n> -\t{ 'm', \"merge\" },\n> -\t{ 0,   \"noop\" },\n> -\t{ 'd', \"drop\" },\n> -\t{ 0,   NULL }\n> +\t[TODO_PICK] = { 'p', \"pick\" },\n> +\t[TODO_REVERT] = { 0,   \"revert\" },\n> +\t[TODO_EDIT] = { 'e', \"edit\" },\n> +\t[TODO_REWORD] = { 'r', \"reword\" },\n> +\t[TODO_FIXUP] = { 'f', \"fixup\" },\n> +\t[TODO_SQUASH] = { 's', \"squash\" },\n> +\t[TODO_EXEC] = { 'x', \"exec\" },\n> +\t[TODO_BREAK] = { 'b', \"break\" },\n> +\t[TODO_LABEL] = { 'l', \"label\" },\n> +\t[TODO_RESET] = { 't', \"reset\" },\n> +\t[TODO_MERGE] = { 'm', \"merge\" },\n> +\t[TODO_NOOP] = { 0,   \"noop\" },\n> +\t[TODO_DROP] = { 'd', \"drop\" },\n> +\t[TODO_COMMENT] = { 0,   NULL },\n>  };\n>  \n>  static const char *command_to_string(const enum todo_command command)\n"},{"id":"458072","messageId":"xmqqbkucijg3.fsf@gitster.g","threadId":"57949","inReplyTo":"918b398d6a2264a99acadd319f780c796bbafc18.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-28T21:09:32Z","receivedAt":"2022-06-28T21:09:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> When working on a large feature, it can be helpful to break that feature\n> into multiple smaller parts that become reviewed in sequence. During\n> development or during review, a change to one part of the feature could\n> affect multiple of these parts. An interactive rebase can help adjust\n> the multi-part \"story\" of the branch.\n>\n> However, if there are branches tracking the different parts of the\n> feature, then rebasing the entire list of commits can create commits not\n> reachable from those \"sub branches\". It can take a manual step to update\n> those branches.\n>\n> Add a new --update-refs option to 'git rebase -i' that adds 'update-ref\n> <ref>' steps to the todo file whenever a commit that is being rebased is\n> decorated with that <ref>. At the very end, the rebase process updates\n> all of the listed refs to the values stored during the rebase operation.\n>\n> Be sure to iterate after any squashing or fixups are placed. Update the\n> branch only after those squashes and fixups are complete. This allows a\n> --fixup commit at the tip of the feature to apply correctly to the sub\n> branch, even if it is fixing up the most-recent commit in that part.\n>\n> One potential problem here is that refs decorating commits that are\n> already marked as \"fixup!\" or \"squash!\" will not be included in this\n> list. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\n> to change the relative order of these refs, so it is not recommended.\n> The workflow here is intended to allow these kinds of commits at the tip\n> of the rebased branch while the other sub branches come along for the\n> ride without intervention.\n>\n> This change update the documentation and builtin to accept the\n> --update-refs option as well as updating the todo file with the\n> 'update-ref' commands. Tests are added to ensure that these todo\n> commands are added in the correct locations.\n\nOK, so command line option is `--update-refs` because the range\nbeing rebased could contain more than one branch tips, and the\noption causes one `update-ref` insn per such ref left in the todo\nlist?  I had to read the above twice to remind me what was going on.\n\nThe design looks OK here but has \"rebase-merge/update-refs\" already\ncame into the picture, or will it be in future steps?  I lost track.\n\nLet's read on.\n\nThanks.\n"},{"id":"458073","messageId":"xmqq7d50ij6f.fsf@gitster.g","threadId":"57949","inReplyTo":"72e0481b643e98c5670eee0bf5c199c4fb693b16.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-28T21:15:20Z","receivedAt":"2022-06-28T21:15:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The previous change introduced the 'git rebase --update-refs' option\n> which added 'update-ref <ref>' commands to the todo list of an\n> interactive rebase.\n>\n> Teach Git to record the HEAD position when reaching these 'update-ref'\n> commands. The ref/OID pair is stored in the\n> $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n> file to avoid having other processes updating the refs in that file\n> while the rebase is in progress.\n>\n> Not only do we update the file when the sequencer reaches these\n> 'update-ref' commands, we then update the refs themselves at the end of\n> the rebase sequence. If the rebase is aborted before this final step,\n> then the refs are not updated.\n\nSo, in general, we would\n\n * first scan the range of commits being rebased\n * compute what should happen and write the \"todo\" thing\n * write also \"update-refs\" to lick the branches to repel others\n * execute the \"todo\" insns one by one, possibly giving control back\n\nAnd this order is important---update-refs file is written fairly\nearly, and the branches potentially involved in the rebase are all\nprotected during the time-consuming (due to possibility of manual\nconflict resolution) execution step.\n\nMakes sense.\n\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 3cd20733bc8..63a69bc073e 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1813,6 +1813,29 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n>  \t)\n>  '\n>  \n> +compare_two_refs () {\n> +\tgit rev-parse $1 >expect &&\n> +\tgit rev-parse $2 >actual &&\n> +\ttest_cmp expect actual\n> +}\n> +\n> +test_expect_success '--update-refs updates refs correctly' '\n> +\tgit checkout -B update-refs no-conflict-branch &&\n> +\tgit branch -f base HEAD~4 &&\n> +\tgit branch -f first HEAD~3 &&\n> +\tgit branch -f second HEAD~3 &&\n> +\tgit branch -f third HEAD~1 &&\n> +\ttest_commit extra2 fileX &&\n> +\tgit commit --amend --fixup=L &&\n> +\n> +\tgit rebase -i --autosquash --update-refs primary &&\n> +\n> +\tcompare_two_refs HEAD~3 refs/heads/first &&\n> +\tcompare_two_refs HEAD~3 refs/heads/second &&\n> +\tcompare_two_refs HEAD~1 refs/heads/third &&\n> +\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n> +'\n> +\n>  # This must be the last test in this file\n>  test_expect_success '$EDITOR and friends are unchanged' '\n>  \ttest_editor_unchanged\n\nIt would be nice to also have a test that makes sure that other\npeople will be prevented from checking out a branch whose tips may\nbe updated at the end.\n\nThanks.\n"},{"id":"458074","messageId":"xmqq35foiizv.fsf@gitster.g","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/8] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-28T21:19:16Z","receivedAt":"2022-06-28T21:19:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series adds a new --update-refs option to 'git rebase' (along with a\n> rebase.updateRefs config option) that adds 'update-ref' commands into the\n> TODO list. This is powered by the commit decoration machinery.\n\nI read through the patches and they made sense to me.  Hopefully I\ncan push out the integration result including this topic by the end\nof the day (today is actually my usual \"every other Tuesday gitster\ngoes offline\" day, but I'll go offline tomorrow instead, so I want\nto injest as many reasonably-done topics as possible today).\n\nThanks.\n"},{"id":"458091","messageId":"4016eee6-0759-dece-cd7b-0ed8454faf63@github.com","threadId":"57949","inReplyTo":"xmqqtu84iklu.fsf@gitster.g","subject":"Re: [PATCH v3 1/8] t2407: test branches currently using apply backend","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-29T12:54:56Z","receivedAt":"2022-06-29T12:55:02Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/28/22 4:44 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>> -test_expect_success 'refuse to overwrite: worktree in rebase' '\n>> +test_expect_success 'refuse to overwrite: worktree in rebase (apply)' '\n>> +\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-apply &&\n>> +\n>> +\tmkdir -p .git/worktrees/wt-3/rebase-apply &&\n>> +\techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-apply/head-name &&\n>> +\techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-apply/onto &&\n>> +\n>> +\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n>> +\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n>> +'\n>> +\n>> +test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n>>  \ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n>>  \n>>  \tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n> \n> This is not the first offender, since the other existing one is\n> doing the same, but it is a bit sad that this makes it worse to\n> expose and depend on the details of the way the rebase happens to be\n> currently implemented.\n\nThis is true that it is based on how  it's currently implemented. I\nwas confused that none of these rebase storage mechanisms are\ndocumented anywhere (so I couldn't extend that documentation with the\nnew update-refs file added in this series). But perhaps that's by\ndesign: this is intentionally an internal implementation detail.\n\nI do wonder where that leaves third-party implementations like libgit2\nto handle any changes we make in this space, or how much we care about\nsupporting someone rebasing in a worktree with an older Git client\nwhile also checking out a branch in another worktree using a newer one.\n\n> Perhaps a more kosher way to do this is to find an commit that\n> surely would not allow fake-1 branch to be cleanly rebased onto and\n> actually start (and cause it to stop) a rebase.\n\nThis, and the equivalent \"pause in the middle of a bisect\" would be\nways to hide these internal details.\n\nWhile I was thinking that this provides a low-weight mechanism for\ntesting the implementation of branch_checked_out(), it is only testing\nthe implementation but not the _intention_. If 'git rebase' changed\nits backend, then these tests would not start failing as we need them\nto.\n\n> I notice that the original offence was committed fairly recently, by\n> d2ba271a (branch: check for bisects and rebases, 2022-06-14) that we\n> can easily eject out of the 'next' branch when we rewind and rebuild\n> it, if we wanted to.\n\nI can either re-roll that series or create a new forward-fix that\nincludes the functionality of this test. Both are the same amount of\nwork for me, so let me know which you prefer.\n\nThanks,\n-Stolee\n"},{"id":"458092","messageId":"9d188e11-7bb4-cae7-b38a-f9f38c8c2fe1@github.com","threadId":"57949","inReplyTo":"xmqqpmisikex.fsf@gitster.g","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-29T12:58:48Z","receivedAt":"2022-06-29T12:58:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/28/22 4:48 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Derrick Stolee <derrickstolee@github.com>\n>>\n>> The branch_checked_out() helper helps commands like 'git branch' and\n>> 'git fetch' from overwriting refs that are currently checked out in\n>> other worktrees.\n>>\n>> A future update to 'git rebase' will introduce a new '--update-refs'\n>> option which will update the local refs that point to commits that are\n>> being rebased. To avoid collisions as the rebase completes, we want to\n>> make the future data store for these refs to be considered by\n>> branch_checked_out().\n>>\n>> The data store is a plaintext file inside the 'rebase-merge' directory\n>> for that worktree. The file alternates refnames and OIDs. The OIDs will\n>> be used to store the to-be-written values as the rebase progresses, but\n>> can be ignored at the moment.\n>>\n>> Create a new sequencer_get_update_refs_state() method that parses this\n>> file and populates a struct string_list with the ref-OID pairs. We can\n>> then use this list to add to the current_checked_out_branches strmap\n>> used by branch_checked_out().\n>>\n>> To properly navigate to the rebase directory for a given worktree,\n>> extract the static strbuf_worktree_gitdir() method to a public API\n>> method.\n>>\n>> We can test that this works without having Git write this file by\n>> artificially creating one in our test script.\n> \n> Hmph, I am not thrilled to see an ad-hoc text file for things like\n> this.  Do the objects that appear in this file otherwise already\n> anchored by existing refs, or can some of them be \"floating\" without\n> any refs pointing at them, i.e. making the \"connected\" and \"fsck\"\n> machinery also being aware of them to protect them from collected as\n> garbage and reported as dangling/unreachable?\n\nYou're right that we could have this sequence of events in a todo\nfile:\n\n  pick deadbeef Here is a commit that will become unreachable\n  update-ref will-be-lost\n\n  squash 1234567 make will-be-lost unreachable\n  ...\n\nSo if a GC runs after this 'update-ref' step but before the rebase\ncompletes, then that commit can be lost. The ref-update at the end\nof the rebase process would fail.\n\nI wonder how important such a situation is, though. But I'm willing\nto add the extra lookups in 'git gc' and 'git fsck' to make this a\nnon-issue.\n\nI'll take a look to see where the other refs in rebase-<backend>/*\nare handled by these processes.\n\nThanks,\n-Stolee\n"},{"id":"458093","messageId":"eb4345ce-8e1c-2f5f-7dfb-41979a89491a@github.com","threadId":"57949","inReplyTo":"xmqqletgijul.fsf@gitster.g","subject":"Re: [PATCH v3 3/8] rebase-interactive: update 'merge' description","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-29T13:02:25Z","receivedAt":"2022-06-29T13:02:39Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/28/22 5:00 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Derrick Stolee <derrickstolee@github.com>\n>>\n>> The 'merge' command description for the todo list documentation in an\n>> interactive rebase has multiple lines. The lines other than the first\n>> one start with dots ('.') while the similar multi-line documentation for\n>> 'fixup' does not.\n>>\n>> The 'merge' command was documented when interactive rebase was first\n>> ported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n>> 2018-08-10). These dots might have been carried over from the previous\n>> shell implementation.\n> \n> The text indeed does come literally from the block removed by that\n> commit.  I wondered if the shell \"gettext\" in git-i18n.sh had some\n> magic in it, but it does not seem to have anything, and po/git.pot\n> around that time has these lines with leading dots, so I suspect\n> that they were quite deliberately added, not for the reasons of\n> formatting machinery (e.g. preventing somebody in the dataflow from\n> losing leading indentation), but to show them to the end users.\n> \n> Unfortunately, the offending commit 4c68e7dd (sequencer: introduce\n> the `merge` command, 2018-04-25) does not justify them X-<.\n> \n>> Looking at the output in an editor, my personal opinion is that the dots\n>> are unnecessary and noisy. Remove them now before adding more commands\n>> with multi-line documentation.\n> \n> I personally do not mind having them in the UI, but I can also be\n> happy to see them go.  It is unlikely that any program is consuming\n> these strings, so I would say this is a fairly safe clean-up.\n\nPerhaps I should be a bit clearer that these are appearing in the\ncomment section of the todo-file when presented to the user for editing:\n\n# Commands:\n# p, pick <commit> = use commit\n# r, reword <commit> = use commit, but edit the commit message\n# e, edit <commit> = use commit, but stop for amending\n# s, squash <commit> = use commit, but meld into previous commit\n# f, fixup [-C | -c] <commit> = like \"squash\" but keep only the previous\n#                    commit's log message, unless -C is used, in which case\n#                    keep only this commit's message; -c is same as -C but\n#                    opens the editor\n# x, exec <command> = run command (the rest of the line) using shell\n# b, break = stop here (continue rebase later with 'git rebase --continue')\n# d, drop <commit> = remove commit\n# l, label <label> = label current HEAD with a name\n# t, reset <label> = reset HEAD to a label\n# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n# .       create a merge commit using the original merge commit's\n# .       message (or the oneline, if no original merge commit was\n# .       specified); use -c <commit> to reword the commit message\n#\n# These lines can be re-ordered; they are executed from top to bottom.\n#\n\nThis does not appear to be used anywhere else.\n\nThanks,\n-Stolee\n"},{"id":"458094","messageId":"8e55693e-dedf-fb42-d31a-33ebbdd6abd6@github.com","threadId":"57949","inReplyTo":"xmqqbkucijg3.fsf@gitster.g","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-29T13:03:46Z","receivedAt":"2022-06-29T13:03:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/28/22 5:09 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n...\n>> This change update the documentation and builtin to accept the\n>> --update-refs option as well as updating the todo file with the\n>> 'update-ref' commands. Tests are added to ensure that these todo\n>> commands are added in the correct locations.\n> \n> OK, so command line option is `--update-refs` because the range\n> being rebased could contain more than one branch tips, and the\n> option causes one `update-ref` insn per such ref left in the todo\n> list?  I had to read the above twice to remind me what was going on.\n> \n> The design looks OK here but has \"rebase-merge/update-refs\" already\n> came into the picture, or will it be in future steps?  I lost track.\n\nSorry, we have the ability to read the file (and tests that read an\nartificial version of the file for the branch_checked_out() logic) but\nhave not written it from within Git yet.\n\nI can make it clearer what is _not_ in this patch and coming in a\nfuture change.\n\nThanks,\n-Stolee\n"},{"id":"458095","messageId":"f2af844e-fc6c-94fd-7cce-0451f57c6576@github.com","threadId":"57949","inReplyTo":"xmqq7d50ij6f.fsf@gitster.g","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-29T13:05:44Z","receivedAt":"2022-06-29T13:05:58Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/28/22 5:15 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n...\n> It would be nice to also have a test that makes sure that other\n> people will be prevented from checking out a branch whose tips may\n> be updated at the end.\n\nPatch 3 adds the tests that 'git branch -f' cannot update these refs,\nbut I could add 'git checkout' tests, too. They both run the same\nbranch_checked_out() helper, but they are doing different things once\nthey have \"access\" to the branch.\n\nThanks,\n-Stolee\n"},{"id":"458096","messageId":"ecca0613-069a-a081-0326-6fa6c6b35569@github.com","threadId":"57949","inReplyTo":"72e0481b643e98c5670eee0bf5c199c4fb693b16.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-29T13:06:58Z","receivedAt":"2022-06-29T13:07:11Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/28/22 9:25 AM, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n\n> +static int write_update_refs_state(struct string_list *refs_to_oids)\n> +{\n> +\tint result = 0;\n> +\tFILE *fp = NULL;\n> +\tstruct string_list_item *item;\n> +\tchar *path = xstrdup(rebase_path_update_refs());\n> +\n> +\tif (safe_create_leading_directories(path)) {\n> +\t\tresult = error(_(\"unable to create leading directories of %s\"),\n> +\t\t\t       path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tfp = fopen(path, \"w\");\n\nI realized (while trying to go to sleep, of course) that I need\nto use a lockfile here. Parallel processes might be reading this\nfile while we are writing it.\n\nThanks,\n-Stolee\n"},{"id":"458169","messageId":"f27d563b-d011-9983-8f7d-52232b052608@gmail.com","threadId":"57949","inReplyTo":"2bc647b6fcd6032a1e97e67739ced31117bfbfce.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-30T09:32:20Z","receivedAt":"2022-06-30T09:32:33Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> The branch_checked_out() helper helps commands like 'git branch' and\n> 'git fetch' from overwriting refs that are currently checked out in\n> other worktrees.\n> \n> A future update to 'git rebase' will introduce a new '--update-refs'\n> option which will update the local refs that point to commits that are\n> being rebased. To avoid collisions as the rebase completes, we want to\n> make the future data store for these refs to be considered by\n> branch_checked_out().\n> \n> The data store is a plaintext file inside the 'rebase-merge' directory\n> for that worktree. The file alternates refnames and OIDs. The OIDs will\n> be used to store the to-be-written values as the rebase progresses, but\n> can be ignored at the moment.\n> \n> Create a new sequencer_get_update_refs_state() method that parses this\n> file and populates a struct string_list with the ref-OID pairs. We can\n> then use this list to add to the current_checked_out_branches strmap\n> used by branch_checked_out().\n> \n> To properly navigate to the rebase directory for a given worktree,\n> extract the static strbuf_worktree_gitdir() method to a public API\n> method.\n> \n> We can test that this works without having Git write this file by\n> artificially creating one in our test script.\n\nThis looks good apart from a couple of small nits\n\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>   branch.c                  | 13 ++++++++++++\n>   sequencer.c               | 42 +++++++++++++++++++++++++++++++++++++++\n>   sequencer.h               |  9 +++++++++\n>   t/t2407-worktree-heads.sh | 22 ++++++++++++++++----\n>   4 files changed, 82 insertions(+), 4 deletions(-)\n> \n> diff --git a/branch.c b/branch.c\n> index 526e8237673..f252c4eef6c 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -365,6 +365,7 @@ static void prepare_checked_out_branches(void)\n>   \t\tchar *old;\n>   \t\tstruct wt_status_state state = { 0 };\n>   \t\tstruct worktree *wt = worktrees[i++];\n> +\t\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n>   \n>   \t\tif (wt->is_bare)\n>   \t\t\tcontinue;\n> @@ -400,6 +401,18 @@ static void prepare_checked_out_branches(void)\n>   \t\t\tstrbuf_release(&ref);\n>   \t\t}\n>   \t\twt_status_state_free_buffers(&state);\n> +\n> +\t\tif (!sequencer_get_update_refs_state(get_worktree_git_dir(wt),\n> +\t\t\t\t\t\t     &update_refs)) {\n> +\t\t\tstruct string_list_item *item;\n> +\t\t\tfor_each_string_list_item(item, &update_refs) {\n> +\t\t\t\told = strmap_put(&current_checked_out_branches,\n> +\t\t\t\t\t\t item->string,\n> +\t\t\t\t\t\t xstrdup(wt->path));\n> +\t\t\t\tfree(old);\n> +\t\t\t}\n> +\t\t\tstring_list_clear(&update_refs, 1);\n> +\t\t}\n>   \t}\n>   \n>   \tfree_worktrees(worktrees);\n> diff --git a/sequencer.c b/sequencer.c\n> index 8c3ed3532ac..1094e146b99 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -5901,3 +5901,45 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)\n>   \n>   \treturn 0;\n>   }\n> +\n> +int sequencer_get_update_refs_state(const char *wt_dir,\n> +\t\t\t\t    struct string_list *refs)\n> +{\n> +\tint result = 0;\n> +\tstruct stat st;\n> +\tFILE *fp = NULL;\n> +\tstruct strbuf ref = STRBUF_INIT;\n> +\tstruct strbuf hash = STRBUF_INIT;\n> +\tchar *path = xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n\nI think it would make sense to introduce rebase_path_update_refs() in \nthis patch rather than later in the series\n\n> +\n> +\tif (stat(path, &st))\n> +\t\tgoto cleanup;\n\nWhat's the reason for stating the file first, rather than just letting \nfopen() fail if it does not exist?\n\nBest Wishes\n\nPhillip\n"},{"id":"458170","messageId":"d6ea65f5-3aa5-aa8c-dad1-a8f36d56b07b@gmail.com","threadId":"57949","inReplyTo":"669f4abd59e7dbb13281e85144d085180934fd15.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/8] rebase-interactive: update 'merge' description","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-30T09:34:02Z","receivedAt":"2022-06-30T09:34:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> The 'merge' command description for the todo list documentation in an\n> interactive rebase has multiple lines. The lines other than the first\n> one start with dots ('.') while the similar multi-line documentation for\n> 'fixup' does not.\n> \n> The 'merge' command was documented when interactive rebase was first\n> ported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n> 2018-08-10). These dots might have been carried over from the previous\n> shell implementation.\n> \n> The 'fixup' command was documented more recently in 9e3cebd97cb (rebase\n> -i: add fixup [-C | -c] command, 2021-01-29).\n> \n> Looking at the output in an editor, my personal opinion is that the dots\n> are unnecessary and noisy. Remove them now before adding more commands\n> with multi-line documentation.\n\nI agree, this is a nice cleanup\n\nThanks\n\nPhillip\n\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>   rebase-interactive.c | 6 +++---\n>   1 file changed, 3 insertions(+), 3 deletions(-)\n> \n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index 87649d0c016..22394224faa 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -54,9 +54,9 @@ void append_todo_help(int command_count,\n>   \"l, label <label> = label current HEAD with a name\\n\"\n>   \"t, reset <label> = reset HEAD to a label\\n\"\n>   \"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\\n\"\n> -\".       create a merge commit using the original merge commit's\\n\"\n> -\".       message (or the oneline, if no original merge commit was\\n\"\n> -\".       specified); use -c <commit> to reword the commit message\\n\"\n> +\"        create a merge commit using the original merge commit's\\n\"\n> +\"        message (or the oneline, if no original merge commit was\\n\"\n> +\"        specified); use -c <commit> to reword the commit message\\n\"\n>   \"\\n\"\n>   \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n>   \tunsigned edit_todo = !(shortrevisions && shortonto);\n\n"},{"id":"458171","messageId":"1ce55924-c151-3d27-90d6-82fed92a4d91@gmail.com","threadId":"57949","inReplyTo":"e95ad41d3551244ea98c43145a089ecfd2536e54.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 5/8] sequencer: add update-ref command","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-30T09:39:59Z","receivedAt":"2022-06-30T09:40:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> Add the boilerplate for an \"update-ref\" command in the sequencer. This\n> connects to the current no-op do_update_ref() which will be filled in\n> after more connections are created.\n> \n> The syntax in the todo list will be \"update-ref <ref-name>\" to signal\n> that we should store the current commit as the value for updating\n> <ref-name> at the end of the rebase.\n> \n> [...]\n> @@ -4431,6 +4437,12 @@ static int pick_commits(struct repository *r,\n>   \t\t\t\treturn error_with_patch(r, item->commit,\n>   \t\t\t\t\t\t\targ, item->arg_len,\n>   \t\t\t\t\t\t\topts, res, 0);\n> +\t\t} else if (item->command == TODO_UPDATE_REF) {\n> +\t\t\tstruct strbuf ref = STRBUF_INIT;\n> +\t\t\tstrbuf_add(&ref, arg, item->arg_len);\n\nIt's a bit of a shame we have to faff here just to get a NULL terminated \nstring. It does not matter but using xmemdupz() & free() would be one \nless line.\n\n> +\t\t\tif ((res = do_update_ref(r, ref.buf)))\n> +\t\t\t\treschedule = 1;\n\nRescheduling here is good\n\nBest Wishes\n\nPhillip\n"},{"id":"458172","messageId":"4907af96-cb62-1d7f-d9ea-18d0e646f4ea@gmail.com","threadId":"57949","inReplyTo":"9d188e11-7bb4-cae7-b38a-f9f38c8c2fe1@github.com","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-06-30T09:47:01Z","receivedAt":"2022-06-30T09:47:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 29/06/2022 13:58, Derrick Stolee wrote:\n> On 6/28/22 4:48 PM, Junio C Hamano wrote:\n>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>>> From: Derrick Stolee <derrickstolee@github.com>\n>>>\n>>> The branch_checked_out() helper helps commands like 'git branch' and\n>>> 'git fetch' from overwriting refs that are currently checked out in\n>>> other worktrees.\n>>>\n>>> A future update to 'git rebase' will introduce a new '--update-refs'\n>>> option which will update the local refs that point to commits that are\n>>> being rebased. To avoid collisions as the rebase completes, we want to\n>>> make the future data store for these refs to be considered by\n>>> branch_checked_out().\n>>>\n>>> The data store is a plaintext file inside the 'rebase-merge' directory\n>>> for that worktree. The file alternates refnames and OIDs. The OIDs will\n>>> be used to store the to-be-written values as the rebase progresses, but\n>>> can be ignored at the moment.\n>>>\n>>> Create a new sequencer_get_update_refs_state() method that parses this\n>>> file and populates a struct string_list with the ref-OID pairs. We can\n>>> then use this list to add to the current_checked_out_branches strmap\n>>> used by branch_checked_out().\n>>>\n>>> To properly navigate to the rebase directory for a given worktree,\n>>> extract the static strbuf_worktree_gitdir() method to a public API\n>>> method.\n>>>\n>>> We can test that this works without having Git write this file by\n>>> artificially creating one in our test script.\n>>\n>> Hmph, I am not thrilled to see an ad-hoc text file for things like\n>> this.  Do the objects that appear in this file otherwise already\n>> anchored by existing refs, or can some of them be \"floating\" without\n>> any refs pointing at them, i.e. making the \"connected\" and \"fsck\"\n>> machinery also being aware of them to protect them from collected as\n>> garbage and reported as dangling/unreachable?\n> \n> You're right that we could have this sequence of events in a todo\n> file:\n> \n>    pick deadbeef Here is a commit that will become unreachable\n>    update-ref will-be-lost\n> \n>    squash 1234567 make will-be-lost unreachable\n>    ...\n> \n> So if a GC runs after this 'update-ref' step but before the rebase\n> completes, then that commit can be lost. The ref-update at the end\n> of the rebase process would fail.\n\nThe commit is protected by HEAD's reflog for a while because we update \nHEAD after each pick but it is not permanently safe.\n\nI've taken a look at the first few patches and they're looking good, I \nhope to look at the rest tomorrow\n\nBest Wishes\n\nPhillip\n\n> I wonder how important such a situation is, though. But I'm willing\n> to add the extra lookups in 'git gc' and 'git fsck' to make this a\n> non-issue.\n> \n> I'll take a look to see where the other refs in rebase-<backend>/*\n> are handled by these processes.\n> \n> Thanks,\n> -Stolee\n\n"},{"id":"458188","messageId":"b0c596d6-f4aa-9575-4d79-64bd112d0baf@github.com","threadId":"57949","inReplyTo":"f27d563b-d011-9983-8f7d-52232b052608@gmail.com","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-30T13:35:34Z","receivedAt":"2022-06-30T13:35:41Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/30/2022 5:32 AM, Phillip Wood wrote:\n> On 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n>> From: Derrick Stolee <derrickstolee@github.com>\n\n>> +int sequencer_get_update_refs_state(const char *wt_dir,\n>> +                    struct string_list *refs)\n>> +{\n>> +    int result = 0;\n>> +    struct stat st;\n>> +    FILE *fp = NULL;\n>> +    struct strbuf ref = STRBUF_INIT;\n>> +    struct strbuf hash = STRBUF_INIT;\n>> +    char *path = xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n> \n> I think it would make sense to introduce rebase_path_update_refs() in this patch rather than later in the series\n\nThe biggest difference is that rebase_path_update_refs() only\ngives the path for the current worktree, while this method needs\nto read the file for any worktree.\n\nThere is likely some opportunity to create rebase_path_update_refs()\nin a different way that serves both purposes.\n\n>> +\n>> +    if (stat(path, &st))\n>> +        goto cleanup;\n> \n> What's the reason for stating the file first, rather than just letting fopen() fail if it does not exist?\n\nNot sure what I was looking at that gave this pattern, but you're\nright that it isn't necessary.\n\nThanks,\n-Stolee\n"},{"id":"458203","messageId":"xmqqwncyf6dx.fsf@gitster.g","threadId":"57949","inReplyTo":"4016eee6-0759-dece-cd7b-0ed8454faf63@github.com","subject":"Re: [PATCH v3 1/8] t2407: test branches currently using apply backend","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-30T16:44:26Z","receivedAt":"2022-06-30T16:44:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n> I can either re-roll that series or create a new forward-fix that\n> includes the functionality of this test. Both are the same amount of\n> work for me, so let me know which you prefer.\n\nEither is fine by me.  Other than this small glitch in the test, the\nremainder of the \"should we allow this branch to be touched?\" topic\nlooked really cleanly done and ready to be unleashed to the public.\n\nThanks.\n"},{"id":"458204","messageId":"xmqqr136f65l.fsf@gitster.g","threadId":"57949","inReplyTo":"9d188e11-7bb4-cae7-b38a-f9f38c8c2fe1@github.com","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-30T16:49:26Z","receivedAt":"2022-06-30T16:49:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n> I wonder how important such a situation is, though. But I'm willing\n> to add the extra lookups in 'git gc' and 'git fsck' to make this a\n> non-issue.\n\nWhat I was hinting at was if some of this can be done by using a\nref, so that we do not have to touch \"gc\" or \"fsck\" at all.\n\nAs to the importance, I would say it is about the same importance as\nbeing able to prevent the branches involved in the rebase from\ntouched in other worktrees.  We expect it to take sufficiently large\nwallclock time from the beginning of a rebase and its finish to make\nsuch a protection necessary, so an auto-gc may even be started from\nother worktrees without our knowledge.\n\nThanks.\n\n"},{"id":"458205","messageId":"xmqqmtduf63e.fsf@gitster.g","threadId":"57949","inReplyTo":"4907af96-cb62-1d7f-d9ea-18d0e646f4ea@gmail.com","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-30T16:50:45Z","receivedAt":"2022-06-30T16:50:54Z","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> The commit is protected by HEAD's reflog for a while because we update\n> HEAD after each pick but it is not permanently safe.\n\nAh, I forgot about that one.  Thanks for pointing it out.  It does\nsound like HEAD's reflog makes it safe enough.\n\n"},{"id":"458207","messageId":"xmqqiloif5e4.fsf@gitster.g","threadId":"57949","inReplyTo":"eb4345ce-8e1c-2f5f-7dfb-41979a89491a@github.com","subject":"Re: [PATCH v3 3/8] rebase-interactive: update 'merge' description","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-30T17:05:55Z","receivedAt":"2022-06-30T17:06:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n> Perhaps I should be a bit clearer that these are appearing in the\n> comment section of the todo-file when presented to the user for editing:\n\nYup, it is good to spell it out for others.\n\n> This does not appear to be used anywhere else.\n\nNowhere in our codebase.  I do not think we have a way to know if\nusers (e.g. hooks and scripts) depend on them, but I am reasonably\nsure that nobody would complain ;-)\n"},{"id":"458209","messageId":"xmqqedz6f58n.fsf@gitster.g","threadId":"57949","inReplyTo":"f2af844e-fc6c-94fd-7cce-0451f57c6576@github.com","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-30T17:09:12Z","receivedAt":"2022-06-30T17:09:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n> On 6/28/22 5:15 PM, Junio C Hamano wrote:\n>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> ...\n>> It would be nice to also have a test that makes sure that other\n>> people will be prevented from checking out a branch whose tips may\n>> be updated at the end.\n>\n> Patch 3 adds the tests that 'git branch -f' cannot update these refs,\n\nAh, I didn't notice.  We are covered then.  Thanks.\n"},{"id":"458216","messageId":"038f1c9e-0c75-c011-22c9-a8f478e4f1bf@github.com","threadId":"57949","inReplyTo":"xmqqwncyf6dx.fsf@gitster.g","subject":"Re: [PATCH v3 1/8] t2407: test branches currently using apply backend","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-06-30T17:35:49Z","receivedAt":"2022-06-30T17:35:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/30/2022 12:44 PM, Junio C Hamano wrote:\n> Derrick Stolee <derrickstolee@github.com> writes:\n> \n>> I can either re-roll that series or create a new forward-fix that\n>> includes the functionality of this test. Both are the same amount of\n>> work for me, so let me know which you prefer.\n> \n> Either is fine by me.  Other than this small glitch in the test, the\n> remainder of the \"should we allow this branch to be touched?\" topic\n> looked really cleanly done and ready to be unleashed to the public.\n\nIf you don't mind, I've got some forward-fixes prepped for the next\nversion of this topic. They are test-only changes and do not change\nthe implementation of branch_checked_out().\n\nOne thing I noticed from looking back on the series is that I wrote a\ntest that specifically tests a case that is impossible to construct\nwithout modifying the .git directory directly (having two worktrees\n\"reserving\" a branch for different reasons). That test will need to\nstay as one that knows about the internals of this storage, but the\nothers can be replaced with more opaque steps.\n\nIn patch 2 of this series, I test the 'update-refs' storage using a\nsimilar mechanism. I can replace that with the opaque version that\ncalls 'git rebase --update-refs', but only as part of patch 7, since\nwriting the file is not implemented yet.\n\nI was going to ask if I should reorder the patches, but there is a\npossibility that we will change the storage of these \"reserved\" refs\nyet again, so I won't focus on this point quite yet.\n\nThanks,\n-Stolee\n"},{"id":"458345","messageId":"f410f5fd-aee9-0ec1-d5a7-639a4eb8fdb0@gmail.com","threadId":"57949","inReplyTo":"918b398d6a2264a99acadd319f780c796bbafc18.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-01T09:20:00Z","receivedAt":"2022-07-01T09:20:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> When working on a large feature, it can be helpful to break that feature\n> into multiple smaller parts that become reviewed in sequence. During\n> development or during review, a change to one part of the feature could\n> affect multiple of these parts. An interactive rebase can help adjust\n> the multi-part \"story\" of the branch.\n> \n> However, if there are branches tracking the different parts of the\n> feature, then rebasing the entire list of commits can create commits not\n> reachable from those \"sub branches\". It can take a manual step to update\n> those branches.\n> \n> Add a new --update-refs option to 'git rebase -i' that adds 'update-ref\n> <ref>' steps to the todo file whenever a commit that is being rebased is\n> decorated with that <ref>. At the very end, the rebase process updates\n> all of the listed refs to the values stored during the rebase operation.\n> \n> Be sure to iterate after any squashing or fixups are placed. Update the\n> branch only after those squashes and fixups are complete. This allows a\n> --fixup commit at the tip of the feature to apply correctly to the sub\n> branch, even if it is fixing up the most-recent commit in that part.\n> \n> One potential problem here is that refs decorating commits that are\n> already marked as \"fixup!\" or \"squash!\" will not be included in this\n> list. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\n> to change the relative order of these refs, so it is not recommended.\n> The workflow here is intended to allow these kinds of commits at the tip\n> of the rebased branch while the other sub branches come along for the\n> ride without intervention.\n> \n> This change update the documentation and builtin to accept the\n> --update-refs option as well as updating the todo file with the\n> 'update-ref' commands. Tests are added to ensure that these todo\n> commands are added in the correct locations.\n> \n> A future change will update the behavior to actually update the refs\n> at the end of the rebase sequence.\n\nThis looks quite good to me, my main concern is that we don't check what \nthe user did when they edited the todo list which leaves a potentially \nlong window when another process could update a branch that is being \nrebased. I've left some comments below, they're mostly me talking to \nmyself while reading the code.\n\n> +struct todo_add_branch_context {\n> +\tstruct todo_item *items;\n> +\tsize_t items_nr;\n> +\tsize_t items_alloc;\n> +\tstruct strbuf *buf;\n> +\tstruct commit *commit;\n> +\tstruct string_list refs_to_oids;\n> +};\n> +\n> +static int add_decorations_to_list(const struct commit *commit,\n> +\t\t\t\t   struct todo_add_branch_context *ctx)\n> +{\n> +\tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n> +\n> +\twhile (decoration) {\n> +\t\tstruct todo_item *item;\n> +\t\tconst char *path;\n> +\t\tsize_t base_offset = ctx->buf->len;\n> +\n> +\t\tALLOC_GROW(ctx->items,\n> +\t\t\tctx->items_nr + 1,\n> +\t\t\tctx->items_alloc);\n> +\t\titem = &ctx->items[ctx->items_nr];\n> +\t\tmemset(item, 0, sizeof(*item));\n> +\n> +\t\t/* If the branch is checked out, then leave a comment instead. */\n> +\t\tif ((path = branch_checked_out(decoration->name))) {\n> +\t\t\titem->command = TODO_COMMENT;\n> +\t\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n> +\t\t\t\t    decoration->name, path);\n> +\t\t} else {\n> +\t\t\tstruct string_list_item *sti;\n> +\t\t\titem->command = TODO_UPDATE_REF;\n> +\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n> +\n> +\t\t\tsti = string_list_append(&ctx->refs_to_oids,\n> +\t\t\t\t\t\t decoration->name);\n> +\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n\nWe don't record where the branch is pointing, but we do stop checkout \nfrom checking out the branch. There is a small race window where another \nprocess can checkout the branch (or start rebasing a branch with 'git \nrebase <upstream> <branch>') after we've added it to this list but have \nnot written it to disk.\n\n> +\t\t}\n> +\n> +\t\titem->offset_in_buf = base_offset;\n> +\t\titem->arg_offset = base_offset;\n> +\t\titem->arg_len = ctx->buf->len - base_offset;\n> +\t\tctx->items_nr++;\n> +\n> +\t\tdecoration = decoration->next;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +/*\n> + * For each 'pick' command, find out if the commit has a decoration in\n> + * refs/heads/. If so, then add a 'label for-update-refs/' command.\n> + */\n> +static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n> +{\n> +\tint i;\n> +\tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n> +\tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n> +\tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n> +\tstruct decoration_filter decoration_filter = {\n> +\t\t.include_ref_pattern = &decorate_refs_include,\n> +\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n> +\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config,\n> +\t};\n> +\tstruct todo_add_branch_context ctx = {\n> +\t\t.buf = &todo_list->buf,\n> +\t\t.refs_to_oids = STRING_LIST_INIT_DUP,\n> +\t};\n> +\n> +\tctx.items_alloc = 2 * todo_list->nr + 1;\n\nWe pre-allocate enough space for each commit to have an update-refs \ncommand added after it ...\n\n> +\tALLOC_ARRAY(ctx.items, ctx.items_alloc);\n> +\n> +\tstring_list_append(&decorate_refs_include, \"refs/heads/\");\n> +\tload_ref_decorations(&decoration_filter, 0);\n> +\n> +\tfor (i = 0; i < todo_list->nr; ) {\n> +\t\tstruct todo_item *item = &todo_list->items[i];\n> +\n> +\t\t/* insert ith item into new list */\n> +\t\tALLOC_GROW(ctx.items,\n> +\t\t\t   ctx.items_nr + 1,\n> +\t\t\t   ctx.items_alloc);\n\nBut we are careful to grow the array if that space is not enough.\n\n> +\t\tctx.items[ctx.items_nr++] = todo_list->items[i++];\n> +\n> +\t\tif (item->commit) {\n> +\t\t\tctx.commit = item->commit;\n> +\t\t\tadd_decorations_to_list(item->commit, &ctx);\n> +\t\t}\n> +\t}\n> +\n> +\tstring_list_clear(&ctx.refs_to_oids, 1);\n> +\tfree(todo_list->items);\n> +\ttodo_list->items = ctx.items;\n> +\ttodo_list->nr = ctx.items_nr;\n> +\ttodo_list->alloc = ctx.items_alloc;\n> +\n> +\treturn 0;\n> +}\n> +\n>   int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n>   \t\t    const char *shortrevisions, const char *onto_name,\n>   \t\t    struct commit *onto, const struct object_id *orig_head,\n>   \t\t    struct string_list *commands, unsigned autosquash,\n> +\t\t    unsigned update_refs,\n>   \t\t    struct todo_list *todo_list)\n>   {\n>   \tchar shortonto[GIT_MAX_HEXSZ + 1];\n> @@ -5637,6 +5741,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>   \t\titem->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n>   \t}\n>   \n> +\tif (update_refs && todo_list_add_update_ref_commands(todo_list))\n> +\t\treturn -1;\n> +\n\nWe add the update-ref commands before the user edits the todo list but \ndo not check if they have added or removed any update-ref commands when \nthey edit it. This means we may lock a branch unnecessarily if they \ndeleted an update-ref command or fail to lock a branch in a timely \nmanner if they add an update-ref command. It will eventually be locked \nwhen we process the update-ref command during the rebase but that could \nbe quite a bit later if there are conflicts.\n\nIf we were to add the update-ref commands before the user edited the \nlist and then write the update-refs file after it has been edited that \nwould avoid some of these problems but leaves a bigger race window when \ntwo process think they can safely update a branch. That could be \naddressed by checking the other worktrees again once we have written the \nfile which we really need to do in order to check the user hasn't added \nan update-ref command for a branch that is already being rebased in \nanother worktree.\n\nBest Wishes\n\nPhillip\n"},{"id":"458349","messageId":"bee9eb82-6c6a-6467-37d1-d3797c583929@gmail.com","threadId":"57949","inReplyTo":"72e0481b643e98c5670eee0bf5c199c4fb693b16.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-01T09:31:05Z","receivedAt":"2022-07-01T09:31:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> The previous change introduced the 'git rebase --update-refs' option\n> which added 'update-ref <ref>' commands to the todo list of an\n> interactive rebase.\n> \n> Teach Git to record the HEAD position when reaching these 'update-ref'\n> commands. The ref/OID pair is stored in the\n> $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n> file to avoid having other processes updating the refs in that file\n> while the rebase is in progress.\n> \n> Not only do we update the file when the sequencer reaches these\n> 'update-ref' commands, we then update the refs themselves at the end of\n> the rebase sequence. If the rebase is aborted before this final step,\n> then the refs are not updated.\n\nThis looks good, I've left a few comments but it seems basically sound \nto me.\n\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>   sequencer.c                   | 114 +++++++++++++++++++++++++++++++++-\n>   t/t3404-rebase-interactive.sh |  23 +++++++\n>   2 files changed, 136 insertions(+), 1 deletion(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index 915d87a0336..4fd1c0b5bce 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -36,6 +36,7 @@\n>   #include \"rebase-interactive.h\"\n>   #include \"reset.h\"\n>   #include \"branch.h\"\n> +#include \"log-tree.h\"\n>   \n>   #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>   \n> @@ -148,6 +149,14 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n>    */\n>   static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n>   \n> +/*\n> + * The update-refs file stores a list of refs that will be updated at the end\n> + * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n> + * update the OIDs for the refs in this file, but the refs are not updated\n> + * until the end of the rebase sequence.\n> + */\n> +static GIT_PATH_FUNC(rebase_path_update_refs, \"rebase-merge/update-refs\")\n> +\n>   /*\n>    * The following files are written by git-rebase just after parsing the\n>    * command-line.\n> @@ -4058,11 +4067,110 @@ leave_merge:\n>   \treturn ret;\n>   }\n>   \n> -static int do_update_ref(struct repository *r, const char *ref_name)\n> +static int write_update_refs_state(struct string_list *refs_to_oids)\n> +{\n> +\tint result = 0;\n> +\tFILE *fp = NULL;\n> +\tstruct string_list_item *item;\n> +\tchar *path = xstrdup(rebase_path_update_refs());\n\nThis is leaked\n\n> +\tif (safe_create_leading_directories(path)) {\n> +\t\tresult = error(_(\"unable to create leading directories of %s\"),\n> +\t\t\t       path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tfp = fopen(path, \"w\");\n> +\tif (!fp) {\n> +\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n> +\t\tgoto cleanup;\n> +\t}\n\nCan we use fopen_or_warn() here? It ignores ENOENT and ENOTDIR but I \ndon't think that should matter here.\n\n> +\n> +\tfor_each_string_list_item(item, refs_to_oids)\n> +\t\tfprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n> +\n> +cleanup:\n> +\tif (fp)\n> +\t\tfclose(fp);\n> +\treturn result;\n> +}\n\n> +compare_two_refs () {\n> +\tgit rev-parse $1 >expect &&\n> +\tgit rev-parse $2 >actual &&\n> +\ttest_cmp expect actual\n> +}\n\nThis is just test_cmp_rev\n\n> +test_expect_success '--update-refs updates refs correctly' '\n> +\tgit checkout -B update-refs no-conflict-branch &&\n> +\tgit branch -f base HEAD~4 &&\n> +\tgit branch -f first HEAD~3 &&\n> +\tgit branch -f second HEAD~3 &&\n> +\tgit branch -f third HEAD~1 &&\n> +\ttest_commit extra2 fileX &&\n> +\tgit commit --amend --fixup=L &&\n> +\n> +\tgit rebase -i --autosquash --update-refs primary &&\n> +\n> +\tcompare_two_refs HEAD~3 refs/heads/first &&\n> +\tcompare_two_refs HEAD~3 refs/heads/second &&\n> +\tcompare_two_refs HEAD~1 refs/heads/third &&\n> +\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n> +'\n\nDo we need a new test for this or can we just check the refs at the end \nof one of the tests added in the last patch?\n\n>   # This must be the last test in this file\n>   test_expect_success '$EDITOR and friends are unchanged' '\n>   \ttest_editor_unchanged\n\nI forgot to say on the last patch but you could maybe add a \ntest_editor_unchanged at the end of t2407 now that there are tests which \ncall test_set_editor\n\nThanks for working on this, it will be a really useful addition to rebase.\n\nPhillip\n\n"},{"id":"458386","messageId":"85541613-cf4b-8631-0aa9-2a80ef4b5213@gmail.com","threadId":"57949","inReplyTo":"b0c596d6-f4aa-9575-4d79-64bd112d0baf@github.com","subject":"Re: [PATCH v3 2/8] branch: consider refs under 'update-refs'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-01T13:40:30Z","receivedAt":"2022-07-01T13:40:35Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 30/06/2022 14:35, Derrick Stolee wrote:\n> On 6/30/2022 5:32 AM, Phillip Wood wrote:\n>> On 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n>>> From: Derrick Stolee <derrickstolee@github.com>\n> \n>>> +int sequencer_get_update_refs_state(const char *wt_dir,\n>>> +                    struct string_list *refs)\n>>> +{\n>>> +    int result = 0;\n>>> +    struct stat st;\n>>> +    FILE *fp = NULL;\n>>> +    struct strbuf ref = STRBUF_INIT;\n>>> +    struct strbuf hash = STRBUF_INIT;\n>>> +    char *path = xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n>>\n>> I think it would make sense to introduce rebase_path_update_refs() in this patch rather than later in the series\n> \n> The biggest difference is that rebase_path_update_refs() only\n> gives the path for the current worktree, while this method needs\n> to read the file for any worktree.\n\nThat's an important distinction that I'd failed to notice\n\n> There is likely some opportunity to create rebase_path_update_refs()\n> in a different way that serves both purposes.\n\nThat would be nice, even just having a #define for \n\"rebase-merge/update-refs\" and using that here and in the other patch \nwould mean we're not hard coding the filename in two different places.\n\nBest Wishes\n\nPhillip\n\n>>> +\n>>> +    if (stat(path, &st))\n>>> +        goto cleanup;\n>>\n>> What's the reason for stating the file first, rather than just letting fopen() fail if it does not exist?\n> \n> Not sure what I was looking at that gave this pattern, but you're\n> right that it isn't necessary.\n> \n> Thanks,\n> -Stolee\n"},{"id":"458387","messageId":"799939ac-e54d-b156-efe1-5f2d7edf2312@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/8] rebase: update branches in multi-part topic","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-01T13:43:41Z","receivedAt":"2022-07-01T13:43:48Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 28/06/2022 14:25, Derrick Stolee via GitGitGadget wrote:\n> (Update: This is now based on ds/branch-checked-out.)\n> \n> This is a feature I've wanted for quite a while. When working on the sparse\n> index topic, I created a long RFC that actually broke into three topics for\n> full review upstream. These topics were sequential, so any feedback on an\n> earlier one required updates to the later ones. I would work on the full\n> feature and use interactive rebase to update the full list of commits.\n> However, I would need to update the branches pointing to those sub-topics.\n> \n> This series adds a new --update-refs option to 'git rebase' (along with a\n> rebase.updateRefs config option) that adds 'update-ref' commands into the\n> TODO list. This is powered by the commit decoration machinery.\n> \n> As an example, here is my in-progress bundle URI RFC split into subtopics as\n> they appear during the TODO list of a git rebase -i --update-refs:\n> \n> pick 2d966282ff3 docs: document bundle URI standard\n> pick 31396e9171a remote-curl: add 'get' capability\n> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n> pick 6adaf842684 fetch: add --bundle-uri option\n> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n> update-ref refs/heads/bundle-redo/fetch\n\nI like the direction this is moving, my only concern with this version \nis how we handle the user adding and removing update-ref commands when \nthe user edits the todo list.\n\nBest Wishes\n\nPhillip\n\n> pick 1e3f6546632 clone: add --bundle-uri option\n> pick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\n> update-ref refs/heads/bundle-redo/clone\n> \n> pick 5451cb6599c bundle-uri: create bundle_list struct and helpers\n> pick 3029c3aca15 bundle-uri: create base key-value pair parsing\n> pick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\n> pick 92625a47673 bundle-uri: unit test \"key=value\" parsing\n> pick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\n> pick 9d6809a8d53 bundle-uri: parse bundle list in config format\n> pick 287a732b54c bundle-uri: fetch a list of bundles\n> update-ref refs/heads/bundle-redo/list\n> \n> pick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\n> pick 520204dcd1c bundle-uri client: add minimal NOOP client\n> pick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\n> pick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\n> pick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\n> pick caf4599a81d bundle-uri: allow relative URLs in bundle lists\n> pick df255000b7e bundle-uri: download bundles from an advertised list\n> pick d71beabf199 clone: unbundle the advertised bundles\n> pick c9578391976 t5601: basic bundle URI tests\n> # Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n> \n> update-ref refs/heads/bundle-redo/advertise\n> \n> \n> Here is an outline of the series:\n> \n>   * Patch 1 updates some tests for branch_checked_out() for the 'apply'\n>     backend.\n>   * Patch 2 updates branch_checked_out() to parse the\n>     rebase-merge/update-refs file to block concurrent ref updates and\n>     checkouts on branches selected by --update-refs.\n>   * Patch 3 updates the todo list documentation to remove some unnecessary\n>     dots in the 'merge' command. This makes it consistent with the 'fixup'\n>     command before we document the 'update-ref' command.\n>   * Patch 4 updates the definition of todo_command_info to use enum values as\n>     array indices.\n>   * Patches 5-7 implement the --update-refs logic itself.\n>   * Patch 8 adds the rebase.updateRefs config option similar to\n>     rebase.autoSquash.\n> \n> \n> Updates in v3\n> =============\n> \n>   * The branch_checked_out() API was extracted to its own topic and is now\n>     the ds/branch-checked-out branch. This series is now based on that one.\n>   * The for_each_decoration() API was removed, since it became trivial once\n>     it did not take a commit directly.\n>   * The branch_checked_out() tests did not verify the rebase-apply data (for\n>     the apply backend), so that is fixed.\n>   * Instead of using the 'label' command and a final 'update-refs' command in\n>     the todo list, use a new 'update-ref ' command. This command updates the\n>     rebase-merge/update-refs file with the OID of HEAD at these steps. At the\n>     very end of the rebase sequence, those refs are updated to the stored OID\n>     values (assuming that they were not removed by the user, in which case we\n>     notice that the OID is the null OID and we do nothing).\n>   * New tests are added.\n>   * The todo-list comment documentation has some new formatting updates, but\n>     also includes a description of 'update-refs' in this version.\n> \n> \n> Updates in v2\n> =============\n> \n> As recommended by the excellent feedback, I have removed the 'exec' commands\n> in favor of the 'label' commands and a new 'update-refs' command at the very\n> end. This way, there is only one step that updates all of the refs at the\n> end instead of updating refs during the rebase. If a user runs 'git rebase\n> --abort' in the middle, then their refs are still where they need to be.\n> \n> Based on some of the discussion, it seemed like one way to do this would be\n> to have an 'update-ref ' command that would take the place of these 'label'\n> commands. However, this would require two things that make it a bit awkward:\n> \n>   1. We would need to replicate the storage of those positions during the\n>      rebase. 'label' already does this pretty well. I've added the\n>      \"for-update-refs/\" label to help here.\n>   2. If we want to close out all of the refs as the rebase is finishing, then\n>      that \"step\" becomes invisible to the user (and a bit more complicated to\n>      insert). Thus, the 'update-refs' step performs this action. If the user\n>      wants to do things after that step, then they can do so by editing the\n>      TODO list.\n> \n> Other updates:\n> \n>   * The 'keep_decorations' parameter was renamed to 'update_refs'.\n>   * I added tests for --rebase-merges=rebase-cousins to show how these labels\n>     interact with other labels and merge commands.\n>   * I changed the order of the insertion of these update-refs labels to be\n>     before the fixups are rearranged. This fixes a bug where the tip commit\n>     is a fixup! so its decorations are never inspected (and they would be in\n>     the wrong place even if they were). The fixup! commands are properly\n>     inserted between a pick and its following label command. Tests\n>     demonstrate this is correct.\n>   * Numerous style choices are updated based on feedback.\n> \n> Thank you for all of the detailed review and ideas in this space. I\n> appreciate any more ideas that can make this feature as effective as it can\n> be.\n> \n> Thanks, -Stolee\n> \n> Derrick Stolee (8):\n>    t2407: test branches currently using apply backend\n>    branch: consider refs under 'update-refs'\n>    rebase-interactive: update 'merge' description\n>    sequencer: define array with enum values\n>    sequencer: add update-ref command\n>    rebase: add --update-refs option\n>    rebase: update refs from 'update-ref' commands\n>    rebase: add rebase.updateRefs config option\n> \n>   Documentation/config/rebase.txt |   3 +\n>   Documentation/git-rebase.txt    |  11 ++\n>   branch.c                        |  13 ++\n>   builtin/rebase.c                |  10 ++\n>   rebase-interactive.c            |   9 +-\n>   sequencer.c                     | 303 ++++++++++++++++++++++++++++++--\n>   sequencer.h                     |  11 ++\n>   t/t2407-worktree-heads.sh       |  52 +++++-\n>   t/t3404-rebase-interactive.sh   | 107 +++++++++++\n>   9 files changed, 496 insertions(+), 23 deletions(-)\n> \n> \n> base-commit: 9bef0b1e6ec371e786c2fba3edcc06ad040a536c\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v3\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v3\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1247\n> \n> Range-diff vs v2:\n> \n>   1:  4f9f3487641 < -:  ----------- log-tree: create for_each_decoration()\n>   2:  5f54766e103 < -:  ----------- branch: add branch_checked_out() helper\n>   -:  ----------- > 1:  fbaedc7f1f0 t2407: test branches currently using apply backend\n>   -:  ----------- > 2:  2bc647b6fcd branch: consider refs under 'update-refs'\n>   -:  ----------- > 3:  669f4abd59e rebase-interactive: update 'merge' description\n>   3:  9f261c7df2c = 4:  6528a50343f sequencer: define array with enum values\n>   4:  842b2186d25 ! 5:  e95ad41d355 sequencer: add update-refs command\n>       @@ Metadata\n>        Author: Derrick Stolee <derrickstolee@github.com>\n>        \n>         ## Commit message ##\n>       -    sequencer: add update-refs command\n>       +    sequencer: add update-ref command\n>        \n>       -    Add the boilerplat for an \"update-refs\" command in the sequencer. This\n>       -    connects to the current no-op do_update_refs() which will be filled in\n>       +    Add the boilerplate for an \"update-ref\" command in the sequencer. This\n>       +    connects to the current no-op do_update_ref() which will be filled in\n>            after more connections are created.\n>        \n>       +    The syntax in the todo list will be \"update-ref <ref-name>\" to signal\n>       +    that we should store the current commit as the value for updating\n>       +    <ref-name> at the end of the rebase.\n>       +\n>            Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n>        \n>       + ## rebase-interactive.c ##\n>       +@@ rebase-interactive.c: void append_todo_help(int command_count,\n>       + \"        create a merge commit using the original merge commit's\\n\"\n>       + \"        message (or the oneline, if no original merge commit was\\n\"\n>       + \"        specified); use -c <commit> to reword the commit message\\n\"\n>       ++\"u, update-ref <ref> = track a placeholder for the <ref> to be updated\\n\"\n>       ++\"                      to this position in the new commits. The <ref> is\\n\"\n>       ++\"                      updated at the end of the rebase\\n\"\n>       + \"\\n\"\n>       + \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n>       + \tunsigned edit_todo = !(shortrevisions && shortonto);\n>       +\n>         ## sequencer.c ##\n>        @@ sequencer.c: static struct {\n>         \t[TODO_LABEL] = { 'l', \"label\" },\n>         \t[TODO_RESET] = { 't', \"reset\" },\n>         \t[TODO_MERGE] = { 'm', \"merge\" },\n>       -+\t[TODO_UPDATE_REFS] = { 'u', \"update-refs\" },\n>       ++\t[TODO_UPDATE_REF] = { 'u', \"update-ref\" },\n>         \t[TODO_NOOP] = { 0,   \"noop\" },\n>         \t[TODO_DROP] = { 'd', \"drop\" },\n>         \t[TODO_COMMENT] = { 0,   NULL },\n>        @@ sequencer.c: static int parse_insn_line(struct repository *r, struct todo_item *item,\n>       - \tpadding = strspn(bol, \" \\t\");\n>       - \tbol += padding;\n>       + \t\t\t     command_to_string(item->command));\n>         \n>       --\tif (item->command == TODO_NOOP || item->command == TODO_BREAK) {\n>       -+\tif (item->command == TODO_NOOP ||\n>       -+\t    item->command == TODO_BREAK ||\n>       -+\t    item->command == TODO_UPDATE_REFS) {\n>       - \t\tif (bol != eol)\n>       - \t\t\treturn error(_(\"%s does not accept arguments: '%s'\"),\n>       - \t\t\t\t     command_to_string(item->command), bol);\n>       + \tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n>       +-\t    item->command == TODO_RESET) {\n>       ++\t    item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {\n>       + \t\titem->commit = NULL;\n>       + \t\titem->arg_offset = bol - buf;\n>       + \t\titem->arg_len = (int)(eol - bol);\n>        @@ sequencer.c: leave_merge:\n>         \treturn ret;\n>         }\n>         \n>       -+static int do_update_refs(struct repository *r)\n>       ++static int do_update_ref(struct repository *r, const char *ref_name)\n>        +{\n>        +\treturn 0;\n>        +}\n>       @@ sequencer.c: static int pick_commits(struct repository *r,\n>         \t\t\t\treturn error_with_patch(r, item->commit,\n>         \t\t\t\t\t\t\targ, item->arg_len,\n>         \t\t\t\t\t\t\topts, res, 0);\n>       -+\t\t} else if (item->command == TODO_UPDATE_REFS) {\n>       -+\t\t\tif ((res = do_update_refs(r)))\n>       ++\t\t} else if (item->command == TODO_UPDATE_REF) {\n>       ++\t\t\tstruct strbuf ref = STRBUF_INIT;\n>       ++\t\t\tstrbuf_add(&ref, arg, item->arg_len);\n>       ++\t\t\tif ((res = do_update_ref(r, ref.buf)))\n>        +\t\t\t\treschedule = 1;\n>       ++\t\t\tstrbuf_release(&ref);\n>         \t\t} else if (!is_noop(item->command))\n>         \t\t\treturn error(_(\"unknown command %d\"), item->command);\n>         \n>       @@ sequencer.h: enum todo_command {\n>         \tTODO_LABEL,\n>         \tTODO_RESET,\n>         \tTODO_MERGE,\n>       -+\tTODO_UPDATE_REFS,\n>       ++\tTODO_UPDATE_REF,\n>         \t/* commands that do nothing but are counted for reporting progress */\n>         \tTODO_NOOP,\n>         \tTODO_DROP,\n>   5:  0a4c110127b ! 6:  918b398d6a2 rebase: add --update-refs option\n>       @@ Commit message\n>            reachable from those \"sub branches\". It can take a manual step to update\n>            those branches.\n>        \n>       -    Add a new --update-refs option to 'git rebase -i' that adds 'label\n>       -    for-update-refs/*' steps to the todo file whenever a commit that is\n>       -    being rebased is decorated with that <ref>. At the very end, the\n>       -    'update-refs' step is added to update all of the branches referenced by\n>       -    the 'label' steps. This allows the user to rebase a long list of commits\n>       -    in a multi-part feature and keep all of their pointers to those parts.\n>       +    Add a new --update-refs option to 'git rebase -i' that adds 'update-ref\n>       +    <ref>' steps to the todo file whenever a commit that is being rebased is\n>       +    decorated with that <ref>. At the very end, the rebase process updates\n>       +    all of the listed refs to the values stored during the rebase operation.\n>        \n>       -    NOTE: This change only introduce the --update-refs option and implements\n>       -    the changes to the todo file. It does _not_ yet implement the action\n>       -    taken by the 'update-refs' todo step, which will be implemented and\n>       -    tested in a later change.\n>       -\n>       -    Use the new for_each_decoration() while iterating over the existing todo\n>       -    list. Be sure to iterate after any squashing or fixups are placed.\n>       -    Update the branch only after those squashes and fixups are complete.\n>       -    This allows a --fixup commit at the tip of the feature to apply\n>       -    correctly to the sub branch, even if it is fixing up the most-recent\n>       -    commit in that part.\n>       +    Be sure to iterate after any squashing or fixups are placed. Update the\n>       +    branch only after those squashes and fixups are complete. This allows a\n>       +    --fixup commit at the tip of the feature to apply correctly to the sub\n>       +    branch, even if it is fixing up the most-recent commit in that part.\n>        \n>            One potential problem here is that refs decorating commits that are\n>            already marked as \"fixup!\" or \"squash!\" will not be included in this\n>       @@ Commit message\n>            of the rebased branch while the other sub branches come along for the\n>            ride without intervention.\n>        \n>       -    Be careful to not attempt updating any branch that is checked out. The\n>       -    most common example is the branch being rebased is checked out and\n>       -    decorates the tip commit. If the user is rebasing commits reachable from\n>       -    a different branch that is checked out in a different worktree, then\n>       -    they may be surprised to not see that ref update. However, it's probably\n>       -    best to not optimize for this scenario and do the safest thing that will\n>       -    result in a successful rebase. A comment is left in the TODO list that\n>       -    signals that these refs are currently checked out.\n>       +    This change update the documentation and builtin to accept the\n>       +    --update-refs option as well as updating the todo file with the\n>       +    'update-ref' commands. Tests are added to ensure that these todo\n>       +    commands are added in the correct locations.\n>       +\n>       +    A future change will update the behavior to actually update the refs\n>       +    at the end of the rebase sequence.\n>        \n>            Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n>        \n>       @@ sequencer.c\n>         #include \"rebase-interactive.h\"\n>         #include \"reset.h\"\n>        +#include \"branch.h\"\n>       -+#include \"log-tree.h\"\n>         \n>         #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>         \n>       @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n>        +\tsize_t items_alloc;\n>        +\tstruct strbuf *buf;\n>        +\tstruct commit *commit;\n>       ++\tstruct string_list refs_to_oids;\n>        +};\n>        +\n>       -+static int add_branch_for_decoration(const struct name_decoration *d, void *data)\n>       ++static int add_decorations_to_list(const struct commit *commit,\n>       ++\t\t\t\t   struct todo_add_branch_context *ctx)\n>        +{\n>       -+\tstruct todo_add_branch_context *ctx = data;\n>       -+\tsize_t base_offset = ctx->buf->len;\n>       -+\tstruct todo_item *item;\n>       -+\tchar *path;\n>       ++\tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n>        +\n>       -+\tALLOC_GROW(ctx->items,\n>       -+\t\t   ctx->items_nr + 1,\n>       -+\t\t   ctx->items_alloc);\n>       -+\titem = &ctx->items[ctx->items_nr];\n>       -+\tmemset(item, 0, sizeof(*item));\n>       ++\twhile (decoration) {\n>       ++\t\tstruct todo_item *item;\n>       ++\t\tconst char *path;\n>       ++\t\tsize_t base_offset = ctx->buf->len;\n>        +\n>       -+\t/* If the branch is checked out, then leave a comment instead. */\n>       -+\tif (branch_checked_out(d->name, &path)) {\n>       -+\t\titem->command = TODO_COMMENT;\n>       -+\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n>       -+\t\t\t    d->name, path);\n>       -+\t\tfree(path);\n>       -+\t} else {\n>       -+\t\titem->command = TODO_LABEL;\n>       -+\t\tstrbuf_addf(ctx->buf, \"for-update-refs/%s\\n\", d->name);\n>       -+\t}\n>       ++\t\tALLOC_GROW(ctx->items,\n>       ++\t\t\tctx->items_nr + 1,\n>       ++\t\t\tctx->items_alloc);\n>       ++\t\titem = &ctx->items[ctx->items_nr];\n>       ++\t\tmemset(item, 0, sizeof(*item));\n>       ++\n>       ++\t\t/* If the branch is checked out, then leave a comment instead. */\n>       ++\t\tif ((path = branch_checked_out(decoration->name))) {\n>       ++\t\t\titem->command = TODO_COMMENT;\n>       ++\t\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n>       ++\t\t\t\t    decoration->name, path);\n>       ++\t\t} else {\n>       ++\t\t\tstruct string_list_item *sti;\n>       ++\t\t\titem->command = TODO_UPDATE_REF;\n>       ++\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n>       ++\n>       ++\t\t\tsti = string_list_append(&ctx->refs_to_oids,\n>       ++\t\t\t\t\t\t decoration->name);\n>       ++\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n>       ++\t\t}\n>       ++\n>       ++\t\titem->offset_in_buf = base_offset;\n>       ++\t\titem->arg_offset = base_offset;\n>       ++\t\titem->arg_len = ctx->buf->len - base_offset;\n>       ++\t\tctx->items_nr++;\n>        +\n>       -+\titem->offset_in_buf = base_offset;\n>       -+\titem->arg_offset = base_offset;\n>       -+\titem->arg_len = ctx->buf->len - base_offset;\n>       -+\tctx->items_nr++;\n>       ++\t\tdecoration = decoration->next;\n>       ++\t}\n>        +\n>        +\treturn 0;\n>        +}\n>       @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n>        +\tstruct decoration_filter decoration_filter = {\n>        +\t\t.include_ref_pattern = &decorate_refs_include,\n>        +\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n>       -+\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config\n>       ++\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config,\n>        +\t};\n>        +\tstruct todo_add_branch_context ctx = {\n>        +\t\t.buf = &todo_list->buf,\n>       ++\t\t.refs_to_oids = STRING_LIST_INIT_DUP,\n>        +\t};\n>        +\n>        +\tctx.items_alloc = 2 * todo_list->nr + 1;\n>       @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n>        +\n>        +\t\tif (item->commit) {\n>        +\t\t\tctx.commit = item->commit;\n>       -+\t\t\tfor_each_decoration(item->commit,\n>       -+\t\t\t\t\t    add_branch_for_decoration,\n>       -+\t\t\t\t\t    &ctx);\n>       ++\t\t\tadd_decorations_to_list(item->commit, &ctx);\n>        +\t\t}\n>        +\t}\n>        +\n>       -+\t/* Add the \"update-refs\" step. */\n>       -+\tALLOC_GROW(ctx.items,\n>       -+\t\t   ctx.items_nr + 1,\n>       -+\t\t   ctx.items_alloc);\n>       -+\tmemset(&ctx.items[ctx.items_nr], 0, sizeof(struct todo_item));\n>       -+\tctx.items[ctx.items_nr].command = TODO_UPDATE_REFS;\n>       -+\tctx.items_nr++;\n>       -+\n>       ++\tstring_list_clear(&ctx.refs_to_oids, 1);\n>        +\tfree(todo_list->items);\n>        +\ttodo_list->items = ctx.items;\n>        +\ttodo_list->nr = ctx.items_nr;\n>       @@ sequencer.h: int complete_action(struct repository *r, struct replay_opts *opts,\n>         int todo_list_rearrange_squash(struct todo_list *todo_list);\n>         \n>        \n>       + ## t/t2407-worktree-heads.sh ##\n>       +@@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite when in error states' '\n>       + \tdone\n>       + '\n>       +\n>       ++. \"$TEST_DIRECTORY\"/lib-rebase.sh\n>       ++\n>       ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite during rebase with --update-refs' '\n>       ++\tgit commit --fixup HEAD~2 --allow-empty &&\n>       ++\t(\n>       ++\t\tset_cat_todo_editor &&\n>       ++\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n>       ++\t\t! grep \"update-refs\" todo\n>       ++\t) &&\n>       ++\tgit branch -f allow-update HEAD~2 &&\n>       ++\t(\n>       ++\t\tset_cat_todo_editor &&\n>       ++\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n>       ++\t\tgrep \"update-ref refs/heads/allow-update\" todo\n>       ++\t)\n>       ++'\n>       ++\n>       + test_done\n>       +\n>         ## t/t3404-rebase-interactive.sh ##\n>        @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correctly' '\n>         \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n>       @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correct\n>        +\n>        +\t\tcat >expect <<-EOF &&\n>        +\t\tpick $(git log -1 --format=%h J) J\n>       -+\t\tlabel for-update-refs/refs/heads/second\n>       -+\t\tlabel for-update-refs/refs/heads/first\n>       ++\t\tupdate-ref refs/heads/second\n>       ++\t\tupdate-ref refs/heads/first\n>        +\t\tpick $(git log -1 --format=%h K) K\n>        +\t\tpick $(git log -1 --format=%h L) L\n>        +\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n>       -+\t\tlabel for-update-refs/refs/heads/third\n>       ++\t\tupdate-ref refs/heads/third\n>        +\t\tpick $(git log -1 --format=%h M) M\n>       -+\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n>       -+\t\tlabel for-update-refs/refs/heads/shared-tip\n>       -+\t\tupdate-refs\n>       ++\t\tupdate-ref refs/heads/no-conflict-branch\n>       ++\t\tupdate-ref refs/heads/shared-tip\n>        +\t\tEOF\n>        +\n>        +\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n>       @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correct\n>        +\t\treset onto\n>        +\t\tpick $(git log -1 --format=%h branch2~1) F\n>        +\t\tpick $(git log -1 --format=%h branch2) I\n>       -+\t\tlabel for-update-refs/refs/heads/branch2\n>       ++\t\tupdate-ref refs/heads/branch2\n>        +\t\tlabel merge\n>        +\t\treset onto\n>        +\t\tpick $(git log -1 --format=%h refs/heads/second) J\n>       -+\t\tlabel for-update-refs/refs/heads/second\n>       -+\t\tlabel for-update-refs/refs/heads/first\n>       ++\t\tupdate-ref refs/heads/second\n>       ++\t\tupdate-ref refs/heads/first\n>        +\t\tpick $(git log -1 --format=%h refs/heads/third~1) K\n>        +\t\tpick $(git log -1 --format=%h refs/heads/third) L\n>        +\t\tfixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n>       -+\t\tlabel for-update-refs/refs/heads/third\n>       ++\t\tupdate-ref refs/heads/third\n>        +\t\tpick $(git log -1 --format=%h HEAD~2) M\n>       -+\t\tlabel for-update-refs/refs/heads/no-conflict-branch\n>       ++\t\tupdate-ref refs/heads/no-conflict-branch\n>        +\t\tmerge -C $(git log -1 --format=%h HEAD~1) merge # merge\n>       -+\t\tlabel for-update-refs/refs/heads/merge-branch\n>       -+\t\tupdate-refs\n>       ++\t\tupdate-ref refs/heads/merge-branch\n>        +\t\tEOF\n>        +\n>        +\t\ttest_must_fail git rebase -i --autosquash \\\n>   6:  68f8e51b19c ! 7:  72e0481b643 sequencer: implement 'update-refs' command\n>       @@ Metadata\n>        Author: Derrick Stolee <derrickstolee@github.com>\n>        \n>         ## Commit message ##\n>       -    sequencer: implement 'update-refs' command\n>       +    rebase: update refs from 'update-ref' commands\n>        \n>       -    The previous change allowed 'git rebase --update-refs' to create 'label'\n>       -    commands for each branch  among the commits being rewritten and add an\n>       -    'update-refs' command at the end of the todo list. Now, teach Git to\n>       -    update the refs during that final 'update-refs' command.\n>       +    The previous change introduced the 'git rebase --update-refs' option\n>       +    which added 'update-ref <ref>' commands to the todo list of an\n>       +    interactive rebase.\n>        \n>       -    We need to create an array of new and old OIDs for each ref by iterating\n>       -    over the refs/rewritten/for-update-refs/ namespace. We cannot update the\n>       -    refs in-place since this will confuse the refs iterator.\n>       +    Teach Git to record the HEAD position when reaching these 'update-ref'\n>       +    commands. The ref/OID pair is stored in the\n>       +    $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n>       +    file to avoid having other processes updating the refs in that file\n>       +    while the rebase is in progress.\n>       +\n>       +    Not only do we update the file when the sequencer reaches these\n>       +    'update-ref' commands, we then update the refs themselves at the end of\n>       +    the rebase sequence. If the rebase is aborted before this final step,\n>       +    then the refs are not updated.\n>        \n>            Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n>        \n>         ## sequencer.c ##\n>       +@@\n>       + #include \"rebase-interactive.h\"\n>       + #include \"reset.h\"\n>       + #include \"branch.h\"\n>       ++#include \"log-tree.h\"\n>       +\n>       + #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>       +\n>       +@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n>       +  */\n>       + static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n>       +\n>       ++/*\n>       ++ * The update-refs file stores a list of refs that will be updated at the end\n>       ++ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n>       ++ * update the OIDs for the refs in this file, but the refs are not updated\n>       ++ * until the end of the rebase sequence.\n>       ++ */\n>       ++static GIT_PATH_FUNC(rebase_path_update_refs, \"rebase-merge/update-refs\")\n>       ++\n>       + /*\n>       +  * The following files are written by git-rebase just after parsing the\n>       +  * command-line.\n>        @@ sequencer.c: leave_merge:\n>         \treturn ret;\n>         }\n>         \n>       --static int do_update_refs(struct repository *r)\n>       -+struct update_refs_context {\n>       -+\tstruct ref_store *refs;\n>       -+\tchar **ref_names;\n>       -+\tstruct object_id *old;\n>       -+\tstruct object_id *new;\n>       -+\tsize_t nr;\n>       -+\tsize_t alloc;\n>       -+};\n>       -+\n>       -+static int add_ref_to_context(const char *refname,\n>       -+\t\t\t      const struct object_id *oid,\n>       -+\t\t\t      int flags,\n>       -+\t\t\t      void *data)\n>       +-static int do_update_ref(struct repository *r, const char *ref_name)\n>       ++static int write_update_refs_state(struct string_list *refs_to_oids)\n>       ++{\n>       ++\tint result = 0;\n>       ++\tFILE *fp = NULL;\n>       ++\tstruct string_list_item *item;\n>       ++\tchar *path = xstrdup(rebase_path_update_refs());\n>       ++\n>       ++\tif (safe_create_leading_directories(path)) {\n>       ++\t\tresult = error(_(\"unable to create leading directories of %s\"),\n>       ++\t\t\t       path);\n>       ++\t\tgoto cleanup;\n>       ++\t}\n>       ++\n>       ++\tfp = fopen(path, \"w\");\n>       ++\tif (!fp) {\n>       ++\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n>       ++\t\tgoto cleanup;\n>       ++\t}\n>       ++\n>       ++\tfor_each_string_list_item(item, refs_to_oids)\n>       ++\t\tfprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n>       ++\n>       ++cleanup:\n>       ++\tif (fp)\n>       ++\t\tfclose(fp);\n>       ++\treturn result;\n>       ++}\n>       ++\n>       ++static int do_update_ref(struct repository *r, const char *refname)\n>         {\n>       -+\tint f = 0;\n>       -+\tconst char *name;\n>       -+\tstruct update_refs_context *ctx = data;\n>       ++\tstruct string_list_item *item;\n>       ++\tstruct string_list list = STRING_LIST_INIT_DUP;\n>       ++\tint found = 0;\n>       ++\n>       ++\tsequencer_get_update_refs_state(r->gitdir, &list);\n>        +\n>       -+\tALLOC_GROW(ctx->ref_names, ctx->nr + 1, ctx->alloc);\n>       -+\tALLOC_GROW(ctx->old, ctx->nr + 1, ctx->alloc);\n>       -+\tALLOC_GROW(ctx->new, ctx->nr + 1, ctx->alloc);\n>       ++\tfor_each_string_list_item(item, &list) {\n>       ++\t\tif (!strcmp(item->string, refname)) {\n>       ++\t\t\tstruct object_id oid;\n>       ++\t\t\tfree(item->util);\n>       ++\t\t\tfound = 1;\n>        +\n>       -+\tif (!skip_prefix(refname, \"refs/rewritten/for-update-refs/\", &name))\n>       -+\t\treturn 1;\n>       ++\t\t\tif (!read_ref(\"HEAD\", &oid)) {\n>       ++\t\t\t\titem->util = oiddup(&oid);\n>       ++\t\t\t\tbreak;\n>       ++\t\t\t}\n>       ++\t\t}\n>       ++\t}\n>        +\n>       -+\tctx->ref_names[ctx->nr] = xstrdup(name);\n>       -+\toidcpy(&ctx->new[ctx->nr], oid);\n>       -+\tif (!refs_resolve_ref_unsafe(ctx->refs, name, 0,\n>       -+\t\t\t\t     &ctx->old[ctx->nr], &f))\n>       -+\t\treturn 1;\n>       ++\tif (!found) {\n>       ++\t\tstruct object_id oid;\n>       ++\t\titem = string_list_append(&list, refname);\n>        +\n>       -+\tctx->nr++;\n>       ++\t\tif (!read_ref(\"HEAD\", &oid))\n>       ++\t\t\titem->util = oiddup(&oid);\n>       ++\t\telse\n>       ++\t\t\titem->util = oiddup(the_hash_algo->null_oid);\n>       ++\t}\n>       ++\n>       ++\twrite_update_refs_state(&list);\n>       ++\tstring_list_clear(&list, 1);\n>         \treturn 0;\n>         }\n>         \n>        +static int do_update_refs(struct repository *r)\n>        +{\n>       -+\tint i, res;\n>       -+\tstruct update_refs_context ctx = {\n>       -+\t\t.refs = get_main_ref_store(r),\n>       -+\t\t.alloc = 16,\n>       -+\t};\n>       -+\tALLOC_ARRAY(ctx.ref_names, ctx.alloc);\n>       -+\tALLOC_ARRAY(ctx.old, ctx.alloc);\n>       -+\tALLOC_ARRAY(ctx.new, ctx.alloc);\n>       -+\n>       -+\tres = refs_for_each_fullref_in(ctx.refs,\n>       -+\t\t\t\t       \"refs/rewritten/for-update-refs/\",\n>       -+\t\t\t\t       add_ref_to_context,\n>       -+\t\t\t\t       &ctx);\n>       -+\n>       -+\tfor (i = 0; !res && i < ctx.nr; i++)\n>       -+\t\tres = refs_update_ref(ctx.refs, \"rewritten during rebase\",\n>       -+\t\t\t\tctx.ref_names[i],\n>       -+\t\t\t\t&ctx.new[i], &ctx.old[i],\n>       ++\tint res = 0;\n>       ++\tstruct string_list_item *item;\n>       ++\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n>       ++\tstruct ref_store *refs = get_main_ref_store(r);\n>       ++\n>       ++\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n>       ++\n>       ++\tfor_each_string_list_item(item, &refs_to_oids) {\n>       ++\t\tstruct object_id *oid_to = item->util;\n>       ++\t\tstruct object_id oid_from;\n>       ++\n>       ++\t\tif (oideq(oid_to, the_hash_algo->null_oid)) {\n>       ++\t\t\t/*\n>       ++\t\t\t * Ref was not updated. User may have deleted the\n>       ++\t\t\t * 'update-ref' step.\n>       ++\t\t\t */\n>       ++\t\t\tcontinue;\n>       ++\t\t}\n>       ++\n>       ++\t\tif (read_ref(item->string, &oid_from)) {\n>       ++\t\t\t/*\n>       ++\t\t\t * The ref does not exist. The user probably\n>       ++\t\t\t * inserted a new 'update-ref' step with a new\n>       ++\t\t\t * branch name.\n>       ++\t\t\t */\n>       ++\t\t\toidcpy(&oid_from, the_hash_algo->null_oid);\n>       ++\t\t}\n>       ++\n>       ++\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n>       ++\t\t\t\titem->string,\n>       ++\t\t\t\toid_to, &oid_from,\n>        +\t\t\t\t0, UPDATE_REFS_MSG_ON_ERR);\n>       ++\t}\n>        +\n>       -+\tfor (i = 0; i < ctx.nr; i++)\n>       -+\t\tfree(ctx.ref_names[i]);\n>       -+\tfree(ctx.ref_names);\n>       -+\tfree(ctx.old);\n>       -+\tfree(ctx.new);\n>       ++\tstring_list_clear(&refs_to_oids, 1);\n>        +\treturn res;\n>        +}\n>        +\n>         static int is_final_fixup(struct todo_list *todo_list)\n>         {\n>         \tint i = todo_list->current;\n>       +@@ sequencer.c: cleanup_head_ref:\n>       + \t\tstrbuf_release(&head_ref);\n>       + \t}\n>       +\n>       ++\tdo_update_refs(r);\n>       ++\n>       + \t/*\n>       + \t * Sequence of picks finished successfully; cleanup by\n>       + \t * removing the .git/sequencer directory\n>       +@@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n>       + \t\t}\n>       + \t}\n>       +\n>       ++\twrite_update_refs_state(&ctx.refs_to_oids);\n>       ++\n>       + \tstring_list_clear(&ctx.refs_to_oids, 1);\n>       + \tfree(todo_list->items);\n>       + \ttodo_list->items = ctx.items;\n>        \n>         ## t/t3404-rebase-interactive.sh ##\n>        @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands with --rebase-merges' '\n>       @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands\n>        +\tgit branch -f third HEAD~1 &&\n>        +\ttest_commit extra2 fileX &&\n>        +\tgit commit --amend --fixup=L &&\n>       -+\t(\n>       -+\t\tgit rebase -i --autosquash --update-refs primary &&\n>       -+\n>       -+\t\tcompare_two_refs HEAD~3 refs/heads/first &&\n>       -+\t\tcompare_two_refs HEAD~3 refs/heads/second &&\n>       -+\t\tcompare_two_refs HEAD~1 refs/heads/third &&\n>       -+\t\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n>       -+\t)\n>       ++\n>       ++\tgit rebase -i --autosquash --update-refs primary &&\n>       ++\n>       ++\tcompare_two_refs HEAD~3 refs/heads/first &&\n>       ++\tcompare_two_refs HEAD~3 refs/heads/second &&\n>       ++\tcompare_two_refs HEAD~1 refs/heads/third &&\n>       ++\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n>        +'\n>        +\n>         # This must be the last test in this file\n>   7:  3d7d3f656b4 ! 8:  d2cfdbfc431 rebase: add rebase.updateRefs config option\n>       @@ Documentation/git-rebase.txt: start would be overridden by the presence of\n>         \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n>         \tin this way.\n>        ++\n>       -+If the `--update-refs` option is enabled by default using the\n>       -+configuration variable `rebase.updateRefs`, this option can be\n>       -+used to override and disable this setting.\n>       ++If the configuration variable `rebase.updateRefs` is set, then this option\n>       ++can be used to override and disable this setting.\n>         \n>         INCOMPATIBLE OPTIONS\n>         --------------------\n> \n"},{"id":"458405","messageId":"xmqqr1348yur.fsf@gitster.g","threadId":"57949","inReplyTo":"bee9eb82-6c6a-6467-37d1-d3797c583929@gmail.com","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-01T18:35:56Z","receivedAt":"2022-07-01T18:36:06Z","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>> +static int write_update_refs_state(struct string_list *refs_to_oids)\n>> +{\n>> +\tint result = 0;\n>> +\tFILE *fp = NULL;\n>> +\tstruct string_list_item *item;\n>> +\tchar *path = xstrdup(rebase_path_update_refs());\n>\n> This is leaked\n\nGood eyes.\n\n>> +\tif (safe_create_leading_directories(path)) {\n>> +\t\tresult = error(_(\"unable to create leading directories of %s\"),\n>> +\t\t\t       path);\n>> +\t\tgoto cleanup;\n>> +\t}\n>> +\n>> +\tfp = fopen(path, \"w\");\n>> +\tif (!fp) {\n>> +\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n>> +\t\tgoto cleanup;\n>> +\t}\n>\n> Can we use fopen_or_warn() here? It ignores ENOENT and ENOTDIR but I\n> don't think that should matter here.\n\nfopen(\"no-such-directory/file\", \"w\") would fail with ENOENT, and\nfopen(\"README.md/file\", \"w\") would fail with ENOTDIR, I would think,\nso \"should not matter because we are writing\" is not the reason, but\nbecause we know the path is in a subdirectory of \".git/\" that we\nknow should exist, the most likely reason for the fopen to fail is\nbecause (1) the repository is broken (we will get ENOENT, ENOTDIR,\nwhich we want to warn about but fopen_or_warn() ignores, as well as\nother errors such as EISDIR), (2) the repository is unwritable (we\nwill get EACCES), or (3) we are running low on diskspace (ENOSPC).\n\nI think that the fopen_or_warn() helper was primarily invented to\nread an optional file (so we deliberately ignore a failure to open\none due to ENOENT and ENOTDIR), and we should be careful of its use\nfor any other purpose, i.e. write access for any purpose and read\naccess for files that we know we should be able to.\n\n>> +compare_two_refs () {\n>> +\tgit rev-parse $1 >expect &&\n>> +\tgit rev-parse $2 >actual &&\n>> +\ttest_cmp expect actual\n>> +}\n>\n> This is just test_cmp_rev\n\nI love to see reviewers who know the existing API and helpers very\nwell (including the fopen-or-warn above).\n\nVery much appreciated.\n\n> Thanks for working on this, it will be a really useful addition to rebase.\n\nDitto.\n\nThanks.\n"},{"id":"458419","messageId":"CABPp-BFqLP51q9RkJr=16+Aqq7R=tYqL5mZGUq-dvfn8LL2AMQ@mail.gmail.com","threadId":"57949","inReplyTo":"918b398d6a2264a99acadd319f780c796bbafc18.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-01T21:20:56Z","receivedAt":"2022-07-01T21:21:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 28, 2022 at 6:26 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> When working on a large feature, it can be helpful to break that feature\n> into multiple smaller parts that become reviewed in sequence. During\n> development or during review, a change to one part of the feature could\n> affect multiple of these parts. An interactive rebase can help adjust\n> the multi-part \"story\" of the branch.\n>\n> However, if there are branches tracking the different parts of the\n> feature, then rebasing the entire list of commits can create commits not\n> reachable from those \"sub branches\". It can take a manual step to update\n> those branches.\n>\n> Add a new --update-refs option to 'git rebase -i' that adds 'update-ref\n> <ref>' steps to the todo file whenever a commit that is being rebased is\n> decorated with that <ref>. At the very end, the rebase process updates\n> all of the listed refs to the values stored during the rebase operation.\n>\n> Be sure to iterate after any squashing or fixups are placed. Update the\n> branch only after those squashes and fixups are complete. This allows a\n> --fixup commit at the tip of the feature to apply correctly to the sub\n> branch, even if it is fixing up the most-recent commit in that part.\n>\n> One potential problem here is that refs decorating commits that are\n> already marked as \"fixup!\" or \"squash!\" will not be included in this\n> list. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\n> to change the relative order of these refs, so it is not recommended.\n> The workflow here is intended to allow these kinds of commits at the tip\n> of the rebased branch while the other sub branches come along for the\n> ride without intervention.\n>\n> This change update the documentation and builtin to accept the\n> --update-refs option as well as updating the todo file with the\n> 'update-ref' commands. Tests are added to ensure that these todo\n> commands are added in the correct locations.\n>\n> A future change will update the behavior to actually update the refs\n> at the end of the rebase sequence.\n>\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>  Documentation/git-rebase.txt  |   8 +++\n>  builtin/rebase.c              |   5 ++\n>  sequencer.c                   | 107 ++++++++++++++++++++++++++++++++++\n>  sequencer.h                   |   1 +\n>  t/t2407-worktree-heads.sh     |  17 ++++++\n>  t/t3404-rebase-interactive.sh |  70 ++++++++++++++++++++++\n>  6 files changed, 208 insertions(+)\n>\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 262fb01aec0..e7611b4089c 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -609,6 +609,13 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n>  start would be overridden by the presence of\n>  `rebase.rescheduleFailedExec=true` configuration.\n>\n> +--update-refs::\n> +--no-update-refs::\n> +       Automatically force-update any branches that point to commits that\n> +       are being rebased. Any branches that are checked out in a worktree\n> +       or point to a `squash! ...` or `fixup! ...` commit are not updated\n> +       in this way.\n> +\n>  INCOMPATIBLE OPTIONS\n>  --------------------\n>\n> @@ -632,6 +639,7 @@ are incompatible with the following options:\n>   * --empty=\n>   * --reapply-cherry-picks\n>   * --edit-todo\n> + * --update-refs\n>   * --root when used in combination with --onto\n>\n>  In addition, the following pairs of options are incompatible:\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 7ab50cda2ad..56d82a52106 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -102,6 +102,7 @@ struct rebase_options {\n>         int reschedule_failed_exec;\n>         int reapply_cherry_picks;\n>         int fork_point;\n> +       int update_refs;\n>  };\n>\n>  #define REBASE_OPTIONS_INIT {                          \\\n> @@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n>                 ret = complete_action(the_repository, &replay, flags,\n>                         shortrevisions, opts->onto_name, opts->onto,\n>                         &opts->orig_head, &commands, opts->autosquash,\n> +                       opts->update_refs,\n>                         &todo_list);\n>         }\n>\n> @@ -1124,6 +1126,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>                 OPT_BOOL(0, \"autosquash\", &options.autosquash,\n>                          N_(\"move commits that begin with \"\n>                             \"squash!/fixup! under -i\")),\n> +               OPT_BOOL(0, \"update-refs\", &options.update_refs,\n> +                        N_(\"update local refs that point to commits \"\n> +                           \"that are being rebased\")),\n>                 { OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n>                         N_(\"GPG-sign commits\"),\n>                         PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n> diff --git a/sequencer.c b/sequencer.c\n> index 0b61835d441..915d87a0336 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -35,6 +35,7 @@\n>  #include \"commit-reach.h\"\n>  #include \"rebase-interactive.h\"\n>  #include \"reset.h\"\n> +#include \"branch.h\"\n>\n>  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>\n> @@ -5615,10 +5616,113 @@ static int skip_unnecessary_picks(struct repository *r,\n>         return 0;\n>  }\n>\n> +struct todo_add_branch_context {\n> +       struct todo_item *items;\n> +       size_t items_nr;\n> +       size_t items_alloc;\n> +       struct strbuf *buf;\n> +       struct commit *commit;\n> +       struct string_list refs_to_oids;\n> +};\n> +\n> +static int add_decorations_to_list(const struct commit *commit,\n> +                                  struct todo_add_branch_context *ctx)\n> +{\n> +       const struct name_decoration *decoration = get_name_decoration(&commit->object);\n> +\n> +       while (decoration) {\n> +               struct todo_item *item;\n> +               const char *path;\n> +               size_t base_offset = ctx->buf->len;\n> +\n> +               ALLOC_GROW(ctx->items,\n> +                       ctx->items_nr + 1,\n> +                       ctx->items_alloc);\n> +               item = &ctx->items[ctx->items_nr];\n> +               memset(item, 0, sizeof(*item));\n> +\n> +               /* If the branch is checked out, then leave a comment instead. */\n> +               if ((path = branch_checked_out(decoration->name))) {\n> +                       item->command = TODO_COMMENT;\n> +                       strbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n> +                                   decoration->name, path);\n> +               } else {\n> +                       struct string_list_item *sti;\n> +                       item->command = TODO_UPDATE_REF;\n> +                       strbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n> +\n> +                       sti = string_list_append(&ctx->refs_to_oids,\n> +                                                decoration->name);\n> +                       sti->util = oiddup(the_hash_algo->null_oid);\n> +               }\n> +\n> +               item->offset_in_buf = base_offset;\n> +               item->arg_offset = base_offset;\n> +               item->arg_len = ctx->buf->len - base_offset;\n> +               ctx->items_nr++;\n> +\n> +               decoration = decoration->next;\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n> +/*\n> + * For each 'pick' command, find out if the commit has a decoration in\n\nIs this really limited to picks?  If someone uses --autosquash and has\na fixup or squash in the list, wouldn't this apply as well, or does\nall of this apply before the transformations to fixup/squash?  Also,\nwhat if the user is doing --rebase-merges and there's a merge commit\nwith a branch pointing at the merge.   Would that be included?\n\n> + * refs/heads/. If so, then add a 'label for-update-refs/' command.\n> + */\n> +static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n> +{\n> +       int i;\n> +       static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n> +       static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n> +       static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n> +       struct decoration_filter decoration_filter = {\n> +               .include_ref_pattern = &decorate_refs_include,\n> +               .exclude_ref_pattern = &decorate_refs_exclude,\n> +               .exclude_ref_config_pattern = &decorate_refs_exclude_config,\n> +       };\n> +       struct todo_add_branch_context ctx = {\n> +               .buf = &todo_list->buf,\n> +               .refs_to_oids = STRING_LIST_INIT_DUP,\n> +       };\n> +\n> +       ctx.items_alloc = 2 * todo_list->nr + 1;\n> +       ALLOC_ARRAY(ctx.items, ctx.items_alloc);\n> +\n> +       string_list_append(&decorate_refs_include, \"refs/heads/\");\n> +       load_ref_decorations(&decoration_filter, 0);\n> +\n> +       for (i = 0; i < todo_list->nr; ) {\n> +               struct todo_item *item = &todo_list->items[i];\n> +\n> +               /* insert ith item into new list */\n> +               ALLOC_GROW(ctx.items,\n> +                          ctx.items_nr + 1,\n> +                          ctx.items_alloc);\n> +\n> +               ctx.items[ctx.items_nr++] = todo_list->items[i++];\n> +\n> +               if (item->commit) {\n> +                       ctx.commit = item->commit;\n> +                       add_decorations_to_list(item->commit, &ctx);\n> +               }\n> +       }\n> +\n> +       string_list_clear(&ctx.refs_to_oids, 1);\n> +       free(todo_list->items);\n> +       todo_list->items = ctx.items;\n> +       todo_list->nr = ctx.items_nr;\n> +       todo_list->alloc = ctx.items_alloc;\n> +\n> +       return 0;\n> +}\n> +\n>  int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n>                     const char *shortrevisions, const char *onto_name,\n>                     struct commit *onto, const struct object_id *orig_head,\n>                     struct string_list *commands, unsigned autosquash,\n> +                   unsigned update_refs,\n>                     struct todo_list *todo_list)\n>  {\n>         char shortonto[GIT_MAX_HEXSZ + 1];\n> @@ -5637,6 +5741,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>                 item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n>         }\n>\n> +       if (update_refs && todo_list_add_update_ref_commands(todo_list))\n> +               return -1;\n> +\n>         if (autosquash && todo_list_rearrange_squash(todo_list))\n>                 return -1;\n>\n> diff --git a/sequencer.h b/sequencer.h\n> index 2cf5c1b9a38..e671d7e0743 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -167,6 +167,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>                     const char *shortrevisions, const char *onto_name,\n>                     struct commit *onto, const struct object_id *orig_head,\n>                     struct string_list *commands, unsigned autosquash,\n> +                   unsigned update_refs,\n>                     struct todo_list *todo_list);\n>  int todo_list_rearrange_squash(struct todo_list *todo_list);\n>\n> diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\n> index f1e9e172a0c..203997d92c6 100755\n> --- a/t/t2407-worktree-heads.sh\n> +++ b/t/t2407-worktree-heads.sh\n> @@ -151,4 +151,21 @@ test_expect_success 'refuse to overwrite when in error states' '\n>         done\n>  '\n>\n> +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n> +\n> +test_expect_success !SANITIZE_LEAK 'refuse to overwrite during rebase with --update-refs' '\n> +       git commit --fixup HEAD~2 --allow-empty &&\n> +       (\n> +               set_cat_todo_editor &&\n> +               test_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n> +               ! grep \"update-refs\" todo\n> +       ) &&\n> +       git branch -f allow-update HEAD~2 &&\n> +       (\n> +               set_cat_todo_editor &&\n> +               test_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n> +               grep \"update-ref refs/heads/allow-update\" todo\n> +       )\n> +'\n> +\n>  test_done\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index f31afd4a547..3cd20733bc8 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1743,6 +1743,76 @@ test_expect_success 'ORIG_HEAD is updated correctly' '\n>         test_cmp_rev ORIG_HEAD test-orig-head@{1}\n>  '\n>\n> +test_expect_success '--update-refs adds label and update-ref commands' '\n> +       git checkout -b update-refs no-conflict-branch &&\n> +       git branch -f base HEAD~4 &&\n> +       git branch -f first HEAD~3 &&\n> +       git branch -f second HEAD~3 &&\n> +       git branch -f third HEAD~1 &&\n> +       git commit --allow-empty --fixup=third &&\n> +       git branch -f shared-tip &&\n> +       (\n> +               set_cat_todo_editor &&\n> +\n> +               cat >expect <<-EOF &&\n> +               pick $(git log -1 --format=%h J) J\n> +               update-ref refs/heads/second\n> +               update-ref refs/heads/first\n> +               pick $(git log -1 --format=%h K) K\n> +               pick $(git log -1 --format=%h L) L\n> +               fixup $(git log -1 --format=%h update-refs) fixup! L # empty\n> +               update-ref refs/heads/third\n> +               pick $(git log -1 --format=%h M) M\n> +               update-ref refs/heads/no-conflict-branch\n> +               update-ref refs/heads/shared-tip\n> +               EOF\n> +\n> +               test_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n> +               test_cmp expect todo\n> +       )\n> +'\n> +\n> +test_expect_success '--update-refs adds commands with --rebase-merges' '\n> +       git checkout -b update-refs-with-merge no-conflict-branch &&\n> +       git branch -f base HEAD~4 &&\n> +       git branch -f first HEAD~3 &&\n> +       git branch -f second HEAD~3 &&\n> +       git branch -f third HEAD~1 &&\n> +       git merge -m merge branch2 &&\n> +       git branch -f merge-branch &&\n> +       git commit --fixup=third --allow-empty &&\n> +       (\n> +               set_cat_todo_editor &&\n> +\n> +               cat >expect <<-EOF &&\n> +               label onto\n> +               reset onto\n> +               pick $(git log -1 --format=%h branch2~1) F\n> +               pick $(git log -1 --format=%h branch2) I\n> +               update-ref refs/heads/branch2\n> +               label merge\n> +               reset onto\n> +               pick $(git log -1 --format=%h refs/heads/second) J\n> +               update-ref refs/heads/second\n> +               update-ref refs/heads/first\n> +               pick $(git log -1 --format=%h refs/heads/third~1) K\n> +               pick $(git log -1 --format=%h refs/heads/third) L\n> +               fixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n> +               update-ref refs/heads/third\n> +               pick $(git log -1 --format=%h HEAD~2) M\n> +               update-ref refs/heads/no-conflict-branch\n> +               merge -C $(git log -1 --format=%h HEAD~1) merge # merge\n> +               update-ref refs/heads/merge-branch\n> +               EOF\n> +\n> +               test_must_fail git rebase -i --autosquash \\\n> +                                  --rebase-merges=rebase-cousins \\\n> +                                  --update-refs primary >todo &&\n> +\n> +               test_cmp expect todo\n> +       )\n> +'\n> +\n>  # This must be the last test in this file\n>  test_expect_success '$EDITOR and friends are unchanged' '\n>         test_editor_unchanged\n> --\n> gitgitgadget\n>\n"},{"id":"458427","messageId":"CABPp-BG++8LAwyH6P-QDj5Lu6-cevOBOjpdY7QsSdwiT0w9URA@mail.gmail.com","threadId":"57949","inReplyTo":"72e0481b643e98c5670eee0bf5c199c4fb693b16.1656422759.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-01T23:18:59Z","receivedAt":"2022-07-01T23:19:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 28, 2022 at 6:26 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> The previous change introduced the 'git rebase --update-refs' option\n> which added 'update-ref <ref>' commands to the todo list of an\n> interactive rebase.\n>\n> Teach Git to record the HEAD position when reaching these 'update-ref'\n> commands. The ref/OID pair is stored in the\n> $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n> file to avoid having other processes updating the refs in that file\n> while the rebase is in progress.\n>\n> Not only do we update the file when the sequencer reaches these\n> 'update-ref' commands, we then update the refs themselves at the end of\n> the rebase sequence. If the rebase is aborted before this final step,\n> then the refs are not updated.\n\nWhy update with each update-ref command?  Couldn't the values be\nstored in memory and only written when we hit a conflict?\n\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>  sequencer.c                   | 114 +++++++++++++++++++++++++++++++++-\n>  t/t3404-rebase-interactive.sh |  23 +++++++\n>  2 files changed, 136 insertions(+), 1 deletion(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index 915d87a0336..4fd1c0b5bce 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -36,6 +36,7 @@\n>  #include \"rebase-interactive.h\"\n>  #include \"reset.h\"\n>  #include \"branch.h\"\n> +#include \"log-tree.h\"\n>\n>  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>\n> @@ -148,6 +149,14 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n>   */\n>  static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n>\n> +/*\n> + * The update-refs file stores a list of refs that will be updated at the end\n> + * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n> + * update the OIDs for the refs in this file, but the refs are not updated\n> + * until the end of the rebase sequence.\n> + */\n> +static GIT_PATH_FUNC(rebase_path_update_refs, \"rebase-merge/update-refs\")\n> +\n>  /*\n>   * The following files are written by git-rebase just after parsing the\n>   * command-line.\n> @@ -4058,11 +4067,110 @@ leave_merge:\n>         return ret;\n>  }\n>\n> -static int do_update_ref(struct repository *r, const char *ref_name)\n> +static int write_update_refs_state(struct string_list *refs_to_oids)\n> +{\n> +       int result = 0;\n> +       FILE *fp = NULL;\n> +       struct string_list_item *item;\n> +       char *path = xstrdup(rebase_path_update_refs());\n> +\n> +       if (safe_create_leading_directories(path)) {\n> +               result = error(_(\"unable to create leading directories of %s\"),\n> +                              path);\n> +               goto cleanup;\n> +       }\n> +\n> +       fp = fopen(path, \"w\");\n> +       if (!fp) {\n> +               result = error_errno(_(\"could not open '%s' for writing\"), path);\n> +               goto cleanup;\n> +       }\n> +\n> +       for_each_string_list_item(item, refs_to_oids)\n> +               fprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n\nHere you are storing the ref and the new oid to move it to.  Any\nreason you don't store the previous oid for the branch?  In\nparticular, if someone hits a conflict, needs to resolve, and comes\nback some time much later and these intermediate branches have since\nmoved on, should we actually update those branches?  (And, if we do,\nshould we at least give a warning?)\n\n> +cleanup:\n> +       if (fp)\n> +               fclose(fp);\n> +       return result;\n> +}\n> +\n> +static int do_update_ref(struct repository *r, const char *refname)\n>  {\n> +       struct string_list_item *item;\n> +       struct string_list list = STRING_LIST_INIT_DUP;\n> +       int found = 0;\n> +\n> +       sequencer_get_update_refs_state(r->gitdir, &list);\n> +\n> +       for_each_string_list_item(item, &list) {\n> +               if (!strcmp(item->string, refname)) {\n> +                       struct object_id oid;\n> +                       free(item->util);\n> +                       found = 1;\n> +\n> +                       if (!read_ref(\"HEAD\", &oid)) {\n> +                               item->util = oiddup(&oid);\n> +                               break;\n> +                       }\n> +               }\n> +       }\n> +\n> +       if (!found) {\n> +               struct object_id oid;\n> +               item = string_list_append(&list, refname);\n> +\n> +               if (!read_ref(\"HEAD\", &oid))\n> +                       item->util = oiddup(&oid);\n> +               else\n> +                       item->util = oiddup(the_hash_algo->null_oid);\n\nSeems a little unfortunate to not use a cached value from the latest\ncommit we picked (and wrote to HEAD) and instead need to re-read HEAD.\nBut, I guess sequencer is written to round-trip through files, so\nmaybe optimizing this here doesn't make sense given all the other\nplaces in sequencer where we do lots of file reading and writing.\n\n> +       }\n> +\n> +       write_update_refs_state(&list);\n> +       string_list_clear(&list, 1);\n>         return 0;\n>  }\n>\n> +static int do_update_refs(struct repository *r)\n> +{\n> +       int res = 0;\n> +       struct string_list_item *item;\n> +       struct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n> +       struct ref_store *refs = get_main_ref_store(r);\n> +\n> +       sequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n> +\n> +       for_each_string_list_item(item, &refs_to_oids) {\n> +               struct object_id *oid_to = item->util;\n> +               struct object_id oid_from;\n> +\n> +               if (oideq(oid_to, the_hash_algo->null_oid)) {\n> +                       /*\n> +                        * Ref was not updated. User may have deleted the\n> +                        * 'update-ref' step.\n> +                        */\n> +                       continue;\n> +               }\n> +\n> +               if (read_ref(item->string, &oid_from)) {\n> +                       /*\n> +                        * The ref does not exist. The user probably\n> +                        * inserted a new 'update-ref' step with a new\n> +                        * branch name.\n> +                        */\n> +                       oidcpy(&oid_from, the_hash_algo->null_oid);\n> +               }\n> +\n> +               res |= refs_update_ref(refs, \"rewritten during rebase\",\n> +                               item->string,\n> +                               oid_to, &oid_from,\n> +                               0, UPDATE_REFS_MSG_ON_ERR);\n> +       }\n> +\n> +       string_list_clear(&refs_to_oids, 1);\n> +       return res;\n> +}\n> +\n>  static int is_final_fixup(struct todo_list *todo_list)\n>  {\n>         int i = todo_list->current;\n> @@ -4580,6 +4688,8 @@ cleanup_head_ref:\n>                 strbuf_release(&head_ref);\n>         }\n>\n> +       do_update_refs(r);\n> +\n>         /*\n>          * Sequence of picks finished successfully; cleanup by\n>          * removing the .git/sequencer directory\n> @@ -5709,6 +5819,8 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n>                 }\n>         }\n>\n> +       write_update_refs_state(&ctx.refs_to_oids);\n> +\n>         string_list_clear(&ctx.refs_to_oids, 1);\n>         free(todo_list->items);\n>         todo_list->items = ctx.items;\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 3cd20733bc8..63a69bc073e 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1813,6 +1813,29 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n>         )\n>  '\n>\n> +compare_two_refs () {\n> +       git rev-parse $1 >expect &&\n> +       git rev-parse $2 >actual &&\n> +       test_cmp expect actual\n> +}\n> +\n> +test_expect_success '--update-refs updates refs correctly' '\n> +       git checkout -B update-refs no-conflict-branch &&\n> +       git branch -f base HEAD~4 &&\n> +       git branch -f first HEAD~3 &&\n> +       git branch -f second HEAD~3 &&\n> +       git branch -f third HEAD~1 &&\n> +       test_commit extra2 fileX &&\n> +       git commit --amend --fixup=L &&\n> +\n> +       git rebase -i --autosquash --update-refs primary &&\n> +\n> +       compare_two_refs HEAD~3 refs/heads/first &&\n> +       compare_two_refs HEAD~3 refs/heads/second &&\n> +       compare_two_refs HEAD~1 refs/heads/third &&\n> +       compare_two_refs HEAD refs/heads/no-conflict-branch\n> +'\n> +\n>  # This must be the last test in this file\n>  test_expect_success '$EDITOR and friends are unchanged' '\n>         test_editor_unchanged\n> --\n> gitgitgadget\n>\n"},{"id":"458445","messageId":"7dd69bb1-8bc7-3ae9-6265-bdba660b4c4a@github.com","threadId":"57949","inReplyTo":"CABPp-BFqLP51q9RkJr=16+Aqq7R=tYqL5mZGUq-dvfn8LL2AMQ@mail.gmail.com","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-04T12:56:23Z","receivedAt":"2022-07-04T12:56:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/1/22 3:20 PM, Elijah Newren wrote:\n>> +/*\n>> + * For each 'pick' command, find out if the commit has a decoration in\n> Is this really limited to picks?  If someone uses --autosquash and has\n> a fixup or squash in the list, wouldn't this apply as well, or does\n> all of this apply before the transformations to fixup/squash?  Also,\n> what if the user is doing --rebase-merges and there's a merge commit\n> with a branch pointing at the merge.   Would that be included?\n> \n>> + * refs/heads/. If so, then add a 'label for-update-refs/' command.\n>> + */\n\nThis is limited to picks (for now) mostly for the reason that the\nfixup! and squash! commits are probably getting reordered, even if\nthey exist at the tip of some refs.\n\nThe workflow I am optimizing for is to create fixup! and squash!\ncommits at the tip of a long multi-topic series, and then those\nget reordered into the list. In that case, the only ref that is\npointing at one of those commits is also the HEAD branch, so that\nis not updated using this mechanism.\n\nThis is a case where I'm very interested in alternatives here, and\nmaybe I should clearly mark this option as experimental so we can\nwork out cases like this based on use.\n\nThanks,\n-Stolee\n"},{"id":"458446","messageId":"5002269d-5cab-3ba4-b99e-dca0c3615118@github.com","threadId":"57949","inReplyTo":"CABPp-BG++8LAwyH6P-QDj5Lu6-cevOBOjpdY7QsSdwiT0w9URA@mail.gmail.com","subject":"Re: [PATCH v3 7/8] rebase: update refs from 'update-ref' commands","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-04T13:05:03Z","receivedAt":"2022-07-04T13:05:11Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/1/22 5:18 PM, Elijah Newren wrote:\n> On Tue, Jun 28, 2022 at 6:26 AM Derrick Stolee via GitGitGadget\n\n>> Not only do we update the file when the sequencer reaches these\n>> 'update-ref' commands, we then update the refs themselves at the end of\n>> the rebase sequence. If the rebase is aborted before this final step,\n>> then the refs are not updated.\n> \n> Why update with each update-ref command?  Couldn't the values be\n> stored in memory and only written when we hit a conflict?\n\nI think that is a natural way to optimize the feature. I didn't\nlook into that option as it seemed more error-prone to me. I'd\nbe happy to be wrong here if an easy tweak makes this possible.\n\n>> -static int do_update_ref(struct repository *r, const char *ref_name)\n>> +static int write_update_refs_state(struct string_list *refs_to_oids)\n>> +{\n>> +       int result = 0;\n>> +       FILE *fp = NULL;\n>> +       struct string_list_item *item;\n>> +       char *path = xstrdup(rebase_path_update_refs());\n>> +\n>> +       if (safe_create_leading_directories(path)) {\n>> +               result = error(_(\"unable to create leading directories of %s\"),\n>> +                              path);\n>> +               goto cleanup;\n>> +       }\n>> +\n>> +       fp = fopen(path, \"w\");\n>> +       if (!fp) {\n>> +               result = error_errno(_(\"could not open '%s' for writing\"), path);\n>> +               goto cleanup;\n>> +       }\n>> +\n>> +       for_each_string_list_item(item, refs_to_oids)\n>> +               fprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n> \n> Here you are storing the ref and the new oid to move it to.  Any\n> reason you don't store the previous oid for the branch?  In\n> particular, if someone hits a conflict, needs to resolve, and comes\n> back some time much later and these intermediate branches have since\n> moved on, should we actually update those branches?  (And, if we do,\n> should we at least give a warning?)\n\nThe branches don't move forward because other Git processes respect\nthe update-refs file (this does not take into account third-party\ntools that don't know about that file, but we need to start somewhere).\n\nYou're right that storing the old value would help us in this case\nwhere another tool updated the ref while we were in the middle of the\nrebase. The storage of the null OID is only to prevent writing over a\nref when the user has removed the 'update-ref <ref>' command from the\ntodo-list. It's probably better to remove the ref from the list when\nthat happens.\n\n>> +       if (!found) {\n>> +               struct object_id oid;\n>> +               item = string_list_append(&list, refname);\n>> +\n>> +               if (!read_ref(\"HEAD\", &oid))\n>> +                       item->util = oiddup(&oid);\n>> +               else\n>> +                       item->util = oiddup(the_hash_algo->null_oid);\n> \n> Seems a little unfortunate to not use a cached value from the latest\n> commit we picked (and wrote to HEAD) and instead need to re-read HEAD.\n> But, I guess sequencer is written to round-trip through files, so\n> maybe optimizing this here doesn't make sense given all the other\n> places in sequencer where we do lots of file reading and writing.\n\nIt's also possible to optimize several things, but I tried to\nminimize the amount of change to the existing methods. This is\nmy first foray into this part of the code, so I don't always know\nwhich information is readily available. I appreciate your attention\nhere.\n\nThanks,\n-Stolee\n"},{"id":"458452","messageId":"CABPp-BHOSHYLEpH_222X_Pv_hZeDwy10zLejrVgwgf6W-pFVyw@mail.gmail.com","threadId":"57949","inReplyTo":"7dd69bb1-8bc7-3ae9-6265-bdba660b4c4a@github.com","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-04T17:57:47Z","receivedAt":"2022-07-04T17:58:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jul 4, 2022 at 5:56 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 7/1/22 3:20 PM, Elijah Newren wrote:\n> >> +/*\n> >> + * For each 'pick' command, find out if the commit has a decoration in\n> > Is this really limited to picks?  If someone uses --autosquash and has\n> > a fixup or squash in the list, wouldn't this apply as well, or does\n> > all of this apply before the transformations to fixup/squash?  Also,\n> > what if the user is doing --rebase-merges and there's a merge commit\n> > with a branch pointing at the merge.   Would that be included?\n> >\n> >> + * refs/heads/. If so, then add a 'label for-update-refs/' command.\n> >> + */\n>\n> This is limited to picks (for now) mostly for the reason that the\n> fixup! and squash! commits are probably getting reordered, even if\n> they exist at the tip of some refs.\n>\n> The workflow I am optimizing for is to create fixup! and squash!\n> commits at the tip of a long multi-topic series, and then those\n> get reordered into the list. In that case, the only ref that is\n> pointing at one of those commits is also the HEAD branch, so that\n> is not updated using this mechanism.\n>\n> This is a case where I'm very interested in alternatives here, and\n> maybe I should clearly mark this option as experimental so we can\n> work out cases like this based on use.\n\nSo my question was that if I had the following `git log --oneline\norigin/main..` output:\n\n    555555 (HEAD -> branch2) fixup! second commit\n    444444 fourth commit\n    333333 third commit\n    222222 (branch1) second commit\n    111111 first commit\n\nand I run `git rebase -i --autosquash --update-refs origin/main`, what\nwould I get?  Is branch1 updated to the rebase of 222222, or does it\ninclude the amending from squashing 555555 into the rebased 222222?\nDoes branch2 get \"left behind\" because it wasn't a pick commit at the\ntip of history, leading us to not update branch2 at all?  Or does it\nget correctly pointed at the rebased version of 444444?\n\nActually, I checked out ds/rebase-update-ref just now to try it, and\nit seems like it does the right thing:\n\n    pick 111111 first commit\n    pick 222222 second commit\n    fixup 555555 fixup! second commit\n    update-ref refs/heads/branch1\n\n    pick 333333 third commit\n    pick 444444 fourth commit\n    # Ref refs/heads/branch2 checked out at '...'\n\nThe last line was very disorienting to me at first and made me think\nwe had a bug, but the update-refs stuff is built on top of the normal\nrebase mechanism and branch2 will be updated by that logic rather than\nby the special update-refs handling.  If I add another branch with a\nfew commits on top of branch2, then branch2 is indeed updated and\nafter the pick of 444444 (and the additional branch, say branch3,\nwould be updated by the normal rebase logic instead of by the\nupdate-refs handling).  So it all works correctly, but users might get\nworried or confused along the way wondering whether it will function\ncorrectly.\n\nAnother part that users might find disorienting is that at the end,\nthe rebase reports:\n    Successfully rebased and updated refs/heads/branch2.\nwhich is correct but totally ignores the fact that it *also* rebased\nand updated other branches.\n"},{"id":"458496","messageId":"84da5ea6-b6a9-7756-66f4-a3832144a68e@github.com","threadId":"57949","inReplyTo":"CABPp-BHOSHYLEpH_222X_Pv_hZeDwy10zLejrVgwgf6W-pFVyw@mail.gmail.com","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-05T22:22:50Z","receivedAt":"2022-07-05T22:22:58Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/4/22 11:57 AM, Elijah Newren wrote:\n> Actually, I checked out ds/rebase-update-ref just now to try it, and\n> it seems like it does the right thing:\n> \n>     pick 111111 first commit\n>     pick 222222 second commit\n>     fixup 555555 fixup! second commit\n>     update-ref refs/heads/branch1\n> \n>     pick 333333 third commit\n>     pick 444444 fourth commit\n\nThanks for trying it out! This is definitely the main goal of the\nfeature, although it is also helpful when resolving conflicts or\ndoing 'edit' steps.\n\n>     # Ref refs/heads/branch2 checked out at '...'\n> \n> The last line was very disorienting to me at first and made me think\n> we had a bug, but the update-refs stuff is built on top of the normal\n> rebase mechanism and branch2 will be updated by that logic rather than\n> by the special update-refs handling.  If I add another branch with a\n> few commits on top of branch2, then branch2 is indeed updated and\n> after the pick of 444444 (and the additional branch, say branch3,\n> would be updated by the normal rebase logic instead of by the\n> update-refs handling).  So it all works correctly, but users might get\n> worried or confused along the way wondering whether it will function\n> correctly.\n\nI'll add a patch that removes the comment in the case of the HEAD\nref. Thanks for the idea!\n\n> Another part that users might find disorienting is that at the end,\n> the rebase reports:\n>     Successfully rebased and updated refs/heads/branch2.\n> which is correct but totally ignores the fact that it *also* rebased\n> and updated other branches.\n\nGood point. I can add an extra message at the end (as well as a\nwarning for any refs that did not properly update at the end).\n\nThanks,\n-Stolee\n"},{"id":"458623","messageId":"CABPp-BFQbG-qQK2biKMV7cE641BzNwk324Gn5eEFUyiz7k4c4g@mail.gmail.com","threadId":"57949","inReplyTo":"84da5ea6-b6a9-7756-66f4-a3832144a68e@github.com","subject":"Re: [PATCH v3 6/8] rebase: add --update-refs option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-08T02:27:04Z","receivedAt":"2022-07-08T02:27:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 5, 2022 at 3:22 PM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 7/4/22 11:57 AM, Elijah Newren wrote:\n> > Actually, I checked out ds/rebase-update-ref just now to try it, and\n> > it seems like it does the right thing:\n> >\n> >     pick 111111 first commit\n> >     pick 222222 second commit\n> >     fixup 555555 fixup! second commit\n> >     update-ref refs/heads/branch1\n> >\n> >     pick 333333 third commit\n> >     pick 444444 fourth commit\n>\n> Thanks for trying it out! This is definitely the main goal of the\n> feature, although it is also helpful when resolving conflicts or\n> doing 'edit' steps.\n>\n> >     # Ref refs/heads/branch2 checked out at '...'\n> >\n> > The last line was very disorienting to me at first and made me think\n> > we had a bug, but the update-refs stuff is built on top of the normal\n> > rebase mechanism and branch2 will be updated by that logic rather than\n> > by the special update-refs handling.  If I add another branch with a\n> > few commits on top of branch2, then branch2 is indeed updated and\n> > after the pick of 444444 (and the additional branch, say branch3,\n> > would be updated by the normal rebase logic instead of by the\n> > update-refs handling).  So it all works correctly, but users might get\n> > worried or confused along the way wondering whether it will function\n> > correctly.\n>\n> I'll add a patch that removes the comment in the case of the HEAD\n> ref. Thanks for the idea!\n\nThanks, that'd improve things.  I'm curious whether it'd be even\nbetter to have an update-ref line for HEAD, so that users don't wonder\nwhether it's omitted from the updates.  (That would leave an open\nquestion whether you filter out HEAD before actually calling\nupdate-ref, or use some other mechanism to make it all work behind the\nscenes.)\n\n> > Another part that users might find disorienting is that at the end,\n> > the rebase reports:\n> >     Successfully rebased and updated refs/heads/branch2.\n> > which is correct but totally ignores the fact that it *also* rebased\n> > and updated other branches.\n>\n> Good point. I can add an extra message at the end (as well as a\n> warning for any refs that did not properly update at the end).\n\nSounds good.\n"},{"id":"458872","messageId":"9e53a27017ac18eefef2c077a905efecab07861d.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 01/12] t2407: test bisect and rebase as black-boxes","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:06:54Z","receivedAt":"2022-07-12T13:07:15Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe tests added by d2ba271aad0 (branch: check for bisects and rebases,\n2022-06-14) modified hidden state to verify the branch_checked_out()\nhelper. While this indeed checks that the method implementation is _as\ndesigned_, it doesn't show that it is _correct_. Specifically, if 'git\nbisect' or 'git rebase' change their back-end for preserving refs, then\nthese tests do not demonstrate that drift as a bug in\nbranch_checked_out().\n\nModify the tests in t2407 to actually rely on a paused bisect or rebase.\nThis requires adding the !SANITIZE_LEAK prereq for tests using those\nbuiltins. The logic is still tested for leaks in the final test which\ndoes set up that back-end directly for an error state that should not be\npossible using Git commands.\n\nReported-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n t/t2407-worktree-heads.sh | 57 +++++++++++++++++++++------------------\n 1 file changed, 31 insertions(+), 26 deletions(-)\n\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex b6be42f74a2..100ab286d5c 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -7,13 +7,18 @@ TEST_PASSES_SANITIZE_LEAK=true\n \n test_expect_success 'setup' '\n \ttest_commit init &&\n-\tgit branch -f fake-1 &&\n-\tgit branch -f fake-2 &&\n \n \tfor i in 1 2 3 4\n \tdo\n+\t\tgit checkout -b conflict-$i &&\n+\t\techo \"not I\" >$i.t &&\n+\t\tgit add $i.t &&\n+\t\tgit commit -m \"will conflict\" &&\n+\n+\t\tgit checkout - &&\n \t\ttest_commit $i &&\n \t\tgit branch wt-$i &&\n+\t\tgit branch fake-$i &&\n \t\tgit worktree add wt-$i wt-$i || return 1\n \tdone &&\n \n@@ -44,26 +49,26 @@ test_expect_success 'refuse to overwrite: checked out in worktree' '\n \tdone\n '\n \n-test_expect_success 'refuse to overwrite: worktree in bisect' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in bisect' '\n+\ttest_when_finished git -C wt-4 bisect reset &&\n \n-\ttouch .git/worktrees/wt-4/BISECT_LOG &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&\n+\t# Set up a bisect so HEAD no longer points to wt-4.\n+\tgit -C wt-4 bisect start &&\n+\tgit -C wt-4 bisect bad wt-4 &&\n+\tgit -C wt-4 bisect good wt-1 &&\n \n-\ttest_must_fail git branch -f fake-2 HEAD 2>err &&\n-\tgrep \"cannot force update the branch '\\''fake-2'\\'' checked out at.*wt-4\" err\n+\ttest_must_fail git branch -f wt-4 HEAD 2>err &&\n+\tgrep \"cannot force update the branch '\\''wt-4'\\'' checked out at.*wt-4\" err\n '\n \n-test_expect_success 'refuse to overwrite: worktree in rebase' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase' '\n+\ttest_when_finished git -C wt-2 rebase --abort &&\n \n-\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n-\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n-\techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&\n+\t# This will fail part-way through due to a conflict.\n+\ttest_must_fail git -C wt-2 rebase conflict-2 &&\n \n-\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n-\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n+\ttest_must_fail git branch -f wt-2 HEAD 2>err &&\n+\tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n '\n \n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n@@ -77,24 +82,24 @@ test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n '\n \n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in bisect' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&\n+\ttest_when_finished git -C wt-4 bisect reset &&\n \n-\ttouch .git/worktrees/wt-4/BISECT_LOG &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&\n+\t# Set up a bisect so HEAD no longer points to wt-4.\n+\tgit -C wt-4 bisect start &&\n+\tgit -C wt-4 bisect bad wt-4 &&\n+\tgit -C wt-4 bisect good wt-1 &&\n \n-\ttest_must_fail git fetch server +refs/heads/fake-2:refs/heads/fake-2 2>err &&\n+\ttest_must_fail git fetch server +refs/heads/wt-4:refs/heads/wt-4 2>err &&\n \tgrep \"refusing to fetch into branch\" err\n '\n \n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n+\ttest_when_finished git -C wt-3 rebase --abort &&\n \n-\tmkdir -p .git/worktrees/wt-4/rebase-merge &&\n-\ttouch .git/worktrees/wt-4/rebase-merge/interactive &&\n-\techo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/head-name &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/onto &&\n+\t# This will fail part-way through due to a conflict.\n+\ttest_must_fail git -C wt-3 rebase conflict-3 &&\n \n-\ttest_must_fail git fetch server +refs/heads/fake-1:refs/heads/fake-1 2>err &&\n+\ttest_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&\n \tgrep \"refusing to fetch into branch\" err\n '\n \n-- \ngitgitgadget\n\n"},{"id":"458873","messageId":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v3.git.1656422759.gitgitgadget@gmail.com","subject":"[PATCH v4 00/12] rebase: update branches in multi-part topic","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:06:53Z","receivedAt":"2022-07-12T13:07:16Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This series is based on ds/branch-checked-out.\n\nThis is a feature I've wanted for quite a while. When working on the sparse\nindex topic, I created a long RFC that actually broke into three topics for\nfull review upstream. These topics were sequential, so any feedback on an\nearlier one required updates to the later ones. I would work on the full\nfeature and use interactive rebase to update the full list of commits.\nHowever, I would need to update the branches pointing to those sub-topics.\n\nThis series adds a new --update-refs option to 'git rebase' (along with a\nrebase.updateRefs config option) that adds 'update-ref' commands into the\nTODO list. This is powered by the commit decoration machinery.\n\nAs an example, here is my in-progress bundle URI RFC split into subtopics as\nthey appear during the TODO list of a git rebase -i --update-refs:\n\npick 2d966282ff3 docs: document bundle URI standard\npick 31396e9171a remote-curl: add 'get' capability\npick 54c6ab70f67 bundle-uri: create basic file-copy logic\npick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\npick 6adaf842684 fetch: add --bundle-uri option\npick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\nupdate-ref refs/heads/bundle-redo/fetch\n\npick 1e3f6546632 clone: add --bundle-uri option\npick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\nupdate-ref refs/heads/bundle-redo/clone\n\npick 5451cb6599c bundle-uri: create bundle_list struct and helpers\npick 3029c3aca15 bundle-uri: create base key-value pair parsing\npick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\npick 92625a47673 bundle-uri: unit test \"key=value\" parsing\npick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\npick 9d6809a8d53 bundle-uri: parse bundle list in config format\npick 287a732b54c bundle-uri: fetch a list of bundles\nupdate-ref refs/heads/bundle-redo/list\n\npick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\npick 520204dcd1c bundle-uri client: add minimal NOOP client\npick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\npick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\npick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\npick caf4599a81d bundle-uri: allow relative URLs in bundle lists\npick df255000b7e bundle-uri: download bundles from an advertised list\npick d71beabf199 clone: unbundle the advertised bundles\npick c9578391976 t5601: basic bundle URI tests\n# Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n\nupdate-ref refs/heads/bundle-redo/advertise\n\n\nHere is an outline of the series:\n\n * Patch 1 updates some tests for branch_checked_out() to use 'git bisect'\n   and 'git rebase' as black-boxes instead of manually editing files inside\n   $GIT_DIR. (Thanks, Junio!)\n * Patch 2 updates some tests for branch_checked_out() for the 'apply'\n   backend.\n * Patch 3 updates branch_checked_out() to parse the\n   rebase-merge/update-refs file to block concurrent ref updates and\n   checkouts on branches selected by --update-refs.\n * Patch 4 updates the todo list documentation to remove some unnecessary\n   dots in the 'merge' command. This makes it consistent with the 'fixup'\n   command before we document the 'update-ref' command.\n * Patch 5 updates the definition of todo_command_info to use enum values as\n   array indices.\n * Patches 6-8 implement the --update-refs logic itself.\n * Patch 9 specifically updates the update-refs file every time the user\n   edits the todo-list (Thanks Phillip!)\n * Patch 10 adds the rebase.updateRefs config option similar to\n   rebase.autoSquash.\n * Patch 11 ignores the HEAD ref when creating the todo list instead of\n   making a comment (Thanks Elijah!)\n * Patch 12 adds messaging to the end of the rebase stating which refs were\n   updated (Thanks Elijah!)\n\n\nUpdates in v4\n=============\n\nThis version took longer than I'd hoped (I had less time to work on it than\nanticipated) but it also has some major updates. These major updates are\ndirect responses to the significant review this series has received. Thank\nyou!\n\n * The update-refs file now stores \"ref/before/after\" triples (still\n   separated by lines). This allows us to store the \"before\" OID of a ref in\n   addition to the \"after\" that we will write to that ref at the end of the\n   rebase. This allows us to do a \"force-with-lease\" update. The\n   branch_checked_out() updates should prevent Git from updating those refs\n   while under the rebase, but older versions and third-party tools don't\n   have that protection.\n * The update-refs file is updated with every update to the todo-list file.\n   This allows for some advanced changes to the file, including removing,\n   adding, and duplicating 'update-ref' commands.\n * The message at the end of the rebase process now lists which refs were\n   updated with the update-ref steps. This includes any ref updates that\n   fail.\n * The branch_checked_out() tests now use 'git bisect' and 'git rebase' as\n   black-boxes instead of testing their internals directly.\n\nHere are the more minor updates:\n\n * Dropped an unnecessary stat() call.\n * Updated commit messages to include extra details, based on confusion in\n   last round.\n * The HEAD branch no longer appears as a comment line in the initial todo\n   list.\n * The update-refs file is now written using a lockfile.\n * Tests now use test_cmp_rev.\n * A memory leak ('path' variable) is resolved.\n\n\nUpdates in v3\n=============\n\n * The branch_checked_out() API was extracted to its own topic and is now\n   the ds/branch-checked-out branch. This series is now based on that one.\n * The for_each_decoration() API was removed, since it became trivial once\n   it did not take a commit directly.\n * The branch_checked_out() tests did not verify the rebase-apply data (for\n   the apply backend), so that is fixed.\n * Instead of using the 'label' command and a final 'update-refs' command in\n   the todo list, use a new 'update-ref ' command. This command updates the\n   rebase-merge/update-refs file with the OID of HEAD at these steps. At the\n   very end of the rebase sequence, those refs are updated to the stored OID\n   values (assuming that they were not removed by the user, in which case we\n   notice that the OID is the null OID and we do nothing).\n * New tests are added.\n * The todo-list comment documentation has some new formatting updates, but\n   also includes a description of 'update-refs' in this version.\n\n\nUpdates in v2\n=============\n\nAs recommended by the excellent feedback, I have removed the 'exec' commands\nin favor of the 'label' commands and a new 'update-refs' command at the very\nend. This way, there is only one step that updates all of the refs at the\nend instead of updating refs during the rebase. If a user runs 'git rebase\n--abort' in the middle, then their refs are still where they need to be.\n\nBased on some of the discussion, it seemed like one way to do this would be\nto have an 'update-ref ' command that would take the place of these 'label'\ncommands. However, this would require two things that make it a bit awkward:\n\n 1. We would need to replicate the storage of those positions during the\n    rebase. 'label' already does this pretty well. I've added the\n    \"for-update-refs/\" label to help here.\n 2. If we want to close out all of the refs as the rebase is finishing, then\n    that \"step\" becomes invisible to the user (and a bit more complicated to\n    insert). Thus, the 'update-refs' step performs this action. If the user\n    wants to do things after that step, then they can do so by editing the\n    TODO list.\n\nOther updates:\n\n * The 'keep_decorations' parameter was renamed to 'update_refs'.\n * I added tests for --rebase-merges=rebase-cousins to show how these labels\n   interact with other labels and merge commands.\n * I changed the order of the insertion of these update-refs labels to be\n   before the fixups are rearranged. This fixes a bug where the tip commit\n   is a fixup! so its decorations are never inspected (and they would be in\n   the wrong place even if they were). The fixup! commands are properly\n   inserted between a pick and its following label command. Tests\n   demonstrate this is correct.\n * Numerous style choices are updated based on feedback.\n\nThank you for all of the detailed review and ideas in this space. I\nappreciate any more ideas that can make this feature as effective as it can\nbe.\n\nThanks, -Stolee\n\nDerrick Stolee (12):\n  t2407: test bisect and rebase as black-boxes\n  t2407: test branches currently using apply backend\n  branch: consider refs under 'update-refs'\n  rebase-interactive: update 'merge' description\n  sequencer: define array with enum values\n  sequencer: add update-ref command\n  rebase: add --update-refs option\n  rebase: update refs from 'update-ref' commands\n  sequencer: rewrite update-refs as user edits todo list\n  rebase: add rebase.updateRefs config option\n  sequencer: ignore HEAD ref under --update-refs\n  sequencer: notify user of --update-refs activity\n\n Documentation/config/rebase.txt |   3 +\n Documentation/git-rebase.txt    |  11 +\n branch.c                        |  13 +\n builtin/rebase.c                |  10 +\n rebase-interactive.c            |  15 +-\n sequencer.c                     | 469 +++++++++++++++++++++++++++++++-\n sequencer.h                     |  23 ++\n t/lib-rebase.sh                 |  15 +\n t/t2407-worktree-heads.sh       | 103 +++++--\n t/t3404-rebase-interactive.sh   | 269 ++++++++++++++++++\n 10 files changed, 887 insertions(+), 44 deletions(-)\n\n\nbase-commit: 9bef0b1e6ec371e786c2fba3edcc06ad040a536c\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1247\n\nRange-diff vs v3:\n\n  -:  ----------- >  1:  9e53a27017a t2407: test bisect and rebase as black-boxes\n  1:  fbaedc7f1f0 !  2:  540a3be256f t2407: test branches currently using apply backend\n     @@ Commit message\n          Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n      \n       ## t/t2407-worktree-heads.sh ##\n     -@@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite: worktree in bisect' '\n     - \tgrep \"cannot force update the branch '\\''fake-2'\\'' checked out at.*wt-4\" err\n     +@@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in bisect' '\n     + \tgrep \"cannot force update the branch '\\''wt-4'\\'' checked out at.*wt-4\" err\n       '\n       \n     --test_expect_success 'refuse to overwrite: worktree in rebase' '\n     -+test_expect_success 'refuse to overwrite: worktree in rebase (apply)' '\n     -+\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-apply &&\n     +-test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase' '\n     ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (apply)' '\n     ++\ttest_when_finished git -C wt-2 rebase --abort &&\n      +\n     -+\tmkdir -p .git/worktrees/wt-3/rebase-apply &&\n     -+\techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-apply/head-name &&\n     -+\techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-apply/onto &&\n     ++\t# This will fail part-way through due to a conflict.\n     ++\ttest_must_fail git -C wt-2 rebase --apply conflict-2 &&\n      +\n     -+\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n     -+\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n     ++\ttest_must_fail git branch -f wt-2 HEAD 2>err &&\n     ++\tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n      +'\n      +\n     -+test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n     - \ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n     ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (merge)' '\n     + \ttest_when_finished git -C wt-2 rebase --abort &&\n       \n     - \tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n     + \t# This will fail part-way through due to a conflict.\n  2:  2bc647b6fcd !  3:  bf301a054e3 branch: consider refs under 'update-refs'\n     @@ Commit message\n          branch_checked_out().\n      \n          The data store is a plaintext file inside the 'rebase-merge' directory\n     -    for that worktree. The file alternates refnames and OIDs. The OIDs will\n     -    be used to store the to-be-written values as the rebase progresses, but\n     -    can be ignored at the moment.\n     +    for that worktree. The file lists refnames followed by two OIDs, each on\n     +    separate lines. The OIDs will be used to store the original values of\n     +    the refs and the to-be-written values as the rebase progresses, but can\n     +    be ignored at the moment.\n      \n          Create a new sequencer_get_update_refs_state() method that parses this\n          file and populates a struct string_list with the ref-OID pairs. We can\n     @@ Commit message\n          method.\n      \n          We can test that this works without having Git write this file by\n     -    artificially creating one in our test script.\n     +    artificially creating one in our test script, at least until 'git rebase\n     +    --update-refs' is implemented and we can use it directly.\n      \n          Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n      \n     @@ branch.c: static void prepare_checked_out_branches(void)\n       \tfree_worktrees(worktrees);\n      \n       ## sequencer.c ##\n     +@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n     +  */\n     + static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n     + \n     ++/*\n     ++ * The update-refs file stores a list of refs that will be updated at the end\n     ++ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n     ++ * update the OIDs for the refs in this file, but the refs are not updated\n     ++ * until the end of the rebase sequence.\n     ++ *\n     ++ * rebase_path_update_refs() returns the path to this file for a given\n     ++ * worktree directory. For the current worktree, pass the_repository->gitdir.\n     ++ */\n     ++static char *rebase_path_update_refs(const char *wt_dir)\n     ++{\n     ++\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n     ++}\n     ++\n     + /*\n     +  * The following files are written by git-rebase just after parsing the\n     +  * command-line.\n     +@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, \"rebase-merge/no-res\n     + static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n     + static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n     + \n     ++/**\n     ++ * A 'struct update_refs_record' represents a value in the update-refs\n     ++ * list. We use a string_list to map refs to these (before, after) pairs.\n     ++ */\n     ++struct update_ref_record {\n     ++\tstruct object_id before;\n     ++\tstruct object_id after;\n     ++};\n     ++\n     + static int git_sequencer_config(const char *k, const char *v, void *cb)\n     + {\n     + \tstruct replay_opts *opts = cb;\n      @@ sequencer.c: int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)\n       \n       \treturn 0;\n     @@ sequencer.c: int sequencer_determine_whence(struct repository *r, enum commit_wh\n      +\t\t\t\t    struct string_list *refs)\n      +{\n      +\tint result = 0;\n     -+\tstruct stat st;\n      +\tFILE *fp = NULL;\n      +\tstruct strbuf ref = STRBUF_INIT;\n      +\tstruct strbuf hash = STRBUF_INIT;\n     -+\tchar *path = xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n     ++\tstruct update_ref_record *rec = NULL;\n      +\n     -+\tif (stat(path, &st))\n     -+\t\tgoto cleanup;\n     ++\tchar *path = rebase_path_update_refs(wt_dir);\n      +\n      +\tfp = fopen(path, \"r\");\n      +\tif (!fp)\n      +\t\tgoto cleanup;\n      +\n      +\twhile (strbuf_getline(&ref, fp) != EOF) {\n     -+\t\tstruct object_id oid;\n      +\t\tstruct string_list_item *item;\n      +\n     ++\t\tCALLOC_ARRAY(rec, 1);\n     ++\n      +\t\tif (strbuf_getline(&hash, fp) == EOF ||\n     -+\t\t    get_oid_hex(hash.buf, &oid)) {\n     ++\t\t    get_oid_hex(hash.buf, &rec->before)) {\n      +\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n      +\t\t\t\t  path);\n      +\t\t\tresult = -1;\n      +\t\t\tgoto cleanup;\n      +\t\t}\n      +\n     -+\t\titem = string_list_append(refs, ref.buf);\n     -+\t\titem->util = oiddup(&oid);\n     ++\t\tif (strbuf_getline(&hash, fp) == EOF ||\n     ++\t\t    get_oid_hex(hash.buf, &rec->after)) {\n     ++\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n     ++\t\t\t\t  path);\n     ++\t\t\tresult = -1;\n     ++\t\t\tgoto cleanup;\n     ++\t\t}\n     ++\n     ++\t\titem = string_list_insert(refs, ref.buf);\n     ++\t\titem->util = rec;\n     ++\t\trec = NULL;\n      +\t}\n      +\n      +cleanup:\n      +\tif (fp)\n      +\t\tfclose(fp);\n      +\tfree(path);\n     ++\tfree(rec);\n      +\tstrbuf_release(&ref);\n      +\tstrbuf_release(&hash);\n      +\treturn result;\n     @@ sequencer.h: void sequencer_post_commit_cleanup(struct repository *r, int verbos\n       #endif /* SEQUENCER_H */\n      \n       ## t/t2407-worktree-heads.sh ##\n     -@@ t/t2407-worktree-heads.sh: TEST_PASSES_SANITIZE_LEAK=true\n     +@@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n     + \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n     + '\n       \n     - test_expect_success 'setup' '\n     - \ttest_commit init &&\n     --\tgit branch -f fake-1 &&\n     --\tgit branch -f fake-2 &&\n     ++test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n     ++\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n     ++\n     ++\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n     ++\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n      +\n     -+\tfor i in 1 2 3 4\n     -+\tdo\n     -+\t\tgit branch -f fake-$i || return 1\n     -+\tdone &&\n     - \n     - \tfor i in 1 2 3 4\n     - \tdo\n     -@@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n     - \techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&\n     - \techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&\n     - \n     --\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n     --\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n      +\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n      +\trefs/heads/fake-3\n      +\t$(git rev-parse HEAD~1)\n     ++\t$(git rev-parse HEAD)\n      +\trefs/heads/fake-4\n      +\t$(git rev-parse HEAD)\n     ++\t$(git rev-parse HEAD)\n      +\tEOF\n      +\n     -+\tfor i in 1 3 4\n     ++\tfor i in 3 4\n      +\tdo\n      +\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n      +\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n      +\t\t\treturn 1\n      +\tdone\n     - '\n     - \n     ++'\n     ++\n       test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n     + \ttest_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&\n     + \tgrep \"refusing to fetch into branch '\\''refs/heads/wt-3'\\''\" err &&\n  3:  669f4abd59e !  4:  dec95681d2b rebase-interactive: update 'merge' description\n     @@ Commit message\n          The 'merge' command description for the todo list documentation in an\n          interactive rebase has multiple lines. The lines other than the first\n          one start with dots ('.') while the similar multi-line documentation for\n     -    'fixup' does not.\n     +    'fixup' does not. This description only appears in the comment text of\n     +    the todo file during an interactive rebase.\n      \n          The 'merge' command was documented when interactive rebase was first\n          ported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n  4:  6528a50343f =  5:  b2c09600918 sequencer: define array with enum values\n  5:  e95ad41d355 =  6:  fa7ecb718cf sequencer: add update-ref command\n  6:  918b398d6a2 !  7:  3ec2cc922f9 rebase: add --update-refs option\n     @@ Commit message\n          'update-ref' commands. Tests are added to ensure that these todo\n          commands are added in the correct locations.\n      \n     -    A future change will update the behavior to actually update the refs\n     -    at the end of the rebase sequence.\n     +    This change does _not_ include the actual behavior of tracking the\n     +    updated refs and writing the new ref values at the end of the rebase\n     +    process. That is deferred to a later change.\n      \n          Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n      \n     @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n      +\t\t\titem->command = TODO_UPDATE_REF;\n      +\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n      +\n     -+\t\t\tsti = string_list_append(&ctx->refs_to_oids,\n     ++\t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n      +\t\t\t\t\t\t decoration->name);\n      +\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n      +\t\t}\n     @@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite when in erro\n      +\t\tgrep \"update-ref refs/heads/allow-update\" todo\n      +\t)\n      +'\n     ++\n     ++# This must be the last test in this file\n     ++test_expect_success '$EDITOR and friends are unchanged' '\n     ++\ttest_editor_unchanged\n     ++'\n      +\n       test_done\n      \n  7:  72e0481b643 !  8:  fb5f64c5201 rebase: update refs from 'update-ref' commands\n     @@ Commit message\n          interactive rebase.\n      \n          Teach Git to record the HEAD position when reaching these 'update-ref'\n     -    commands. The ref/OID pair is stored in the\n     +    commands. The ref/before/after triple is stored in the\n          $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n          file to avoid having other processes updating the refs in that file\n          while the rebase is in progress.\n     @@ Commit message\n          Not only do we update the file when the sequencer reaches these\n          'update-ref' commands, we then update the refs themselves at the end of\n          the rebase sequence. If the rebase is aborted before this final step,\n     -    then the refs are not updated.\n     +    then the refs are not updated. The 'before' value is used to ensure that\n     +    we do not accidentally obliterate a ref that was updated concurrently\n     +    (say, by an older version of Git or a third-party tool).\n     +\n     +    Now that the 'git rebase --update-refs' command is implemented to write\n     +    to the update-refs file, we can remove the fake construction of the\n     +    update-refs file from a test in t2407-worktree-heads.sh.\n      \n          Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n      \n     @@ sequencer.c\n       \n       #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n       \n     -@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n     -  */\n     - static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n     +@@ sequencer.c: struct update_ref_record {\n     + \tstruct object_id after;\n     + };\n       \n     -+/*\n     -+ * The update-refs file stores a list of refs that will be updated at the end\n     -+ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n     -+ * update the OIDs for the refs in this file, but the refs are not updated\n     -+ * until the end of the rebase sequence.\n     -+ */\n     -+static GIT_PATH_FUNC(rebase_path_update_refs, \"rebase-merge/update-refs\")\n     -+\n     - /*\n     -  * The following files are written by git-rebase just after parsing the\n     -  * command-line.\n     ++static struct update_ref_record *init_update_ref_record(const char *ref)\n     ++{\n     ++\tstruct update_ref_record *rec = xmalloc(sizeof(*rec));\n     ++\n     ++\toidcpy(&rec->before, null_oid());\n     ++\toidcpy(&rec->after, null_oid());\n     ++\n     ++\t/* This may fail, but that's fine, we will keep the null OID. */\n     ++\tread_ref(ref, &rec->before);\n     ++\n     ++\treturn rec;\n     ++}\n     ++\n     + static int git_sequencer_config(const char *k, const char *v, void *cb)\n     + {\n     + \tstruct replay_opts *opts = cb;\n      @@ sequencer.c: leave_merge:\n       \treturn ret;\n       }\n       \n      -static int do_update_ref(struct repository *r, const char *ref_name)\n      +static int write_update_refs_state(struct string_list *refs_to_oids)\n     -+{\n     + {\n      +\tint result = 0;\n     ++\tstruct lock_file lock = LOCK_INIT;\n      +\tFILE *fp = NULL;\n      +\tstruct string_list_item *item;\n     -+\tchar *path = xstrdup(rebase_path_update_refs());\n     ++\tchar *path;\n     ++\n     ++\tif (!refs_to_oids->nr)\n     ++\t\treturn 0;\n     ++\n     ++\tpath = rebase_path_update_refs(the_repository->gitdir);\n      +\n      +\tif (safe_create_leading_directories(path)) {\n      +\t\tresult = error(_(\"unable to create leading directories of %s\"),\n     @@ sequencer.c: leave_merge:\n      +\t\tgoto cleanup;\n      +\t}\n      +\n     -+\tfp = fopen(path, \"w\");\n     ++\tif (hold_lock_file_for_update(&lock, path, 0) < 0) {\n     ++\t\tresult = error(_(\"another 'rebase' process appears to be running; \"\n     ++\t\t\t\t \"'%s.lock' already exists\"),\n     ++\t\t\t       path);\n     ++\t\tgoto cleanup;\n     ++\t}\n     ++\n     ++\tfp = fdopen_lock_file(&lock, \"w\");\n      +\tif (!fp) {\n      +\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n     ++\t\trollback_lock_file(&lock);\n      +\t\tgoto cleanup;\n      +\t}\n      +\n     -+\tfor_each_string_list_item(item, refs_to_oids)\n     -+\t\tfprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n     ++\tfor_each_string_list_item(item, refs_to_oids) {\n     ++\t\tstruct update_ref_record *rec = item->util;\n     ++\t\tfprintf(fp, \"%s\\n%s\\n%s\\n\", item->string,\n     ++\t\t\toid_to_hex(&rec->before), oid_to_hex(&rec->after));\n     ++\t}\n     ++\n     ++\tresult = commit_lock_file(&lock);\n      +\n      +cleanup:\n     -+\tif (fp)\n     -+\t\tfclose(fp);\n     ++\tfree(path);\n      +\treturn result;\n      +}\n      +\n      +static int do_update_ref(struct repository *r, const char *refname)\n     - {\n     ++{\n      +\tstruct string_list_item *item;\n      +\tstruct string_list list = STRING_LIST_INIT_DUP;\n     -+\tint found = 0;\n      +\n      +\tsequencer_get_update_refs_state(r->gitdir, &list);\n      +\n      +\tfor_each_string_list_item(item, &list) {\n      +\t\tif (!strcmp(item->string, refname)) {\n     -+\t\t\tstruct object_id oid;\n     -+\t\t\tfree(item->util);\n     -+\t\t\tfound = 1;\n     -+\n     -+\t\t\tif (!read_ref(\"HEAD\", &oid)) {\n     -+\t\t\t\titem->util = oiddup(&oid);\n     -+\t\t\t\tbreak;\n     -+\t\t\t}\n     ++\t\t\tstruct update_ref_record *rec = item->util;\n     ++\t\t\tread_ref(\"HEAD\", &rec->after);\n     ++\t\t\tbreak;\n      +\t\t}\n      +\t}\n      +\n     -+\tif (!found) {\n     -+\t\tstruct object_id oid;\n     -+\t\titem = string_list_append(&list, refname);\n     -+\n     -+\t\tif (!read_ref(\"HEAD\", &oid))\n     -+\t\t\titem->util = oiddup(&oid);\n     -+\t\telse\n     -+\t\t\titem->util = oiddup(the_hash_algo->null_oid);\n     -+\t}\n     -+\n      +\twrite_update_refs_state(&list);\n      +\tstring_list_clear(&list, 1);\n       \treturn 0;\n     @@ sequencer.c: leave_merge:\n      +\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n      +\n      +\tfor_each_string_list_item(item, &refs_to_oids) {\n     -+\t\tstruct object_id *oid_to = item->util;\n     -+\t\tstruct object_id oid_from;\n     ++\t\tstruct update_ref_record *rec = item->util;\n      +\n     -+\t\tif (oideq(oid_to, the_hash_algo->null_oid)) {\n     ++\t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n      +\t\t\t/*\n      +\t\t\t * Ref was not updated. User may have deleted the\n      +\t\t\t * 'update-ref' step.\n     @@ sequencer.c: leave_merge:\n      +\t\t\tcontinue;\n      +\t\t}\n      +\n     -+\t\tif (read_ref(item->string, &oid_from)) {\n     -+\t\t\t/*\n     -+\t\t\t * The ref does not exist. The user probably\n     -+\t\t\t * inserted a new 'update-ref' step with a new\n     -+\t\t\t * branch name.\n     -+\t\t\t */\n     -+\t\t\toidcpy(&oid_from, the_hash_algo->null_oid);\n     -+\t\t}\n     -+\n      +\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n     -+\t\t\t\titem->string,\n     -+\t\t\t\toid_to, &oid_from,\n     -+\t\t\t\t0, UPDATE_REFS_MSG_ON_ERR);\n     ++\t\t\t\t       item->string,\n     ++\t\t\t\t       &rec->after, &rec->before,\n     ++\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n      +\t}\n      +\n      +\tstring_list_clear(&refs_to_oids, 1);\n     @@ sequencer.c: cleanup_head_ref:\n       \t/*\n       \t * Sequence of picks finished successfully; cleanup by\n       \t * removing the .git/sequencer directory\n     +@@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n     + \n     + \t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n     + \t\t\t\t\t\t decoration->name);\n     +-\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n     ++\t\t\tsti->util = init_update_ref_record(decoration->name);\n     + \t\t}\n     + \n     + \t\titem->offset_in_buf = base_offset;\n      @@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n       \t\t}\n       \t}\n     @@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo\n       \tfree(todo_list->items);\n       \ttodo_list->items = ctx.items;\n      \n     + ## t/t2407-worktree-heads.sh ##\n     +@@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n     + \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n     + '\n     + \n     +-test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n     +-\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n     +-\n     +-\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n     +-\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n     ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' '\n     ++\ttest_when_finished git -C wt-3 rebase --abort &&\n     + \n     +-\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n     +-\trefs/heads/fake-3\n     +-\t$(git rev-parse HEAD~1)\n     +-\t$(git rev-parse HEAD)\n     +-\trefs/heads/fake-4\n     +-\t$(git rev-parse HEAD)\n     +-\t$(git rev-parse HEAD)\n     +-\tEOF\n     ++\tgit branch -f can-be-updated wt-3 &&\n     ++\ttest_must_fail git -C wt-3 rebase --update-refs conflict-3 &&\n     + \n     + \tfor i in 3 4\n     + \tdo\n     +-\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n     +-\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n     ++\t\ttest_must_fail git branch -f can-be-updated HEAD 2>err &&\n     ++\t\tgrep \"cannot force update the branch '\\''can-be-updated'\\'' checked out at.*wt-3\" err ||\n     + \t\t\treturn 1\n     + \tdone\n     + '\n     +\n       ## t/t3404-rebase-interactive.sh ##\n      @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands with --rebase-merges' '\n       \t)\n       '\n       \n     -+compare_two_refs () {\n     -+\tgit rev-parse $1 >expect &&\n     -+\tgit rev-parse $2 >actual &&\n     -+\ttest_cmp expect actual\n     -+}\n     -+\n      +test_expect_success '--update-refs updates refs correctly' '\n      +\tgit checkout -B update-refs no-conflict-branch &&\n      +\tgit branch -f base HEAD~4 &&\n     @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands\n      +\n      +\tgit rebase -i --autosquash --update-refs primary &&\n      +\n     -+\tcompare_two_refs HEAD~3 refs/heads/first &&\n     -+\tcompare_two_refs HEAD~3 refs/heads/second &&\n     -+\tcompare_two_refs HEAD~1 refs/heads/third &&\n     -+\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n     ++\ttest_cmp_rev HEAD~3 refs/heads/first &&\n     ++\ttest_cmp_rev HEAD~3 refs/heads/second &&\n     ++\ttest_cmp_rev HEAD~1 refs/heads/third &&\n     ++\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n      +'\n      +\n       # This must be the last test in this file\n  -:  ----------- >  9:  29c7c76805a sequencer: rewrite update-refs as user edits todo list\n  8:  d2cfdbfc431 = 10:  c0022d07579 rebase: add rebase.updateRefs config option\n  -:  ----------- > 11:  d53b4ff2cee sequencer: ignore HEAD ref under --update-refs\n  -:  ----------- > 12:  d5cd4b49e46 sequencer: notify user of --update-refs activity\n\n-- \ngitgitgadget\n"},{"id":"458874","messageId":"540a3be256f8a0ca57ae0ed90cc04190a28e91e1.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 02/12] t2407: test branches currently using apply backend","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:06:55Z","receivedAt":"2022-07-12T13:07:17Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe tests in t2407 that verify the branch_checked_out() helper in the\ncase of bisects and rebases were added by 9347303db89 (branch: check for\nbisects and rebases, 2022-06-08). However, that commit failed to check\nfor rebases that are using the 'apply' backend.\n\nAdd a test that checks the apply backend. The implementation was already\ncorrect here, but it is good to have regression tests before modifying\nthe implementation further.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n t/t2407-worktree-heads.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex 100ab286d5c..a67ce5fb003 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -61,7 +61,17 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in bisect' '\n \tgrep \"cannot force update the branch '\\''wt-4'\\'' checked out at.*wt-4\" err\n '\n \n-test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase' '\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (apply)' '\n+\ttest_when_finished git -C wt-2 rebase --abort &&\n+\n+\t# This will fail part-way through due to a conflict.\n+\ttest_must_fail git -C wt-2 rebase --apply conflict-2 &&\n+\n+\ttest_must_fail git branch -f wt-2 HEAD 2>err &&\n+\tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n+'\n+\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (merge)' '\n \ttest_when_finished git -C wt-2 rebase --abort &&\n \n \t# This will fail part-way through due to a conflict.\n-- \ngitgitgadget\n\n"},{"id":"458875","messageId":"bf301a054e3903444c2ea1c2e0234209d5ece291.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 03/12] branch: consider refs under 'update-refs'","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:06:56Z","receivedAt":"2022-07-12T13:07:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe branch_checked_out() helper helps commands like 'git branch' and\n'git fetch' from overwriting refs that are currently checked out in\nother worktrees.\n\nA future update to 'git rebase' will introduce a new '--update-refs'\noption which will update the local refs that point to commits that are\nbeing rebased. To avoid collisions as the rebase completes, we want to\nmake the future data store for these refs to be considered by\nbranch_checked_out().\n\nThe data store is a plaintext file inside the 'rebase-merge' directory\nfor that worktree. The file lists refnames followed by two OIDs, each on\nseparate lines. The OIDs will be used to store the original values of\nthe refs and the to-be-written values as the rebase progresses, but can\nbe ignored at the moment.\n\nCreate a new sequencer_get_update_refs_state() method that parses this\nfile and populates a struct string_list with the ref-OID pairs. We can\nthen use this list to add to the current_checked_out_branches strmap\nused by branch_checked_out().\n\nTo properly navigate to the rebase directory for a given worktree,\nextract the static strbuf_worktree_gitdir() method to a public API\nmethod.\n\nWe can test that this works without having Git write this file by\nartificially creating one in our test script, at least until 'git rebase\n--update-refs' is implemented and we can use it directly.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n branch.c                  | 13 +++++++\n sequencer.c               | 74 +++++++++++++++++++++++++++++++++++++++\n sequencer.h               |  9 +++++\n t/t2407-worktree-heads.sh | 23 ++++++++++++\n 4 files changed, 119 insertions(+)\n\ndiff --git a/branch.c b/branch.c\nindex 526e8237673..f252c4eef6c 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -365,6 +365,7 @@ static void prepare_checked_out_branches(void)\n \t\tchar *old;\n \t\tstruct wt_status_state state = { 0 };\n \t\tstruct worktree *wt = worktrees[i++];\n+\t\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n \n \t\tif (wt->is_bare)\n \t\t\tcontinue;\n@@ -400,6 +401,18 @@ static void prepare_checked_out_branches(void)\n \t\t\tstrbuf_release(&ref);\n \t\t}\n \t\twt_status_state_free_buffers(&state);\n+\n+\t\tif (!sequencer_get_update_refs_state(get_worktree_git_dir(wt),\n+\t\t\t\t\t\t     &update_refs)) {\n+\t\t\tstruct string_list_item *item;\n+\t\t\tfor_each_string_list_item(item, &update_refs) {\n+\t\t\t\told = strmap_put(&current_checked_out_branches,\n+\t\t\t\t\t\t item->string,\n+\t\t\t\t\t\t xstrdup(wt->path));\n+\t\t\t\tfree(old);\n+\t\t\t}\n+\t\t\tstring_list_clear(&update_refs, 1);\n+\t\t}\n \t}\n \n \tfree_worktrees(worktrees);\ndiff --git a/sequencer.c b/sequencer.c\nindex 8c3ed3532ac..e93c61cbd25 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -147,6 +147,20 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n  */\n static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n \n+/*\n+ * The update-refs file stores a list of refs that will be updated at the end\n+ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n+ * update the OIDs for the refs in this file, but the refs are not updated\n+ * until the end of the rebase sequence.\n+ *\n+ * rebase_path_update_refs() returns the path to this file for a given\n+ * worktree directory. For the current worktree, pass the_repository->gitdir.\n+ */\n+static char *rebase_path_update_refs(const char *wt_dir)\n+{\n+\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n+}\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line.\n@@ -169,6 +183,15 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, \"rebase-merge/no-res\n static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n \n+/**\n+ * A 'struct update_refs_record' represents a value in the update-refs\n+ * list. We use a string_list to map refs to these (before, after) pairs.\n+ */\n+struct update_ref_record {\n+\tstruct object_id before;\n+\tstruct object_id after;\n+};\n+\n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n@@ -5901,3 +5924,54 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)\n \n \treturn 0;\n }\n+\n+int sequencer_get_update_refs_state(const char *wt_dir,\n+\t\t\t\t    struct string_list *refs)\n+{\n+\tint result = 0;\n+\tFILE *fp = NULL;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf hash = STRBUF_INIT;\n+\tstruct update_ref_record *rec = NULL;\n+\n+\tchar *path = rebase_path_update_refs(wt_dir);\n+\n+\tfp = fopen(path, \"r\");\n+\tif (!fp)\n+\t\tgoto cleanup;\n+\n+\twhile (strbuf_getline(&ref, fp) != EOF) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tCALLOC_ARRAY(rec, 1);\n+\n+\t\tif (strbuf_getline(&hash, fp) == EOF ||\n+\t\t    get_oid_hex(hash.buf, &rec->before)) {\n+\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n+\t\t\t\t  path);\n+\t\t\tresult = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (strbuf_getline(&hash, fp) == EOF ||\n+\t\t    get_oid_hex(hash.buf, &rec->after)) {\n+\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n+\t\t\t\t  path);\n+\t\t\tresult = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\titem = string_list_insert(refs, ref.buf);\n+\t\titem->util = rec;\n+\t\trec = NULL;\n+\t}\n+\n+cleanup:\n+\tif (fp)\n+\t\tfclose(fp);\n+\tfree(path);\n+\tfree(rec);\n+\tstrbuf_release(&ref);\n+\tstrbuf_release(&hash);\n+\treturn result;\n+}\ndiff --git a/sequencer.h b/sequencer.h\nindex da64473636b..3ae541bb145 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -232,4 +232,13 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose);\n int sequencer_get_last_command(struct repository* r,\n \t\t\t       enum replay_action *action);\n int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);\n+\n+/**\n+ * Append the set of ref-OID pairs that are currently stored for the 'git\n+ * rebase --update-refs' feature if such a rebase is currently happening.\n+ *\n+ * Localized to a worktree's git dir.\n+ */\n+int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs);\n+\n #endif /* SEQUENCER_H */\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex a67ce5fb003..97f5c87f8c8 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -81,6 +81,29 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n '\n \n+test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n+\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n+\n+\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n+\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n+\n+\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n+\trefs/heads/fake-3\n+\t$(git rev-parse HEAD~1)\n+\t$(git rev-parse HEAD)\n+\trefs/heads/fake-4\n+\t$(git rev-parse HEAD)\n+\t$(git rev-parse HEAD)\n+\tEOF\n+\n+\tfor i in 3 4\n+\tdo\n+\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n+\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n+\t\t\treturn 1\n+\tdone\n+'\n+\n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n \ttest_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&\n \tgrep \"refusing to fetch into branch '\\''refs/heads/wt-3'\\''\" err &&\n-- \ngitgitgadget\n\n"},{"id":"458876","messageId":"b2c09600918572e1157969a27f4e83a34ea0af69.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 05/12] sequencer: define array with enum values","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:06:58Z","receivedAt":"2022-07-12T13:07:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe todo_command_info array defines which strings match with which\ntodo_command enum values. The array is defined in the same order as the\nenum values, but if one changed without the other, then we would have\nunexpected results.\n\nMake it easier to see changes to the enum and this array by using the\nenum values as the indices of the array.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e93c61cbd25..575f9be5ea0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1708,20 +1708,20 @@ static struct {\n \tchar c;\n \tconst char *str;\n } todo_command_info[] = {\n-\t{ 'p', \"pick\" },\n-\t{ 0,   \"revert\" },\n-\t{ 'e', \"edit\" },\n-\t{ 'r', \"reword\" },\n-\t{ 'f', \"fixup\" },\n-\t{ 's', \"squash\" },\n-\t{ 'x', \"exec\" },\n-\t{ 'b', \"break\" },\n-\t{ 'l', \"label\" },\n-\t{ 't', \"reset\" },\n-\t{ 'm', \"merge\" },\n-\t{ 0,   \"noop\" },\n-\t{ 'd', \"drop\" },\n-\t{ 0,   NULL }\n+\t[TODO_PICK] = { 'p', \"pick\" },\n+\t[TODO_REVERT] = { 0,   \"revert\" },\n+\t[TODO_EDIT] = { 'e', \"edit\" },\n+\t[TODO_REWORD] = { 'r', \"reword\" },\n+\t[TODO_FIXUP] = { 'f', \"fixup\" },\n+\t[TODO_SQUASH] = { 's', \"squash\" },\n+\t[TODO_EXEC] = { 'x', \"exec\" },\n+\t[TODO_BREAK] = { 'b', \"break\" },\n+\t[TODO_LABEL] = { 'l', \"label\" },\n+\t[TODO_RESET] = { 't', \"reset\" },\n+\t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_NOOP] = { 0,   \"noop\" },\n+\t[TODO_DROP] = { 'd', \"drop\" },\n+\t[TODO_COMMENT] = { 0,   NULL },\n };\n \n static const char *command_to_string(const enum todo_command command)\n-- \ngitgitgadget\n\n"},{"id":"458877","messageId":"dec95681d2be3b09090c4df325bdcac4a5b97530.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 04/12] rebase-interactive: update 'merge' description","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:06:57Z","receivedAt":"2022-07-12T13:07:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe 'merge' command description for the todo list documentation in an\ninteractive rebase has multiple lines. The lines other than the first\none start with dots ('.') while the similar multi-line documentation for\n'fixup' does not. This description only appears in the comment text of\nthe todo file during an interactive rebase.\n\nThe 'merge' command was documented when interactive rebase was first\nported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n2018-08-10). These dots might have been carried over from the previous\nshell implementation.\n\nThe 'fixup' command was documented more recently in 9e3cebd97cb (rebase\n-i: add fixup [-C | -c] command, 2021-01-29).\n\nLooking at the output in an editor, my personal opinion is that the dots\nare unnecessary and noisy. Remove them now before adding more commands\nwith multi-line documentation.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 87649d0c016..22394224faa 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -54,9 +54,9 @@ void append_todo_help(int command_count,\n \"l, label <label> = label current HEAD with a name\\n\"\n \"t, reset <label> = reset HEAD to a label\\n\"\n \"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\\n\"\n-\".       create a merge commit using the original merge commit's\\n\"\n-\".       message (or the oneline, if no original merge commit was\\n\"\n-\".       specified); use -c <commit> to reword the commit message\\n\"\n+\"        create a merge commit using the original merge commit's\\n\"\n+\"        message (or the oneline, if no original merge commit was\\n\"\n+\"        specified); use -c <commit> to reword the commit message\\n\"\n \"\\n\"\n \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n \tunsigned edit_todo = !(shortrevisions && shortonto);\n-- \ngitgitgadget\n\n"},{"id":"458878","messageId":"fa7ecb718cfb404c791d6d107799aa2756ca7ee2.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 06/12] sequencer: add update-ref command","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:06:59Z","receivedAt":"2022-07-12T13:07:37Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nAdd the boilerplate for an \"update-ref\" command in the sequencer. This\nconnects to the current no-op do_update_ref() which will be filled in\nafter more connections are created.\n\nThe syntax in the todo list will be \"update-ref <ref-name>\" to signal\nthat we should store the current commit as the value for updating\n<ref-name> at the end of the rebase.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c |  3 +++\n sequencer.c          | 14 +++++++++++++-\n sequencer.h          |  1 +\n 3 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 22394224faa..1ff07647af3 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -57,6 +57,9 @@ void append_todo_help(int command_count,\n \"        create a merge commit using the original merge commit's\\n\"\n \"        message (or the oneline, if no original merge commit was\\n\"\n \"        specified); use -c <commit> to reword the commit message\\n\"\n+\"u, update-ref <ref> = track a placeholder for the <ref> to be updated\\n\"\n+\"                      to this position in the new commits. The <ref> is\\n\"\n+\"                      updated at the end of the rebase\\n\"\n \"\\n\"\n \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n \tunsigned edit_todo = !(shortrevisions && shortonto);\ndiff --git a/sequencer.c b/sequencer.c\nindex 575f9be5ea0..1d6442c9639 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1719,6 +1719,7 @@ static struct {\n \t[TODO_LABEL] = { 'l', \"label\" },\n \t[TODO_RESET] = { 't', \"reset\" },\n \t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_UPDATE_REF] = { 'u', \"update-ref\" },\n \t[TODO_NOOP] = { 0,   \"noop\" },\n \t[TODO_DROP] = { 'd', \"drop\" },\n \t[TODO_COMMENT] = { 0,   NULL },\n@@ -2480,7 +2481,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,\n \t\t\t     command_to_string(item->command));\n \n \tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n-\t    item->command == TODO_RESET) {\n+\t    item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {\n \t\titem->commit = NULL;\n \t\titem->arg_offset = bol - buf;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -4079,6 +4080,11 @@ leave_merge:\n \treturn ret;\n }\n \n+static int do_update_ref(struct repository *r, const char *ref_name)\n+{\n+\treturn 0;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -4454,6 +4460,12 @@ static int pick_commits(struct repository *r,\n \t\t\t\treturn error_with_patch(r, item->commit,\n \t\t\t\t\t\t\targ, item->arg_len,\n \t\t\t\t\t\t\topts, res, 0);\n+\t\t} else if (item->command == TODO_UPDATE_REF) {\n+\t\t\tstruct strbuf ref = STRBUF_INIT;\n+\t\t\tstrbuf_add(&ref, arg, item->arg_len);\n+\t\t\tif ((res = do_update_ref(r, ref.buf)))\n+\t\t\t\treschedule = 1;\n+\t\t\tstrbuf_release(&ref);\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 3ae541bb145..2cf5c1b9a38 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -95,6 +95,7 @@ enum todo_command {\n \tTODO_LABEL,\n \tTODO_RESET,\n \tTODO_MERGE,\n+\tTODO_UPDATE_REF,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n-- \ngitgitgadget\n\n"},{"id":"458879","messageId":"3ec2cc922f971af4e4a558188cf139cc0c0150d6.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 07/12] rebase: add --update-refs option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:07:00Z","receivedAt":"2022-07-12T13:07:49Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen working on a large feature, it can be helpful to break that feature\ninto multiple smaller parts that become reviewed in sequence. During\ndevelopment or during review, a change to one part of the feature could\naffect multiple of these parts. An interactive rebase can help adjust\nthe multi-part \"story\" of the branch.\n\nHowever, if there are branches tracking the different parts of the\nfeature, then rebasing the entire list of commits can create commits not\nreachable from those \"sub branches\". It can take a manual step to update\nthose branches.\n\nAdd a new --update-refs option to 'git rebase -i' that adds 'update-ref\n<ref>' steps to the todo file whenever a commit that is being rebased is\ndecorated with that <ref>. At the very end, the rebase process updates\nall of the listed refs to the values stored during the rebase operation.\n\nBe sure to iterate after any squashing or fixups are placed. Update the\nbranch only after those squashes and fixups are complete. This allows a\n--fixup commit at the tip of the feature to apply correctly to the sub\nbranch, even if it is fixing up the most-recent commit in that part.\n\nOne potential problem here is that refs decorating commits that are\nalready marked as \"fixup!\" or \"squash!\" will not be included in this\nlist. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\nto change the relative order of these refs, so it is not recommended.\nThe workflow here is intended to allow these kinds of commits at the tip\nof the rebased branch while the other sub branches come along for the\nride without intervention.\n\nThis change update the documentation and builtin to accept the\n--update-refs option as well as updating the todo file with the\n'update-ref' commands. Tests are added to ensure that these todo\ncommands are added in the correct locations.\n\nThis change does _not_ include the actual behavior of tracking the\nupdated refs and writing the new ref values at the end of the rebase\nprocess. That is deferred to a later change.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/git-rebase.txt  |   8 +++\n builtin/rebase.c              |   5 ++\n sequencer.c                   | 107 ++++++++++++++++++++++++++++++++++\n sequencer.h                   |   1 +\n t/t2407-worktree-heads.sh     |  22 +++++++\n t/t3404-rebase-interactive.sh |  70 ++++++++++++++++++++++\n 6 files changed, 213 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 262fb01aec0..e7611b4089c 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -609,6 +609,13 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n start would be overridden by the presence of\n `rebase.rescheduleFailedExec=true` configuration.\n \n+--update-refs::\n+--no-update-refs::\n+\tAutomatically force-update any branches that point to commits that\n+\tare being rebased. Any branches that are checked out in a worktree\n+\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n+\tin this way.\n+\n INCOMPATIBLE OPTIONS\n --------------------\n \n@@ -632,6 +639,7 @@ are incompatible with the following options:\n  * --empty=\n  * --reapply-cherry-picks\n  * --edit-todo\n+ * --update-refs\n  * --root when used in combination with --onto\n \n In addition, the following pairs of options are incompatible:\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7ab50cda2ad..56d82a52106 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -102,6 +102,7 @@ struct rebase_options {\n \tint reschedule_failed_exec;\n \tint reapply_cherry_picks;\n \tint fork_point;\n+\tint update_refs;\n };\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n@@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \t\tret = complete_action(the_repository, &replay, flags,\n \t\t\tshortrevisions, opts->onto_name, opts->onto,\n \t\t\t&opts->orig_head, &commands, opts->autosquash,\n+\t\t\topts->update_refs,\n \t\t\t&todo_list);\n \t}\n \n@@ -1124,6 +1126,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n+\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n+\t\t\t N_(\"update local refs that point to commits \"\n+\t\t\t    \"that are being rebased\")),\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n \t\t\tN_(\"GPG-sign commits\"),\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\ndiff --git a/sequencer.c b/sequencer.c\nindex 1d6442c9639..e657862cda2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -35,6 +35,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"branch.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -5638,10 +5639,113 @@ static int skip_unnecessary_picks(struct repository *r,\n \treturn 0;\n }\n \n+struct todo_add_branch_context {\n+\tstruct todo_item *items;\n+\tsize_t items_nr;\n+\tsize_t items_alloc;\n+\tstruct strbuf *buf;\n+\tstruct commit *commit;\n+\tstruct string_list refs_to_oids;\n+};\n+\n+static int add_decorations_to_list(const struct commit *commit,\n+\t\t\t\t   struct todo_add_branch_context *ctx)\n+{\n+\tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n+\n+\twhile (decoration) {\n+\t\tstruct todo_item *item;\n+\t\tconst char *path;\n+\t\tsize_t base_offset = ctx->buf->len;\n+\n+\t\tALLOC_GROW(ctx->items,\n+\t\t\tctx->items_nr + 1,\n+\t\t\tctx->items_alloc);\n+\t\titem = &ctx->items[ctx->items_nr];\n+\t\tmemset(item, 0, sizeof(*item));\n+\n+\t\t/* If the branch is checked out, then leave a comment instead. */\n+\t\tif ((path = branch_checked_out(decoration->name))) {\n+\t\t\titem->command = TODO_COMMENT;\n+\t\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n+\t\t\t\t    decoration->name, path);\n+\t\t} else {\n+\t\t\tstruct string_list_item *sti;\n+\t\t\titem->command = TODO_UPDATE_REF;\n+\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n+\n+\t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n+\t\t\t\t\t\t decoration->name);\n+\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n+\t\t}\n+\n+\t\titem->offset_in_buf = base_offset;\n+\t\titem->arg_offset = base_offset;\n+\t\titem->arg_len = ctx->buf->len - base_offset;\n+\t\tctx->items_nr++;\n+\n+\t\tdecoration = decoration->next;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * For each 'pick' command, find out if the commit has a decoration in\n+ * refs/heads/. If so, then add a 'label for-update-refs/' command.\n+ */\n+static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n+{\n+\tint i;\n+\tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n+\tstruct decoration_filter decoration_filter = {\n+\t\t.include_ref_pattern = &decorate_refs_include,\n+\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n+\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config,\n+\t};\n+\tstruct todo_add_branch_context ctx = {\n+\t\t.buf = &todo_list->buf,\n+\t\t.refs_to_oids = STRING_LIST_INIT_DUP,\n+\t};\n+\n+\tctx.items_alloc = 2 * todo_list->nr + 1;\n+\tALLOC_ARRAY(ctx.items, ctx.items_alloc);\n+\n+\tstring_list_append(&decorate_refs_include, \"refs/heads/\");\n+\tload_ref_decorations(&decoration_filter, 0);\n+\n+\tfor (i = 0; i < todo_list->nr; ) {\n+\t\tstruct todo_item *item = &todo_list->items[i];\n+\n+\t\t/* insert ith item into new list */\n+\t\tALLOC_GROW(ctx.items,\n+\t\t\t   ctx.items_nr + 1,\n+\t\t\t   ctx.items_alloc);\n+\n+\t\tctx.items[ctx.items_nr++] = todo_list->items[i++];\n+\n+\t\tif (item->commit) {\n+\t\t\tctx.commit = item->commit;\n+\t\t\tadd_decorations_to_list(item->commit, &ctx);\n+\t\t}\n+\t}\n+\n+\tstring_list_clear(&ctx.refs_to_oids, 1);\n+\tfree(todo_list->items);\n+\ttodo_list->items = ctx.items;\n+\ttodo_list->nr = ctx.items_nr;\n+\ttodo_list->alloc = ctx.items_alloc;\n+\n+\treturn 0;\n+}\n+\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list)\n {\n \tchar shortonto[GIT_MAX_HEXSZ + 1];\n@@ -5660,6 +5764,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\titem->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n \t}\n \n+\tif (update_refs && todo_list_add_update_ref_commands(todo_list))\n+\t\treturn -1;\n+\n \tif (autosquash && todo_list_rearrange_squash(todo_list))\n \t\treturn -1;\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 2cf5c1b9a38..e671d7e0743 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -167,6 +167,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list);\n int todo_list_rearrange_squash(struct todo_list *todo_list);\n \ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex 97f5c87f8c8..8a03f14df8d 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -164,4 +164,26 @@ test_expect_success 'refuse to overwrite when in error states' '\n \tdone\n '\n \n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite during rebase with --update-refs' '\n+\tgit commit --fixup HEAD~2 --allow-empty &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n+\t\t! grep \"update-refs\" todo\n+\t) &&\n+\tgit branch -f allow-update HEAD~2 &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n+\t\tgrep \"update-ref refs/heads/allow-update\" todo\n+\t)\n+'\n+\n+# This must be the last test in this file\n+test_expect_success '$EDITOR and friends are unchanged' '\n+\ttest_editor_unchanged\n+'\n+\n test_done\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f31afd4a547..3cd20733bc8 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1743,6 +1743,76 @@ test_expect_success 'ORIG_HEAD is updated correctly' '\n \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n '\n \n+test_expect_success '--update-refs adds label and update-ref commands' '\n+\tgit checkout -b update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit commit --allow-empty --fixup=third &&\n+\tgit branch -f shared-tip &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tpick $(git log -1 --format=%h J) J\n+\t\tupdate-ref refs/heads/second\n+\t\tupdate-ref refs/heads/first\n+\t\tpick $(git log -1 --format=%h K) K\n+\t\tpick $(git log -1 --format=%h L) L\n+\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n+\t\tupdate-ref refs/heads/third\n+\t\tpick $(git log -1 --format=%h M) M\n+\t\tupdate-ref refs/heads/no-conflict-branch\n+\t\tupdate-ref refs/heads/shared-tip\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n+test_expect_success '--update-refs adds commands with --rebase-merges' '\n+\tgit checkout -b update-refs-with-merge no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit merge -m merge branch2 &&\n+\tgit branch -f merge-branch &&\n+\tgit commit --fixup=third --allow-empty &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tlabel onto\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h branch2~1) F\n+\t\tpick $(git log -1 --format=%h branch2) I\n+\t\tupdate-ref refs/heads/branch2\n+\t\tlabel merge\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h refs/heads/second) J\n+\t\tupdate-ref refs/heads/second\n+\t\tupdate-ref refs/heads/first\n+\t\tpick $(git log -1 --format=%h refs/heads/third~1) K\n+\t\tpick $(git log -1 --format=%h refs/heads/third) L\n+\t\tfixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n+\t\tupdate-ref refs/heads/third\n+\t\tpick $(git log -1 --format=%h HEAD~2) M\n+\t\tupdate-ref refs/heads/no-conflict-branch\n+\t\tmerge -C $(git log -1 --format=%h HEAD~1) merge # merge\n+\t\tupdate-ref refs/heads/merge-branch\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   --update-refs primary >todo &&\n+\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"458880","messageId":"fb5f64c52010e01ce0b5a886e9611fa2a6ea7833.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 08/12] rebase: update refs from 'update-ref' commands","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:07:01Z","receivedAt":"2022-07-12T13:07:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change introduced the 'git rebase --update-refs' option\nwhich added 'update-ref <ref>' commands to the todo list of an\ninteractive rebase.\n\nTeach Git to record the HEAD position when reaching these 'update-ref'\ncommands. The ref/before/after triple is stored in the\n$GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\nfile to avoid having other processes updating the refs in that file\nwhile the rebase is in progress.\n\nNot only do we update the file when the sequencer reaches these\n'update-ref' commands, we then update the refs themselves at the end of\nthe rebase sequence. If the rebase is aborted before this final step,\nthen the refs are not updated. The 'before' value is used to ensure that\nwe do not accidentally obliterate a ref that was updated concurrently\n(say, by an older version of Git or a third-party tool).\n\nNow that the 'git rebase --update-refs' command is implemented to write\nto the update-refs file, we can remove the fake construction of the\nupdate-refs file from a test in t2407-worktree-heads.sh.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c                   | 113 +++++++++++++++++++++++++++++++++-\n t/t2407-worktree-heads.sh     |  21 ++-----\n t/t3404-rebase-interactive.sh |  17 +++++\n 3 files changed, 134 insertions(+), 17 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e657862cda2..2d89b3b727a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -36,6 +36,7 @@\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n #include \"branch.h\"\n+#include \"log-tree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -193,6 +194,19 @@ struct update_ref_record {\n \tstruct object_id after;\n };\n \n+static struct update_ref_record *init_update_ref_record(const char *ref)\n+{\n+\tstruct update_ref_record *rec = xmalloc(sizeof(*rec));\n+\n+\toidcpy(&rec->before, null_oid());\n+\toidcpy(&rec->after, null_oid());\n+\n+\t/* This may fail, but that's fine, we will keep the null OID. */\n+\tread_ref(ref, &rec->before);\n+\n+\treturn rec;\n+}\n+\n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n@@ -4081,11 +4095,102 @@ leave_merge:\n \treturn ret;\n }\n \n-static int do_update_ref(struct repository *r, const char *ref_name)\n+static int write_update_refs_state(struct string_list *refs_to_oids)\n {\n+\tint result = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tFILE *fp = NULL;\n+\tstruct string_list_item *item;\n+\tchar *path;\n+\n+\tif (!refs_to_oids->nr)\n+\t\treturn 0;\n+\n+\tpath = rebase_path_update_refs(the_repository->gitdir);\n+\n+\tif (safe_create_leading_directories(path)) {\n+\t\tresult = error(_(\"unable to create leading directories of %s\"),\n+\t\t\t       path);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (hold_lock_file_for_update(&lock, path, 0) < 0) {\n+\t\tresult = error(_(\"another 'rebase' process appears to be running; \"\n+\t\t\t\t \"'%s.lock' already exists\"),\n+\t\t\t       path);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfp = fdopen_lock_file(&lock, \"w\");\n+\tif (!fp) {\n+\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n+\t\trollback_lock_file(&lock);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfor_each_string_list_item(item, refs_to_oids) {\n+\t\tstruct update_ref_record *rec = item->util;\n+\t\tfprintf(fp, \"%s\\n%s\\n%s\\n\", item->string,\n+\t\t\toid_to_hex(&rec->before), oid_to_hex(&rec->after));\n+\t}\n+\n+\tresult = commit_lock_file(&lock);\n+\n+cleanup:\n+\tfree(path);\n+\treturn result;\n+}\n+\n+static int do_update_ref(struct repository *r, const char *refname)\n+{\n+\tstruct string_list_item *item;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\n+\tsequencer_get_update_refs_state(r->gitdir, &list);\n+\n+\tfor_each_string_list_item(item, &list) {\n+\t\tif (!strcmp(item->string, refname)) {\n+\t\t\tstruct update_ref_record *rec = item->util;\n+\t\t\tread_ref(\"HEAD\", &rec->after);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\twrite_update_refs_state(&list);\n+\tstring_list_clear(&list, 1);\n \treturn 0;\n }\n \n+static int do_update_refs(struct repository *r)\n+{\n+\tint res = 0;\n+\tstruct string_list_item *item;\n+\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(r);\n+\n+\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n+\n+\tfor_each_string_list_item(item, &refs_to_oids) {\n+\t\tstruct update_ref_record *rec = item->util;\n+\n+\t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n+\t\t\t/*\n+\t\t\t * Ref was not updated. User may have deleted the\n+\t\t\t * 'update-ref' step.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n+\t\t\t\t       item->string,\n+\t\t\t\t       &rec->after, &rec->before,\n+\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n+\t}\n+\n+\tstring_list_clear(&refs_to_oids, 1);\n+\treturn res;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -4603,6 +4708,8 @@ cleanup_head_ref:\n \t\tstrbuf_release(&head_ref);\n \t}\n \n+\tdo_update_refs(r);\n+\n \t/*\n \t * Sequence of picks finished successfully; cleanup by\n \t * removing the .git/sequencer directory\n@@ -5676,7 +5783,7 @@ static int add_decorations_to_list(const struct commit *commit,\n \n \t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n \t\t\t\t\t\t decoration->name);\n-\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n+\t\t\tsti->util = init_update_ref_record(decoration->name);\n \t\t}\n \n \t\titem->offset_in_buf = base_offset;\n@@ -5732,6 +5839,8 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n \t\t}\n \t}\n \n+\twrite_update_refs_state(&ctx.refs_to_oids);\n+\n \tstring_list_clear(&ctx.refs_to_oids, 1);\n \tfree(todo_list->items);\n \ttodo_list->items = ctx.items;\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex 8a03f14df8d..50815acd3e8 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -81,25 +81,16 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n '\n \n-test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n-\n-\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n-\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' '\n+\ttest_when_finished git -C wt-3 rebase --abort &&\n \n-\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n-\trefs/heads/fake-3\n-\t$(git rev-parse HEAD~1)\n-\t$(git rev-parse HEAD)\n-\trefs/heads/fake-4\n-\t$(git rev-parse HEAD)\n-\t$(git rev-parse HEAD)\n-\tEOF\n+\tgit branch -f can-be-updated wt-3 &&\n+\ttest_must_fail git -C wt-3 rebase --update-refs conflict-3 &&\n \n \tfor i in 3 4\n \tdo\n-\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n-\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n+\t\ttest_must_fail git branch -f can-be-updated HEAD 2>err &&\n+\t\tgrep \"cannot force update the branch '\\''can-be-updated'\\'' checked out at.*wt-3\" err ||\n \t\t\treturn 1\n \tdone\n '\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3cd20733bc8..a37820fa728 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1813,6 +1813,23 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t)\n '\n \n+test_expect_success '--update-refs updates refs correctly' '\n+\tgit checkout -B update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\ttest_commit extra2 fileX &&\n+\tgit commit --amend --fixup=L &&\n+\n+\tgit rebase -i --autosquash --update-refs primary &&\n+\n+\ttest_cmp_rev HEAD~3 refs/heads/first &&\n+\ttest_cmp_rev HEAD~3 refs/heads/second &&\n+\ttest_cmp_rev HEAD~1 refs/heads/third &&\n+\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"458881","messageId":"29c7c76805abb9b8afd44a65cb39d8f9d3c95892.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 09/12] sequencer: rewrite update-refs as user edits todo list","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:07:02Z","receivedAt":"2022-07-12T13:07:53Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nAn interactive rebase provides opportunities for the user to edit the\ntodo list. The --update-refs option initializes the list with some\n'update-ref <ref>' steps, but the user could add these manually.\nFurther, the user could add or remove these steps during pauses in the\ninteractive rebase.\n\nAdd a new method, todo_list_filter_update_refs(), that scans a todo_list\nand compares it to the stored update-refs file. There are two actions\nthat can happen at this point:\n\n1. If a '<ref>/<before>/<after>' triple in the update-refs file does not\n   have a matching 'update-ref <ref>' command in the todo-list _and_ the\n   <after> value is the null OID, then remove that triple. Here, the\n   user removed the 'update-ref <ref>' command before it was executed,\n   since if it was executed then the <after> value would store the\n   commit at that position.\n\n2. If a 'update-ref <ref>' command in the todo-list does not have a\n   matching '<ref>/<before>/<after>' triple in the update-refs file,\n   then insert a new one. Store the <before> value to be the current\n   OID pointed at by <ref>. This is handled inside of the\n   init_update_ref_record() helper method.\n\nWe can test that this works by rewriting the todo-list several times in\nthe course of a rebase. Check that each ref is locked or unlocked for\nupdates after each todo-list update. We an also verify that the ref\nupdate fails if a concurrent process updates one of the refs after the\nrebase process records the \"locked\" ref location.\n\nTo help these tests, add a new 'set_replace_editor' helper that will\nreplace the todo-list with an exact file.\n\nReported-by: Phillip Wood <phillip.wood123@gmail.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c          |   6 ++\n sequencer.c                   |  96 +++++++++++++++++++++++\n sequencer.h                   |  12 +++\n t/lib-rebase.sh               |  15 ++++\n t/t3404-rebase-interactive.sh | 139 ++++++++++++++++++++++++++++++++++\n 5 files changed, 268 insertions(+)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 1ff07647af3..7407c593191 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -146,6 +146,12 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\treturn -4;\n \t}\n \n+\t/*\n+\t * See if branches need to be added or removed from the update-refs\n+\t * file based on the new todo list.\n+\t */\n+\ttodo_list_filter_update_refs(r, new_todo);\n+\n \treturn 0;\n }\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 2d89b3b727a..2808c027d68 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4141,6 +4141,102 @@ cleanup:\n \treturn result;\n }\n \n+/*\n+ * Parse the update-refs file for the current rebase, then remove the\n+ * refs that do not appear in the todo_list (and have not had updated\n+ * values stored) and add refs that are in the todo_list but not\n+ * represented in the update-refs file.\n+ *\n+ * If there are changes to the update-refs list, then write the new state\n+ * to disk.\n+ */\n+void todo_list_filter_update_refs(struct repository *r,\n+\t\t\t\t  struct todo_list *todo_list)\n+{\n+\tint i;\n+\tint updated = 0;\n+\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n+\n+\tsequencer_get_update_refs_state(r->gitdir, &update_refs);\n+\n+\t/*\n+\t * For each item in the update_refs list, if it has no updated\n+\t * value and does not appear in the todo_list, then remove it\n+\t * from the update_refs list.\n+\t */\n+\tfor (i = 0; i < update_refs.nr; i++) {\n+\t\tint j;\n+\t\tint found = 0;\n+\t\tconst char *ref = update_refs.items[i].string;\n+\t\tsize_t reflen = strlen(ref);\n+\t\tstruct update_ref_record *rec = update_refs.items[i].util;\n+\n+\t\t/* OID already stored as updated. */\n+\t\tif (!is_null_oid(&rec->after))\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; !found && j < todo_list->total_nr; j++) {\n+\t\t\tstruct todo_item *item = &todo_list->items[j];\n+\t\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n+\n+\t\t\tif (item->command != TODO_UPDATE_REF)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (item->arg_len != reflen ||\n+\t\t\t    strncmp(arg, ref, reflen))\n+\t\t\t\tcontinue;\n+\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found) {\n+\t\t\tfree(update_refs.items[i].string);\n+\t\t\tfree(update_refs.items[i].util);\n+\n+\t\t\tupdate_refs.nr--;\n+\t\t\tMOVE_ARRAY(update_refs.items + i, update_refs.items + i + 1, update_refs.nr - i);\n+\n+\t\t\tupdated = 1;\n+\t\t\ti--;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * For each todo_item, check if its ref is in the update_refs list.\n+\t * If not, then add it as an un-updated ref.\n+\t */\n+\tfor (i = 0; i < todo_list->total_nr; i++) {\n+\t\tstruct todo_item *item = &todo_list->items[i];\n+\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n+\t\tint j, found = 0;\n+\n+\t\tif (item->command != TODO_UPDATE_REF)\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; !found && j < update_refs.nr; j++) {\n+\t\t\tconst char *ref = update_refs.items[j].string;\n+\n+\t\t\tfound = strlen(ref) == item->arg_len &&\n+\t\t\t\t!strncmp(ref, arg, item->arg_len);\n+\t\t}\n+\n+\t\tif (!found) {\n+\t\t\tstruct string_list_item *inserted;\n+\t\t\tstruct strbuf argref = STRBUF_INIT;\n+\n+\t\t\tstrbuf_add(&argref, arg, item->arg_len);\n+\t\t\tinserted = string_list_insert(&update_refs, argref.buf);\n+\t\t\tinserted->util = init_update_ref_record(argref.buf);\n+\t\t\tstrbuf_release(&argref);\n+\t\t\tupdated = 1;\n+\t\t}\n+\t}\n+\n+\tif (updated)\n+\t\twrite_update_refs_state(&update_refs);\n+\tstring_list_clear(&update_refs, 1);\n+}\n+\n static int do_update_ref(struct repository *r, const char *refname)\n {\n \tstruct string_list_item *item;\ndiff --git a/sequencer.h b/sequencer.h\nindex e671d7e0743..98f3bcc1658 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -132,6 +132,18 @@ void todo_list_release(struct todo_list *todo_list);\n const char *todo_item_get_arg(struct todo_list *todo_list,\n \t\t\t      struct todo_item *item);\n \n+/*\n+ * Parse the update-refs file for the current rebase, then remove the\n+ * refs that do not appear in the todo_list (and have not had updated\n+ * values stored) and add refs that are in the todo_list but not\n+ * represented in the update-refs file.\n+ *\n+ * If there are changes to the update-refs list, then write the new state\n+ * to disk.\n+ */\n+void todo_list_filter_update_refs(struct repository *r,\n+\t\t\t\t  struct todo_list *todo_list);\n+\n /* Call this to setup defaults before parsing command line options */\n void sequencer_init_config(struct replay_opts *opts);\n int sequencer_pick_revisions(struct repository *repo,\ndiff --git a/t/lib-rebase.sh b/t/lib-rebase.sh\nindex ec6b9b107da..b57541356bd 100644\n--- a/t/lib-rebase.sh\n+++ b/t/lib-rebase.sh\n@@ -207,3 +207,18 @@ check_reworded_commits () {\n \t\t>reword-log &&\n \ttest_cmp reword-expected reword-log\n }\n+\n+# usage: set_replace_editor <file>\n+#\n+# Replace the todo file with the exact contents of the given file.\n+set_replace_editor () {\n+\tcat >script <<-\\EOF &&\n+\tcat FILENAME >\"$1\"\n+\n+\techo 'rebase -i script after editing:'\n+\tcat \"$1\"\n+\tEOF\n+\n+\tsed -e \"s/FILENAME/$1/g\" <script | write_script fake-editor.sh &&\n+\ttest_set_editor \"$(pwd)/fake-editor.sh\"\n+}\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex a37820fa728..7bfbd405ab8 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1830,6 +1830,145 @@ test_expect_success '--update-refs updates refs correctly' '\n \ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n '\n \n+test_expect_success 'respect user edits to update-ref steps' '\n+\tgit checkout -B update-refs-break no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit branch -f unseen base &&\n+\n+\t# First, we will add breaks to the expected todo file\n+\tcat >fake-todo-1 <<-EOF &&\n+\tpick $(git rev-parse HEAD~3)\n+\tbreak\n+\tupdate-ref refs/heads/second\n+\tupdate-ref refs/heads/first\n+\n+\tpick $(git rev-parse HEAD~2)\n+\tpick $(git rev-parse HEAD~1)\n+\tupdate-ref refs/heads/third\n+\n+\tpick $(git rev-parse HEAD)\n+\tupdate-ref refs/heads/no-conflict-branch\n+\tEOF\n+\n+\t# Second, we will drop some update-refs commands (and move one)\n+\tcat >fake-todo-2 <<-EOF &&\n+\tupdate-ref refs/heads/second\n+\n+\tpick $(git rev-parse HEAD~2)\n+\tupdate-ref refs/heads/third\n+\tpick $(git rev-parse HEAD~1)\n+\tbreak\n+\n+\tpick $(git rev-parse HEAD)\n+\tEOF\n+\n+\t# Third, we will:\n+\t# * insert a new one (new-branch),\n+\t# * re-add an old one (first), and\n+\t# * add a second instance of a previously-stored one (second)\n+\tcat >fake-todo-3 <<-EOF &&\n+\tupdate-ref refs/heads/unseen\n+\tupdate-ref refs/heads/new-branch\n+\tpick $(git rev-parse HEAD)\n+\tupdate-ref refs/heads/first\n+\tupdate-ref refs/heads/second\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor fake-todo-1 &&\n+\t\tgit rebase -i --update-refs primary &&\n+\n+\t\t# These branches are currently locked.\n+\t\tfor b in first second third no-conflict-branch\n+\t\tdo\n+\t\t\ttest_must_fail git branch -f $b base || return 1\n+\t\tdone &&\n+\n+\t\tset_replace_editor fake-todo-2 &&\n+\t\tgit rebase --edit-todo &&\n+\n+\t\t# These branches are currently locked.\n+\t\tfor b in second third\n+\t\tdo\n+\t\t\ttest_must_fail git branch -f $b base || return 1\n+\t\tdone &&\n+\n+\t\t# These branches are currently unlocked for checkout.\n+\t\tfor b in first no-conflict-branch\n+\t\tdo\n+\t\t\tgit worktree add wt-$b $b &&\n+\t\t\tgit worktree remove wt-$b || return 1\n+\t\tdone &&\n+\n+\t\tgit rebase --continue &&\n+\n+\t\tset_replace_editor fake-todo-3 &&\n+\t\tgit rebase --edit-todo &&\n+\n+\t\t# These branches are currently locked.\n+\t\tfor b in second third first unseen\n+\t\tdo\n+\t\t\ttest_must_fail git branch -f $b base || return 1\n+\t\tdone &&\n+\n+\t\t# These branches are currently unlocked for checkout.\n+\t\tfor b in no-conflict-branch\n+\t\tdo\n+\t\t\tgit worktree add wt-$b $b &&\n+\t\t\tgit worktree remove wt-$b || return 1\n+\t\tdone &&\n+\n+\t\tgit rebase --continue\n+\t) &&\n+\n+\ttest_cmp_rev HEAD~2 refs/heads/third &&\n+\ttest_cmp_rev HEAD~1 refs/heads/unseen &&\n+\ttest_cmp_rev HEAD~1 refs/heads/new-branch &&\n+\ttest_cmp_rev HEAD refs/heads/first &&\n+\ttest_cmp_rev HEAD refs/heads/second &&\n+\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n+'\n+\n+test_expect_success '--update-refs: check failed ref update' '\n+\tgit checkout -B update-refs-error no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~2 &&\n+\tgit branch -f third HEAD~1 &&\n+\n+\tcat >fake-todo <<-EOF &&\n+\tpick $(git rev-parse HEAD~3)\n+\tbreak\n+\tupdate-ref refs/heads/first\n+\n+\tpick $(git rev-parse HEAD~2)\n+\tupdate-ref refs/heads/second\n+\n+\tpick $(git rev-parse HEAD~1)\n+\tupdate-ref refs/heads/third\n+\n+\tpick $(git rev-parse HEAD)\n+\tupdate-ref refs/heads/no-conflict-branch\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor fake-todo &&\n+\t\tgit rebase -i --update-refs base\n+\t) &&\n+\n+\t# At this point, the values of first, second, and third are\n+\t# recorded in the update-refs file. We will force-update the\n+\t# \"second\" ref, but \"git branch -f\" will not work because of\n+\t# the lock in the update-refs file.\n+\tgit rev-parse third >.git/refs/heads/second &&\n+\n+\tgit rebase --continue 2>err &&\n+\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"458882","messageId":"d53b4ff2cee0ca518ab4958a29e22602511f3691.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 11/12] sequencer: ignore HEAD ref under --update-refs","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:07:04Z","receivedAt":"2022-07-12T13:07:57Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen using the 'git rebase -i --update-refs' option, the todo list is\npopulated with 'update-ref' commands for all tip refs in the history\nthat is being rebased. Refs that are checked out by some worktree are\ninstead added as a comment to warn the user that they will not be\nupdated.\n\nUntil now, this included the HEAD ref, which is being updated by the\nrebase process itself, regardless of the --update-refs option. Remove\nthe comment in this case by ignoring any decorations that match the HEAD\nref.\n\nReported-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c | 13 +++++++++++++\n 1 file changed, 13 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 2808c027d68..82ef062d497 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5855,12 +5855,25 @@ static int add_decorations_to_list(const struct commit *commit,\n \t\t\t\t   struct todo_add_branch_context *ctx)\n {\n \tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n+\tconst char *head_ref = resolve_ref_unsafe(\"HEAD\",\n+\t\t\t\t\t\t  RESOLVE_REF_READING,\n+\t\t\t\t\t\t  NULL,\n+\t\t\t\t\t\t  NULL);\n \n \twhile (decoration) {\n \t\tstruct todo_item *item;\n \t\tconst char *path;\n \t\tsize_t base_offset = ctx->buf->len;\n \n+\t\t/*\n+\t\t * If the branch is the current HEAD, then it will be\n+\t\t * updated by the default rebase behavior.\n+\t\t */\n+\t\tif (head_ref && !strcmp(head_ref, decoration->name)) {\n+\t\t\tdecoration = decoration->next;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tALLOC_GROW(ctx->items,\n \t\t\tctx->items_nr + 1,\n \t\t\tctx->items_alloc);\n-- \ngitgitgadget\n\n"},{"id":"458883","messageId":"c0022d075790da3d917381c4fa7128c972aa923d.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 10/12] rebase: add rebase.updateRefs config option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:07:03Z","receivedAt":"2022-07-12T13:07:59Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change added the --update-refs command-line option.  For\nusers who always want this mode, create the rebase.updateRefs config\noption which behaves the same way as rebase.autoSquash does with the\n--autosquash option.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/config/rebase.txt |  3 +++\n Documentation/git-rebase.txt    |  3 +++\n builtin/rebase.c                |  5 +++++\n t/t3404-rebase-interactive.sh   | 14 ++++++++++++++\n 4 files changed, 25 insertions(+)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex 8c979cb20f2..f19bd0e0407 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -21,6 +21,9 @@ rebase.autoStash::\n \t`--autostash` options of linkgit:git-rebase[1].\n \tDefaults to false.\n \n+rebase.updateRefs::\n+\tIf set to true enable `--update-refs` option by default.\n+\n rebase.missingCommitsCheck::\n \tIf set to \"warn\", git rebase -i will print a warning if some\n \tcommits are removed (e.g. a line was deleted), however the\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex e7611b4089c..1249f9ed617 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -615,6 +615,9 @@ start would be overridden by the presence of\n \tare being rebased. Any branches that are checked out in a worktree\n \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n \tin this way.\n++\n+If the configuration variable `rebase.updateRefs` is set, then this option\n+can be used to override and disable this setting.\n \n INCOMPATIBLE OPTIONS\n --------------------\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 56d82a52106..8ebc98ea505 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -802,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"rebase.updaterefs\")) {\n+\t\topts->update_refs = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"rebase.reschedulefailedexec\")) {\n \t\topts->reschedule_failed_exec = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 7bfbd405ab8..4b7b77a4123 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1768,6 +1768,12 @@ test_expect_success '--update-refs adds label and update-ref commands' '\n \t\tEOF\n \n \t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n@@ -1809,6 +1815,14 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t\t\t\t   --rebase-merges=rebase-cousins \\\n \t\t\t\t   --update-refs primary >todo &&\n \n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n-- \ngitgitgadget\n\n"},{"id":"458884","messageId":"d5cd4b49e46bc2c186c6e89333360a975700c99a.1657631226.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v4 12/12] sequencer: notify user of --update-refs activity","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-12T13:07:05Z","receivedAt":"2022-07-12T13:08:10Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen the user runs 'git rebase -i --update-refs', the end message still\nsays only\n\n  Successfully rebased and updated <HEAD-ref>.\n\nUpdate the sequencer to collect the successful (and unsuccessful) ref\nupdates due to the --update-refs option, so the end message now says\n\n  Successfully rebased and updated <HEAD-ref>.\n  Updated the following refs with --update-refs:\n\trefs/heads/first\n\trefs/heads/third\n  Failed to update the following refs with --update-refs:\n\trefs/heads/second\n\nTo test this output, we need to be very careful to format the expected\nerror to drop the leading tab characters. Also, we need to be aware that\nthe verbose output from 'git rebase' is writing progress lines which\ndon't use traditional newlines but clear the line after every progress\nitem is complete. When opening the error file in an editor, these lines\nare visible, but when looking at the diff in a terminal those lines\ndisappear because of the characters that delete the previous characters.\nUse 'sed' to clear those progress lines and clear the tabs so we can get\nan exact match on our expected output.\n\nReported-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c                   | 40 +++++++++++++++++++++++++++++------\n t/t3404-rebase-interactive.sh | 35 +++++++++++++++++++++++++++---\n 2 files changed, 66 insertions(+), 9 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 82ef062d497..bdc67c66f3e 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4257,17 +4257,20 @@ static int do_update_ref(struct repository *r, const char *refname)\n \treturn 0;\n }\n \n-static int do_update_refs(struct repository *r)\n+static int do_update_refs(struct repository *r, int quiet)\n {\n \tint res = 0;\n \tstruct string_list_item *item;\n \tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n \tstruct ref_store *refs = get_main_ref_store(r);\n+\tstruct strbuf update_msg = STRBUF_INIT;\n+\tstruct strbuf error_msg = STRBUF_INIT;\n \n \tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n \n \tfor_each_string_list_item(item, &refs_to_oids) {\n \t\tstruct update_ref_record *rec = item->util;\n+\t\tint loop_res;\n \n \t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n \t\t\t/*\n@@ -4277,13 +4280,38 @@ static int do_update_refs(struct repository *r)\n \t\t\tcontinue;\n \t\t}\n \n-\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n-\t\t\t\t       item->string,\n-\t\t\t\t       &rec->after, &rec->before,\n-\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tloop_res = refs_update_ref(refs, \"rewritten during rebase\",\n+\t\t\t\t\t   item->string,\n+\t\t\t\t\t   &rec->after, &rec->before,\n+\t\t\t\t\t   0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tres |= loop_res;\n+\n+\t\tif (quiet)\n+\t\t\tcontinue;\n+\n+\t\tif (loop_res)\n+\t\t\tstrbuf_addf(&error_msg, \"\\t%s\\n\", item->string);\n+\t\telse\n+\t\t\tstrbuf_addf(&update_msg, \"\\t%s\\n\", item->string);\n+\t}\n+\n+\tif (!quiet &&\n+\t    (update_msg.len || error_msg.len)) {\n+\t\tfprintf(stderr,\n+\t\t\t_(\"Updated the following refs with %s:\\n%s\"),\n+\t\t\t\"--update-refs\",\n+\t\t\tupdate_msg.buf);\n+\n+\t\tif (res)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Failed to update the following refs with %s:\\n%s\"),\n+\t\t\t\t\"--update-refs\",\n+\t\t\t\terror_msg.buf);\n \t}\n \n \tstring_list_clear(&refs_to_oids, 1);\n+\tstrbuf_release(&update_msg);\n+\tstrbuf_release(&error_msg);\n \treturn res;\n }\n \n@@ -4804,7 +4832,7 @@ cleanup_head_ref:\n \t\tstrbuf_release(&head_ref);\n \t}\n \n-\tdo_update_refs(r);\n+\tdo_update_refs(r, opts->quiet);\n \n \t/*\n \t * Sequence of picks finished successfully; cleanup by\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 4b7b77a4123..ef902b5431f 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1836,12 +1836,26 @@ test_expect_success '--update-refs updates refs correctly' '\n \ttest_commit extra2 fileX &&\n \tgit commit --amend --fixup=L &&\n \n-\tgit rebase -i --autosquash --update-refs primary &&\n+\tgit rebase -i --autosquash --update-refs primary 2>err &&\n \n \ttest_cmp_rev HEAD~3 refs/heads/first &&\n \ttest_cmp_rev HEAD~3 refs/heads/second &&\n \ttest_cmp_rev HEAD~1 refs/heads/third &&\n-\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n+\ttest_cmp_rev HEAD refs/heads/no-conflict-branch &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tSuccessfully rebased and updated refs/heads/update-refs.\n+\tUpdated the following refs with --update-refs:\n+\t\trefs/heads/first\n+\t\trefs/heads/no-conflict-branch\n+\t\trefs/heads/second\n+\t\trefs/heads/third\n+\tEOF\n+\n+\t# Clear \"Rebasing (X/Y)\" progress lines and drop leading tabs.\n+\tsed -e \"s/Rebasing.*Successfully/Successfully/g\" -e \"s/^\\t//g\" \\\n+\t\t<err >err.trimmed &&\n+\ttest_cmp expect err.trimmed\n '\n \n test_expect_success 'respect user edits to update-ref steps' '\n@@ -1980,7 +1994,22 @@ test_expect_success '--update-refs: check failed ref update' '\n \tgit rev-parse third >.git/refs/heads/second &&\n \n \tgit rebase --continue 2>err &&\n-\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n+\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tUpdated the following refs with --update-refs:\n+\t\trefs/heads/first\n+\t\trefs/heads/no-conflict-branch\n+\t\trefs/heads/third\n+\tFailed to update the following refs with --update-refs:\n+\t\trefs/heads/second\n+\tEOF\n+\n+\t# Clear \"Rebasing (X/Y)\" progress lines and drop leading tabs.\n+\ttail -n 6 err >err.last &&\n+\tsed -e \"s/Rebasing.*Successfully/Successfully/g\" -e \"s/^\\t//g\" \\\n+\t\t<err.last >err.trimmed &&\n+\ttest_cmp expect err.trimmed\n '\n \n # This must be the last test in this file\n-- \ngitgitgadget\n"},{"id":"458894","messageId":"xmqq8royibor.fsf@gitster.g","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/12] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-12T15:37:56Z","receivedAt":"2022-07-12T15:38:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series is based on ds/branch-checked-out.\n> ...\n> Updates in v4\n> =============\n>\n> This version took longer than I'd hoped (I had less time to work on it than\n> anticipated) but it also has some major updates. These major updates are\n> direct responses to the significant review this series has received. Thank\n> you!\n>\n>  * The update-refs file now stores \"ref/before/after\" triples (still\n>    separated by lines). This allows us to store the \"before\" OID of a ref in\n>    addition to the \"after\" that we will write to that ref at the end of the\n>    rebase. This allows us to do a \"force-with-lease\" update. The\n>    branch_checked_out() updates should prevent Git from updating those refs\n>    while under the rebase, but older versions and third-party tools don't\n>    have that protection.\n\nNice.\n\n>  * The update-refs file is updated with every update to the todo-list file.\n>    This allows for some advanced changes to the file, including removing,\n>    adding, and duplicating 'update-ref' commands.\n>  * The message at the end of the rebase process now lists which refs were\n>    updated with the update-ref steps. This includes any ref updates that\n>    fail.\n>  * The branch_checked_out() tests now use 'git bisect' and 'git rebase' as\n>    black-boxes instead of testing their internals directly.\n>\n> Here are the more minor updates:\n>\n>  * Dropped an unnecessary stat() call.\n>  * Updated commit messages to include extra details, based on confusion in\n>    last round.\n>  * The HEAD branch no longer appears as a comment line in the initial todo\n>    list.\n>  * The update-refs file is now written using a lockfile.\n>  * Tests now use test_cmp_rev.\n>  * A memory leak ('path' variable) is resolved.\n\nInteresting.\n\nThis is a tangent, but may serve as some food for thought.\n\nWhen I queue (or develop myself) a topic that depends on another\ntopic, I often do\n\n    $ git checkout --detach vX.Y.Z ;# choose an appropriate base\n    $ git merge --into derived-topic base-topic\n    $ develop develop (or \"git am\")\n\nwhich would end up in\n\n    vX.Y.Z -----M---A---B---C derived-topic\n               /\n     base-topic\n\nso that \"git log --first-parent --oneline master..\" would show the\ncommits in the topic plus the fact that it depends on which other\ntopic recorded in a single merge commit.  A topic that depends on\ntwo or more topics can be handled the same way.\n\nOne good thing about this arrangement, unlike the \"totally linear\"\none depicted at the top of your cover letter, is that it is easier\nto rebuild each topic independently and the first-parent view is\nstill useful.  If you futz with the base topic in a totally linear\nhistory, \"log --decorate\" of the derived topic would no longer tell\nyou where the old iteration of the base topic ended.\n\nIt would be very nice to see the update-ref feature (or something\nlike that) makes it easy to deal with such a topology, too.\n\n"},{"id":"459045","messageId":"73311eba-244b-3c88-d0b9-f97f426f1de6@github.com","threadId":"57949","inReplyTo":"xmqq8royibor.fsf@gitster.g","subject":"Re: [PATCH v4 00/12] rebase: update branches in multi-part topic","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-14T14:50:56Z","receivedAt":"2022-07-14T14:51:05Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/12/22 11:37 AM, Junio C Hamano wrote:> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> This is a tangent, but may serve as some food for thought.\n> \n> When I queue (or develop myself) a topic that depends on another\n> topic, I often do\n> \n>     $ git checkout --detach vX.Y.Z ;# choose an appropriate base\n>     $ git merge --into derived-topic base-topic\n>     $ develop develop (or \"git am\")\n> \n> which would end up in\n> \n>     vX.Y.Z -----M---A---B---C derived-topic\n>                /\n>      base-topic\n> \n> so that \"git log --first-parent --oneline master..\" would show the\n> commits in the topic plus the fact that it depends on which other\n> topic recorded in a single merge commit.  A topic that depends on\n> two or more topics can be handled the same way.\n> \n> One good thing about this arrangement, unlike the \"totally linear\"\n> one depicted at the top of your cover letter, is that it is easier\n> to rebuild each topic independently and the first-parent view is\n> still useful.  If you futz with the base topic in a totally linear\n> history, \"log --decorate\" of the derived topic would no longer tell\n> you where the old iteration of the base topic ended.\n> \n> It would be very nice to see the update-ref feature (or something\n> like that) makes it easy to deal with such a topology, too.\n\nYour topology is an interesting one, but --update-refs isn't limited to\nlinear history.\n\n(Begin aside)\n\nOne goal of this feature is to make it easier to manage the topics that\nare being juggled in friendly forks. For example, git-for-windows/git and\nmicrosoft/git have some topics that evolve version-to-version but might be\ncleaned up and sent upstream. Both of these forks use 'git rebase\n--rebase-merges=rebase-cousins' when consuming new upstream versions to\nkeep the merge structure of these new topics. However, we lose the branch\nnames and need to reconstruct them from the context of the merge commits.\n\nWith --update-refs, we can automatically rewrite the branches that are\nincluded in these individual topics. That might make it simpler to extract\na series to send upstream.\n\nOne test in this series does test such a case with the --rebase-merges\noption.\n\n(End aside)\n\nBack to your topology, I wonder what your rebase command looks like when\ntracking those topics.\n\nThe goal of --update-refs is to help rebase multiple branches at the same\ntime, and with your example here, it would imply you want to rebase both\ndependent topics.\n\n Before:\n\n  A---B----C---M---Q1---Q2---Q3 <-- refs/heads/Q\n   \\          /\n    P1--P2--P3 <-- refs/heads/P\n\n After rebasing both topics simultaneously (with 'git rebase --update-refs\n C' while Q is checked out):\n\n  A---B---C---D---P1---P2---P3---Q1---Q2---Q3\n\t\t\t    ^              ^\n\t\t\trefs/heads/P  refs/heads/Q\n\nBut it seems what you mean to say is to update the merge commit M, which\nmeans that the 'P' branch above has been updated independently of the 'Q'\nbranch, so we need to update 'Q' after-the-fact. I'm not sure what that\nrebase would look like, indepdendent of updating refs.\n\nDo you have an example rebase command that manipulates the commits the\nway you want? Then I can better understand how the --update-refs could fit\nin with that. (Or maybe the point of your tangent is that there isn't an\noption.)\n\nIf instead we thought about an example like re-rolling the 'next' branch\nentirely on top of the 'master' branch, then we have an example that is\ncloser to the friendly fork example. (I know this isn't a realistic\nscenario since we don't rewrite the commits already merged to 'next', but\nit's an interesting stress test.)\n\nWhile having 'next' checked out, I ran\n\n  git rebase -i --update-refs --rebase-merges=rebase-cousins master\n\nand it updated all of the branches currently in the region master..next.\nI've attached the output of\n\n  git -c log.excludeDecoration=\"refs/remotes/*\" \\\n        log --oneline --graph --boundary master..next\n\njust to show what this looks like.\n\nThanks,\n-Stolee\n\n*   bdf2d8e9e1 Merge branch 'll/curl-accept-language' into next\n|\\  \n| * b0c4adcdd7 remote-curl: send Accept-Language header to server\n* |   36fcf76053 Merge branch 'jk/diff-files-cleanup-fix' into next\n|\\ \\  \n| * | 04393ae7f7 diff-files: move misplaced cleanup label\n* | |   3f6ccaf58c Merge branch 'rs/cocci-array-copy' into next\n|\\ \\ \\  \n| * | | dc574132a0 cocci: avoid normalization rules for memcpy\n* | | |   2125d3847d Merge branch 'jk/ref-filter-discard-commit-buffer' into next\n|\\ \\ \\ \\  \n| * | | | 359b01ca84 ref-filter: disable save_commit_buffer while traversing\n* | | | |   fd0e10a90f Merge branch 'jk/clone-unborn-confusion' into next\n|\\ \\ \\ \\ \\  \n| * | | | | 2eb5d38386 clone: move unborn head creation to update_head()\n| * | | | | 73a0858224 clone: use remote branch if it matches default HEAD\n| * | | | | 54a0c1896c clone: propagate empty remote HEAD even with other branches\n| * | | | | 51fa61ba3e clone: drop extra newline from warning message\n| | |/ / /  \n| |/| | |   \n* | | | |   05d6fbe0e5 Merge branch 'hx/lookup-commit-in-graph-fix' into next\n|\\ \\ \\ \\ \\  \n| * | | | | 854d62d9fe t5330: remove run_with_limited_processses()\n* | | | | |   5386228a28 Merge branch 'jc/resolve-undo' into next\n|\\ \\ \\ \\ \\ \\  \n| * | | | | | b237e81516 fsck: do not dereference NULL while checking resolve-undo data\n* | | | | | |   a34a4435d9 Merge branch 'sg/multi-pack-index-parse-options-fix' into next\n|\\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | cc74afb83f multi-pack-index: simplify handling of unknown --options\n| | |_|_|/ / /  \n| |/| | | | |   \n* | | | | | |   57b36b2e9b Merge branch 'bc/nettle-sha256' into next\n|\\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | e555735836 sha256: add support for Nettle\n| |/ / / / / /  \n* | | | | | |   11cf608d10 Merge branch 'kk/p4-client-name-encoding-fix' into next\n|\\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | 34f67c9619 git-p4: fix bug with encoding of p4 client name\n| |/ / / / / /  \n* | | | | | |   99a9342263 Merge branch 'jd/gpg-interface-trust-level-string' into next\n|\\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | 803978da49 gpg-interface: add function for converting trust level to string\n| |/ / / / / /  \n* | | | | | |   47942e9ac7 Merge branch 'ab/cocci-unused' into next\n|\\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | 06f5f8940c cocci: generalize \"unused\" rule to cover more than \"strbuf\"\n| * | | | | | | 4f40f6cb73 cocci: add and apply a rule to find \"unused\" strbufs\n| * | | | | | | 7a9a10b10e cocci: have \"coccicheck{,-pending}\" depend on \"coccicheck-test\"\n| * | | | | | | f7ff6597a7 cocci: add a \"coccicheck-test\" target and test *.cocci rules\n| * | | | | | | af0aa6904b Makefile & .gitignore: ignore & clean \"git.res\", not \"*.res\"\n| * | | | | | | 7b63ea5750 Makefile: remove mandatory \"spatch\" arguments from SPATCH_FLAGS\n* | | | | | | |   9720950472 Merge branch 'gc/submodule-use-super-prefix' into next\n|\\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | 5ad87271cf submodule--helper: remove display path helper\n| * | | | | | | | d7a714fddc submodule--helper update: use --super-prefix\n| * | | | | | | | b0f8b21305 submodule--helper: remove unused SUPPORT_SUPER_PREFIX flags\n| * | | | | | | | 58cec298f1 submodule--helper: use correct display path helper\n| * | | | | | | | cb49e1e8d3 submodule--helper: don't recreate recursive prefix\n| * | | | | | | | 618b8445d9 submodule--helper update: use display path helper\n| * | | | | | | | 8fc36c39d9 submodule--helper tests: add missing \"display path\" coverage\n| * | | | | | | |   c9e221b124 Merge branch 'ab/submodule-cleanup' into gc/submodule-use-super-prefix\n| |\\ \\ \\ \\ \\ \\ \\ \\  \n| | |_|/ / / / / /  \n| |/| | | | | | |   \n* | | | | | | | |   172d4d2a2b Merge branch 'en/merge-dual-dir-renames-fix' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | 8fe673ee1d merge-ort: fix issue with dual rename and add/add conflict\n| * | | | | | | | | 2ef5b00eba merge-ort: shuffle the computation and cleanup of potential collisions\n| * | | | | | | | | b3468c2055 merge-ort: make a separate function for freeing struct collisions\n| * | | | | | | | | a4719d9e46 merge-ort: small cleanups of check_for_directory_rename\n| * | | | | | | | | 70fae629e6 t6423: add tests of dual directory rename plus add/add conflict\n| | |_|_|_|_|/ / /  \n| |/| | | | | | |   \n* | | | | | | | |   9fc51f4bdd Merge branch 'ab/test-without-templates' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | c8ab2aa9ce tests: don't assume a .git/info for .git/info/sparse-checkout\n| * | | | | | | | | 913769a360 tests: don't assume a .git/info for .git/info/exclude\n| * | | | | | | | | af4eb37df7 tests: don't assume a .git/info for .git/info/refs\n| * | | | | | | | | 5383f7f9de tests: don't assume a .git/info for .git/info/attributes\n| * | | | | | | | | dc7e8c3b3a tests: don't assume a .git/info for .git/info/grafts\n| * | | | | | | | | fdc489c66c tests: don't depend on template-created .git/branches\n| * | | | | | | | | 7a1cb29f61 t0008: don't rely on default \".git/info/exclude\"\n| |/ / / / / / / /  \n* | | | | | | | |   8d446e7df8 Merge branch 'ab/build-gitweb' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | a35258c62a gitweb/Makefile: add a \"NO_GITWEB\" parameter\n| * | | | | | | | | d3b827408c Makefile: build 'gitweb' in the default target\n| * | | | | | | | | affc3b755c gitweb/Makefile: include in top-level Makefile\n| * | | | | | | | | 27438ef5e0 gitweb: remove \"test\" and \"test-installed\" targets\n| * | | | | | | | | b82d66eb0c gitweb/Makefile: prepare to merge into top-level Makefile\n| * | | | | | | | | 564ebde3d3 gitweb/Makefile: clear up and de-duplicate the gitweb.{css,js} vars\n| * | | | | | | | | 1e08fa5e2b gitweb/Makefile: add a $(GITWEB_ALL) variable\n| * | | | | | | | | 7decdb9b4a gitweb/Makefile: define all .PHONY prerequisites inline\n* | | | | | | | | |   3ab689137a Merge branch 'ab/test-tool-leakfix' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | | f40a693450 test-tool delta: fix a memory leak\n| * | | | | | | | | | 34e691288d test-tool ref-store: fix a memory leak\n| * | | | | | | | | | 9794633b4e test-tool bloom: fix memory leaks\n| * | | | | | | | | | 1caaa858cc test-tool json-writer: fix memory leaks\n| * | | | | | | | | | a20b0dc796 test-tool regex: call regfree(), fix memory leaks\n| * | | | | | | | | | 1c343e5aef test-tool urlmatch-normalization: fix a memory leak\n| * | | | | | | | | | 9afa46d4a6 test-tool {dump,scrap}-cache-tree: fix memory leaks\n| * | | | | | | | | | e287a5b0a4 test-tool path-utils: fix a memory leak\n| * | | | | | | | | | 330ca8501b test-tool test-hash: fix a memory leak\n| | |_|/ / / / / / /  \n| |/| | | | | | | |   \n* | | | | | | | | |   b9183a05c5 Merge branch 'ab/leakfix' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | | ece3974ba6 pull: fix a \"struct oid_array\" memory leak\n| * | | | | | | | | | 27472b5195 cat-file: fix a common \"struct object_context\" memory leak\n| * | | | | | | | | | 55916bba0f gc: fix a memory leak\n| * | | | | | | | | | 33d0dda633 checkout: avoid \"struct unpack_trees_options\" leak\n| * | | | | | | | | | e72e12cc02 merge-file: fix memory leaks on error path\n| * | | | | | | | | | 480a0e30a7 merge-file: refactor for subsequent memory leak fix\n| * | | | | | | | | | d90dafbe31 cat-file: fix a memory leak in --batch-command mode\n| * | | | | | | | | | fd74ac95ac revert: free \"struct replay_opts\" members\n| * | | | | | | | | | bc57ba1d54 submodule.c: free() memory from xgetcwd()\n| * | | | | | | | | | 74a06a9f21 clone: fix memory leak in wanted_peer_refs()\n| * | | | | | | | | | 99b6c45d8f check-ref-format: fix trivial memory leak\n| |/ / / / / / / / /  \n* | | | | | | | | |   542f8fec7e Merge branch 'jc/builtin-mv-move-array' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | | 4901a36b4b builtin/mv.c: use the MOVE_ARRAY() macro instead of memmove()\n| | |_|/ / / / / / /  \n| |/| | | | | | | |   \n* | | | | | | | | |   1b5d8c02cf Merge branch 'fr/vimdiff-layout-fix' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | | a62efbaabb vimdiff: make layout engine more robust against user vim settings\n| |/ / / / / / / / /  \n* | | | | | | | | |   f022414e31 Merge branch 'hx/lookup-commit-in-graph-fix' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| | |_|_|_|_|_|/ / /  \n| |/| | | | | | | |   \n| * | | | | | | | | a4d60a6221 commit-graph.c: no lazy fetch in lookup_commit_in_graph()\n| |/ / / / / / / /  \n* | | | | | | | |   90790c4455 Merge branch 'ab/submodule-cleanup' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\  \n| | |_|_|/ / / / /  \n| |/| | | | | | |   \n| * | | | | | | | 5b893f7d81 git-sh-setup.sh: remove \"say\" function, change last users\n| * | | | | | | | 2eec463739 git-submodule.sh: use \"$quiet\", not \"$GIT_QUIET\"\n| * | | | | | | | b788fc671b submodule--helper: eliminate internal \"--update\" option\n| * | | | | | | | 8f12108c29 submodule--helper: understand --checkout, --merge and --rebase synonyms\n| * | | | | | | | 36d45163b6 submodule--helper: report \"submodule\" as our name in some \"-h\" output\n| * | | | | | | | 6e556c412e submodule--helper: rename \"absorb-git-dirs\" to \"absorbgitdirs\"\n| * | | | | | | | 0d68ee723e submodule update: remove \"-v\" option\n| * | | | | | | | d9c7f69aaa submodule--helper: have --require-init imply --init\n| * | | | | | | | da3aae9e84 git-submodule.sh: remove unused top-level \"--branch\" argument\n| * | | | | | | | 757d092797 git-submodule.sh: make the \"$cached\" variable a boolean\n| * | | | | | | | 960fad98e8 git-submodule.sh: remove unused $prefix variable\n| * | | | | | | | 85775255f1 git-submodule.sh: remove unused sanitize_submodule_env()\n* | | | | | | | |   d1ce5a9649 Merge branch 'sy/mv-out-of-cone' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | b91a2b6594 mv: add check_dir_in_index() and solve general dir check issue\n| * | | | | | | | | 24ea81d9ac mv: use flags mode for update_mode\n| * | | | | | | | | 8a26a3915f mv: check if <destination> exists in index to handle overwriting\n| * | | | | | | | | 6645b03ca5 mv: check if out-of-cone file exists in index with SKIP_WORKTREE bit\n| * | | | | | | | | 7889755bae mv: decouple if/else-if checks using goto\n| * | | | | | | | | 707fa2f76a mv: update sparsity after moving from out-of-cone to in-cone\n| * | | | | | | | | 1143cc01b7 t1092: mv directory from out-of-cone to in-cone\n| * | | | | | | | | 367844e5b7 t7002: add tests for moving out-of-cone file/directory\n| | |_|/ / / / / /  \n| |/| | | | | | |   \n* | | | | | | | |   97316a96ed Merge branch 'hx/unpack-streaming' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | aaf81223f4 unpack-objects: use stream_loose_object() to unpack large objects\n| * | | | | | | | | 3c3ca0b0c1 core doc: modernize core.bigFileThreshold documentation\n| * | | | | | | | | 2b6070ac4c object-file.c: add \"stream_loose_object()\" to handle large object\n| * | | | | | | | | 21e7d88140 object-file.c: factor out deflate part of write_loose_object()\n| * | | | | | | | | 97a9db6ffb object-file.c: refactor write_loose_object() to several steps\n| * | | | | | | | | a1bf5ca29f unpack-objects: low memory footprint for get_data() in dry_run mode\n* | | | | | | | | |   b4846d0daa Merge branch 'en/merge-tree' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | | 7260e87248 git-merge-tree.txt: add a section on potentional usage mistakes\n| * | | | | | | | | | 7976721d17 merge-tree: add a --allow-unrelated-histories flag\n| * | | | | | | | | | 7c48b27822 merge-tree: allow `ls-files -u` style info to be NUL terminated\n| * | | | | | | | | | de90581141 merge-ort: optionally produce machine-readable output\n| * | | | | | | | | | cb2607759e merge-ort: store more specific conflict information\n| * | | | | | | | | | 2715e8a931 merge-ort: make `path_messages` a strmap to a string_list\n| * | | | | | | | | | 6debb7527b merge-ort: store messages in a list, not in a single strbuf\n| * | | | | | | | | | b520bc6caa merge-tree: provide easy access to `ls-files -u` style info\n| * | | | | | | | | | 7fa3338870 merge-tree: provide a list of which files have conflicts\n| * | | | | | | | | | a4040cfa35 merge-ort: remove command-line-centric submodule message from merge-ort\n| * | | | | | | | | | fae26ce79c merge-ort: provide a merge_get_conflicted_files() helper function\n| * | | | | | | | | | a1a7811975 merge-tree: support including merge messages in output\n| * | | | | | | | | | a34edae68a merge-ort: split out a separate display_update_messages() function\n| * | | | | | | | | | 1f0c3a29da merge-tree: implement real merges\n| * | | | | | | | | | 6ec755a0e1 merge-tree: add option parsing and initial shell for real merge function\n| * | | | | | | | | | 55e48f6bf7 merge-tree: move logic for existing merge into new function\n| * | | | | | | | | | 70176b7015 merge-tree: rename merge_trees() to trivial_merge_trees()\n* | | | | | | | | | |   d340a03e89 Merge branch 'gg/worktree-from-the-above' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| * | | | | | | | | | | 299f88a3b3 dir: minor refactoring / clean-up\n| * | | | | | | | | | | 992bb2eff9 dir: traverse into repository\n| | |_|_|_|/ / / / / /  \n| |/| | | | | | | | |   \n* | | | | | | | | | |   a06ded0851 Merge branch 'jc/resolve-undo' into next\n|\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\  \n| |/ / / / / / / / / /  \n|/| | | | | | | / / /   \n| | |_|_|_|_|_|/ / /    \n| |/| | | | | | | |     \n| * | | | | | | | | 0c2d8a5b28 revision: mark blobs needed for resolve-undo as reachable\n|/ / / / / / / / /  \n| o / / / / / / / f770e9f396 Git 2.37-rc2\n|  / / / / / / /  \n| o / / / / / / ab336e8f1c Seventh batch\n|  / / / / / /  \n| | o / / / / 8168d5e9c2 Git 2.37-rc0\n| |  / / / /  \n| | o / / / 2668e3608e Sixth batch\n| |  / / /  \n| | o / / 30cc8d0f14 A regression fix for 2.37\n| |  / /  \n| o / / e4a4b31577 Git 2.37\n|  / /  \no / / 4e2a4d1dd4 The second batch\n / /  \no / 54c8a7c379 revisions API: add a TODO for diff_free(&revs->diffopt)\n /  \no 1e59178e3f Sync with 'maint'\n"},{"id":"459058","messageId":"xmqqv8rz5ztg.fsf@gitster.g","threadId":"57949","inReplyTo":"73311eba-244b-3c88-d0b9-f97f426f1de6@github.com","subject":"Re: [PATCH v4 00/12] rebase: update branches in multi-part topic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-14T18:11:55Z","receivedAt":"2022-07-14T18:12:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n>  Before:\n>\n>   A---B----C---M---Q1---Q2---Q3 <-- refs/heads/Q\n>    \\          /\n>     P1--P2--P3 <-- refs/heads/P\n>\n>  After rebasing both topics simultaneously (with 'git rebase --update-refs\n>  C' while Q is checked out):\n>\n>   A---B---C---D---P1---P2---P3---Q1---Q2---Q3\n> \t\t\t    ^              ^\n> \t\t\trefs/heads/P  refs/heads/Q\n>\n> But it seems what you mean to say is to update the merge commit M, which\n> means that the 'P' branch above has been updated independently of the 'Q'\n> branch, so we need to update 'Q' after-the-fact.\n\nI am not aiming to flatten P and Q into a single strand of pearls.\nThat would defeat the point of \"git log --oneline --first-parent\"\nthat can be used to view \"master..Q\", whose output would be \"at the\nbottom the topic P lies there, and on top there are 3 patches\".\n\nP's may be your ds/branch-checked-out topic while Q's may be this\ntopic.  Other people may find bugs, improvements and a room for\nunwanted churns in the former, and P may gain a few more commit,\nin which case M thru Q3 needs to be rebuilt.\n\nIn a manual procedure, when I realize that P will gain a few\nmore patches (or gets rewritten):\n\n * find what other topics depend on P and make a mental note (i.e. Q\n   needs to be rebuilt)\n\n * perform an equivalent of \"git rebase -i --onto A P\", but without\n   using \"git rebase\".\n\n   - git checkout P\n   - git checkout --detach master...\t;# reuse the same base\n   - git am -s\t\t\t\t;# apply\n   - git rebase -i                      ;# minor fix-up while queuing\n   - git range-diff @{-1}...\t\t;# sanity check\n   - make test\t\t\t\t;# further sanity check\n   - git checkout -B @{-1}\t\t;# update P to the new round\n   - git range-diff @{1}...             ;# final sanity check\n\n   this rebuilds P1, P2, and P3 into a new series on A\n\n * for each topic that needs rebuilding (i.e. Q), find M and rebuild\n   it\n\n   - git checkout Q\n   - git checkout --detach master...\t;# reuse the same base\n   - git merge --into Q P\t\t;# recreate M with updated P\n   - git rebase --onto HEAD M Q\t\t;# rebuild Q\n   - git checkout -B @{-1}\t\t;# update Q to sit on top of new P\n   - git range-diff @{1}...\t\t;# sanity check (should be empty)\n\n\n> I'm not sure what that rebase would look like, indepdendent of\n> updating refs.\n\nI suspect that a creative use of \"git rebase --rebase-merge master\nQ\" should allow me to get there.  Here is an outline of the todo\nlist you'd get out of \"git rebase --rebase-merge -i v2.37.0\" while\nthe topic ds/rebase-update-ref is checked out:\n\n---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- \nlabel onto\n\n# Branch ds-branch-checked-out\nreset 5ed49a75f3 # Merge branch 'os/fetch-check-not-current-branch'\npick 31ad6b61bd branch: add branch_checked_out() helper\npick d2ba271aad branch: check for bisects and rebases\n...\npick 9bef0b1e6e branch: drop unused worktrees variable\nlabel ds-branch-checked-out\n\nreset onto\nmerge -C 7fefa1b68e ds-branch-checked-out # Merge branch 'ds/branch-checked-out' into ds/rebase-update-ref\npick a0bfa0ec53 t2407: test bisect and rebase as black-boxes\npick 43547f7a52 t2407: test branches currently using apply backend\n...\npick 8b2a776cab sequencer: notify user of --update-refs activity\n---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- \n\nSo it would likely involve\n\n - \"git checkout Q && git rebase -i --rebase-merge master\" to get\n   the todo list.\n\n - remove all \"pick\"s for existing commits on branch P, and replace\n   them with a \"break\"\n\n - add an update-ref insn to update ds/branch-checked-out topic\n   using the ds-branch-checked-out label at the end of the todo\n   list.\n\n - exit the editor, and in the \"break\" session, run \"am\" to accept\n   the new round of patches for P.\n\n - \"git rebase --continue\" to let the tip labeled as P and let the\n   rest of the todo list rebuild Q\n\nbut I am not sure what should happen when there are more than one\ndependent topic (i.e. in addition to Q, topic R also depends on P).\nIt also is unclear in the above procedure with \"rebase-merge\" what\nto feed to \"range-diff\" in the sanity-checking step.  I could type\n\"git range-diff P...\" but being able to use @{-1} is a lot handier.\n\n"},{"id":"459132","messageId":"11ff21b9-7169-00c5-95c0-130675ffe2ae@gmail.com","threadId":"57949","inReplyTo":"d5cd4b49e46bc2c186c6e89333360a975700c99a.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 12/12] sequencer: notify user of --update-refs activity","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-15T10:12:53Z","receivedAt":"2022-07-15T10:12:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 12/07/2022 14:07, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> When the user runs 'git rebase -i --update-refs', the end message still\n> says only\n> \n>    Successfully rebased and updated <HEAD-ref>.\n> \n> Update the sequencer to collect the successful (and unsuccessful) ref\n> updates due to the --update-refs option, so the end message now says\n> \n>    Successfully rebased and updated <HEAD-ref>.\n>    Updated the following refs with --update-refs:\n> \trefs/heads/first\n> \trefs/heads/third\n>    Failed to update the following refs with --update-refs:\n> \trefs/heads/second\n> \n> To test this output, we need to be very careful to format the expected\n> error to drop the leading tab characters. Also, we need to be aware that\n> the verbose output from 'git rebase' is writing progress lines which\n\ns/is writing/writes/ ?\n\n> don't use traditional newlines but clear the line after every progress\n> item is complete.\n\nI was a bit confused by the reference to \"verbose output\" in this \nparagraph. When the user passes --verbose then we do actually use NL, it \nis when the user does not pass verbose that we use CR instead.\n\n> When opening the error file in an editor, these lines\n> are visible, but when looking at the diff in a terminal those lines\n> disappear because of the characters that delete the previous characters.\n> Use 'sed' to clear those progress lines and clear the tabs so we can get\n> an exact match on our expected output.\n\nThanks for the comprehensive commit message and for implementing an \nexcellent suggestion from Elijah. I wonder if it makes sense to \ndistinguish between the current branch and all the others when writing \nthe update message as we do here or if all the refs should just be in a \nsingle list. I also think it doesn't matter much and we can change it \nlater if we want.\n\n From the last test it looks like we are already printing something when \nwe fail to update a ref (possibly this comes from the refs backend code) \nI don't think it hurts to print a summary of them all after that though.\n\n> Reported-by: Elijah Newren <newren@gmail.com>\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>   sequencer.c                   | 40 +++++++++++++++++++++++++++++------\n>   t/t3404-rebase-interactive.sh | 35 +++++++++++++++++++++++++++---\n>   2 files changed, 66 insertions(+), 9 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index 82ef062d497..bdc67c66f3e 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4257,17 +4257,20 @@ static int do_update_ref(struct repository *r, const char *refname)\n>   \treturn 0;\n>   }\n>   \n> -static int do_update_refs(struct repository *r)\n> +static int do_update_refs(struct repository *r, int quiet)\n>   {\n>   \tint res = 0;\n>   \tstruct string_list_item *item;\n>   \tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n>   \tstruct ref_store *refs = get_main_ref_store(r);\n> +\tstruct strbuf update_msg = STRBUF_INIT;\n> +\tstruct strbuf error_msg = STRBUF_INIT;\n>   \n>   \tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n>   \n>   \tfor_each_string_list_item(item, &refs_to_oids) {\n>   \t\tstruct update_ref_record *rec = item->util;\n> +\t\tint loop_res;\n>   \n>   \t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n>   \t\t\t/*\n> @@ -4277,13 +4280,38 @@ static int do_update_refs(struct repository *r)\n>   \t\t\tcontinue;\n>   \t\t}\n>   \n> -\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n> -\t\t\t\t       item->string,\n> -\t\t\t\t       &rec->after, &rec->before,\n> -\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n> +\t\tloop_res = refs_update_ref(refs, \"rewritten during rebase\",\n> +\t\t\t\t\t   item->string,\n> +\t\t\t\t\t   &rec->after, &rec->before,\n> +\t\t\t\t\t   0, UPDATE_REFS_MSG_ON_ERR);\n> +\t\tres |= loop_res;\n> +\n> +\t\tif (quiet)\n> +\t\t\tcontinue;\n> +\n> +\t\tif (loop_res)\n> +\t\t\tstrbuf_addf(&error_msg, \"\\t%s\\n\", item->string);\n> +\t\telse\n> +\t\t\tstrbuf_addf(&update_msg, \"\\t%s\\n\", item->string);\n> +\t}\n> +\n> +\tif (!quiet &&\n\nAs you skip adding anything to the strbufs when quiet is true you don't \nreally need this test\n\n> +\t    (update_msg.len || error_msg.len)) {\n> +\t\tfprintf(stderr,\n> +\t\t\t_(\"Updated the following refs with %s:\\n%s\"),\n> +\t\t\t\"--update-refs\",\n> +\t\t\tupdate_msg.buf);\n\nThis will be printed even if all the updates falied\n\n> +\n> +\t\tif (res)\n> +\t\t\tfprintf(stderr,\n> +\t\t\t\t_(\"Failed to update the following refs with %s:\\n%s\"),\n> +\t\t\t\t\"--update-refs\",\n> +\t\t\t\terror_msg.buf);\n>   \t}\n>   \n>   \tstring_list_clear(&refs_to_oids, 1);\n> +\tstrbuf_release(&update_msg);\n> +\tstrbuf_release(&error_msg);\n>   \treturn res;\n>   }\n>   \n> @@ -4804,7 +4832,7 @@ cleanup_head_ref:\n>   \t\tstrbuf_release(&head_ref);\n>   \t}\n>   \n> -\tdo_update_refs(r);\n> +\tdo_update_refs(r, opts->quiet);\n>   \n>   \t/*\n>   \t * Sequence of picks finished successfully; cleanup by\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 4b7b77a4123..ef902b5431f 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1836,12 +1836,26 @@ test_expect_success '--update-refs updates refs correctly' '\n>   \ttest_commit extra2 fileX &&\n>   \tgit commit --amend --fixup=L &&\n>   \n> -\tgit rebase -i --autosquash --update-refs primary &&\n> +\tgit rebase -i --autosquash --update-refs primary 2>err &&\n>   \n>   \ttest_cmp_rev HEAD~3 refs/heads/first &&\n>   \ttest_cmp_rev HEAD~3 refs/heads/second &&\n>   \ttest_cmp_rev HEAD~1 refs/heads/third &&\n> -\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n> +\ttest_cmp_rev HEAD refs/heads/no-conflict-branch &&\n> +\n> +\tcat >expect <<-\\EOF &&\n> +\tSuccessfully rebased and updated refs/heads/update-refs.\n> +\tUpdated the following refs with --update-refs:\n> +\t\trefs/heads/first\n> +\t\trefs/heads/no-conflict-branch\n> +\t\trefs/heads/second\n> +\t\trefs/heads/third\n> +\tEOF\n> +\n> +\t# Clear \"Rebasing (X/Y)\" progress lines and drop leading tabs.\n> +\tsed -e \"s/Rebasing.*Successfully/Successfully/g\" -e \"s/^\\t//g\" \\\n> +\t\t<err >err.trimmed &&\n> +\ttest_cmp expect err.trimmed\n>   '\n>   \n>   test_expect_success 'respect user edits to update-ref steps' '\n> @@ -1980,7 +1994,22 @@ test_expect_success '--update-refs: check failed ref update' '\n>   \tgit rev-parse third >.git/refs/heads/second &&\n>   \n>   \tgit rebase --continue 2>err &&\n> -\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n> +\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err &&\n> +\n> +\tcat >expect <<-\\EOF &&\n> +\tUpdated the following refs with --update-refs:\n> +\t\trefs/heads/first\n> +\t\trefs/heads/no-conflict-branch\n> +\t\trefs/heads/third\n> +\tFailed to update the following refs with --update-refs:\n> +\t\trefs/heads/second\n> +\tEOF\n> +\n> +\t# Clear \"Rebasing (X/Y)\" progress lines and drop leading tabs.\n> +\ttail -n 6 err >err.last &&\n\nI'm curious as to why we need tail here but not in the test above.\n\nThis is looking good, I'd be happy enough to see it merged as is.\n\nBest Wishes\n\nPhillip\n\n> +\tsed -e \"s/Rebasing.*Successfully/Successfully/g\" -e \"s/^\\t//g\" \\\n> +\t\t<err.last >err.trimmed &&\n> +\ttest_cmp expect err.trimmed\n>   '\n>   \n>   # This must be the last test in this file\n\n"},{"id":"459133","messageId":"7a471a98-09d1-78cb-d6dd-a7faaa9071ba@gmail.com","threadId":"57949","inReplyTo":"29c7c76805abb9b8afd44a65cb39d8f9d3c95892.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 09/12] sequencer: rewrite update-refs as user edits todo list","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-15T10:27:41Z","receivedAt":"2022-07-15T10:27:48Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 12/07/2022 14:07, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> An interactive rebase provides opportunities for the user to edit the\n> todo list. The --update-refs option initializes the list with some\n> 'update-ref <ref>' steps, but the user could add these manually.\n> Further, the user could add or remove these steps during pauses in the\n> interactive rebase.\n> \n> Add a new method, todo_list_filter_update_refs(), that scans a todo_list\n> and compares it to the stored update-refs file. There are two actions\n> that can happen at this point:\n> \n> 1. If a '<ref>/<before>/<after>' triple in the update-refs file does not\n>     have a matching 'update-ref <ref>' command in the todo-list _and_ the\n>     <after> value is the null OID, then remove that triple. Here, the\n>     user removed the 'update-ref <ref>' command before it was executed,\n>     since if it was executed then the <after> value would store the\n>     commit at that position.\n> \n> 2. If a 'update-ref <ref>' command in the todo-list does not have a\n>     matching '<ref>/<before>/<after>' triple in the update-refs file,\n>     then insert a new one. Store the <before> value to be the current\n>     OID pointed at by <ref>. This is handled inside of the\n>     init_update_ref_record() helper method.\n> \n> We can test that this works by rewriting the todo-list several times in\n> the course of a rebase. Check that each ref is locked or unlocked for\n> updates after each todo-list update. We an also verify that the ref\n> update fails if a concurrent process updates one of the refs after the\n> rebase process records the \"locked\" ref location.\n\nThanks for adding this and taking the time to write some good tests. \nWhen adding a new update-ref command to the todo list it would be nice \nto check if the ref is already checked. We could print a warning or \nforce the user to re-edit the todo list as we do if they delete a pick \nand rebase.missingcommitscheck == error\n\nThe checks below are quadratic but we don't expect that many refs so \nthat should be fine. I don't think it is worth the complexity of \nbuilding a hash table or whatever at this stage.\n\nBest Wishes\n\nPhillip\n\n> To help these tests, add a new 'set_replace_editor' helper that will\n> replace the todo-list with an exact file.\n> \n> Reported-by: Phillip Wood <phillip.wood123@gmail.com>\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>   rebase-interactive.c          |   6 ++\n>   sequencer.c                   |  96 +++++++++++++++++++++++\n>   sequencer.h                   |  12 +++\n>   t/lib-rebase.sh               |  15 ++++\n>   t/t3404-rebase-interactive.sh | 139 ++++++++++++++++++++++++++++++++++\n>   5 files changed, 268 insertions(+)\n> \n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index 1ff07647af3..7407c593191 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -146,6 +146,12 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>   \t\treturn -4;\n>   \t}\n>   \n> +\t/*\n> +\t * See if branches need to be added or removed from the update-refs\n> +\t * file based on the new todo list.\n> +\t */\n> +\ttodo_list_filter_update_refs(r, new_todo);\n> +\n>   \treturn 0;\n>   }\n>   \n> diff --git a/sequencer.c b/sequencer.c\n> index 2d89b3b727a..2808c027d68 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4141,6 +4141,102 @@ cleanup:\n>   \treturn result;\n>   }\n>   \n> +/*\n> + * Parse the update-refs file for the current rebase, then remove the\n> + * refs that do not appear in the todo_list (and have not had updated\n> + * values stored) and add refs that are in the todo_list but not\n> + * represented in the update-refs file.\n> + *\n> + * If there are changes to the update-refs list, then write the new state\n> + * to disk.\n> + */\n> +void todo_list_filter_update_refs(struct repository *r,\n> +\t\t\t\t  struct todo_list *todo_list)\n> +{\n> +\tint i;\n> +\tint updated = 0;\n> +\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n> +\n> +\tsequencer_get_update_refs_state(r->gitdir, &update_refs);\n> +\n> +\t/*\n> +\t * For each item in the update_refs list, if it has no updated\n> +\t * value and does not appear in the todo_list, then remove it\n> +\t * from the update_refs list.\n> +\t */\n> +\tfor (i = 0; i < update_refs.nr; i++) {\n> +\t\tint j;\n> +\t\tint found = 0;\n> +\t\tconst char *ref = update_refs.items[i].string;\n> +\t\tsize_t reflen = strlen(ref);\n> +\t\tstruct update_ref_record *rec = update_refs.items[i].util;\n> +\n> +\t\t/* OID already stored as updated. */\n> +\t\tif (!is_null_oid(&rec->after))\n> +\t\t\tcontinue;\n> +\n> +\t\tfor (j = 0; !found && j < todo_list->total_nr; j++) {\n> +\t\t\tstruct todo_item *item = &todo_list->items[j];\n> +\t\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n> +\n> +\t\t\tif (item->command != TODO_UPDATE_REF)\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tif (item->arg_len != reflen ||\n> +\t\t\t    strncmp(arg, ref, reflen))\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tfound = 1;\n> +\t\t}\n> +\n> +\t\tif (!found) {\n> +\t\t\tfree(update_refs.items[i].string);\n> +\t\t\tfree(update_refs.items[i].util);\n> +\n> +\t\t\tupdate_refs.nr--;\n> +\t\t\tMOVE_ARRAY(update_refs.items + i, update_refs.items + i + 1, update_refs.nr - i);\n> +\n> +\t\t\tupdated = 1;\n> +\t\t\ti--;\n> +\t\t}\n> +\t}\n> +\n> +\t/*\n> +\t * For each todo_item, check if its ref is in the update_refs list.\n> +\t * If not, then add it as an un-updated ref.\n> +\t */\n> +\tfor (i = 0; i < todo_list->total_nr; i++) {\n> +\t\tstruct todo_item *item = &todo_list->items[i];\n> +\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n> +\t\tint j, found = 0;\n> +\n> +\t\tif (item->command != TODO_UPDATE_REF)\n> +\t\t\tcontinue;\n> +\n> +\t\tfor (j = 0; !found && j < update_refs.nr; j++) {\n> +\t\t\tconst char *ref = update_refs.items[j].string;\n> +\n> +\t\t\tfound = strlen(ref) == item->arg_len &&\n> +\t\t\t\t!strncmp(ref, arg, item->arg_len);\n> +\t\t}\n> +\n> +\t\tif (!found) {\n> +\t\t\tstruct string_list_item *inserted;\n> +\t\t\tstruct strbuf argref = STRBUF_INIT;\n> +\n> +\t\t\tstrbuf_add(&argref, arg, item->arg_len);\n> +\t\t\tinserted = string_list_insert(&update_refs, argref.buf);\n> +\t\t\tinserted->util = init_update_ref_record(argref.buf);\n> +\t\t\tstrbuf_release(&argref);\n> +\t\t\tupdated = 1;\n> +\t\t}\n> +\t}\n> +\n> +\tif (updated)\n> +\t\twrite_update_refs_state(&update_refs);\n> +\tstring_list_clear(&update_refs, 1);\n> +}\n> +\n>   static int do_update_ref(struct repository *r, const char *refname)\n>   {\n>   \tstruct string_list_item *item;\n> diff --git a/sequencer.h b/sequencer.h\n> index e671d7e0743..98f3bcc1658 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -132,6 +132,18 @@ void todo_list_release(struct todo_list *todo_list);\n>   const char *todo_item_get_arg(struct todo_list *todo_list,\n>   \t\t\t      struct todo_item *item);\n>   \n> +/*\n> + * Parse the update-refs file for the current rebase, then remove the\n> + * refs that do not appear in the todo_list (and have not had updated\n> + * values stored) and add refs that are in the todo_list but not\n> + * represented in the update-refs file.\n> + *\n> + * If there are changes to the update-refs list, then write the new state\n> + * to disk.\n> + */\n> +void todo_list_filter_update_refs(struct repository *r,\n> +\t\t\t\t  struct todo_list *todo_list);\n> +\n>   /* Call this to setup defaults before parsing command line options */\n>   void sequencer_init_config(struct replay_opts *opts);\n>   int sequencer_pick_revisions(struct repository *repo,\n> diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh\n> index ec6b9b107da..b57541356bd 100644\n> --- a/t/lib-rebase.sh\n> +++ b/t/lib-rebase.sh\n> @@ -207,3 +207,18 @@ check_reworded_commits () {\n>   \t\t>reword-log &&\n>   \ttest_cmp reword-expected reword-log\n>   }\n> +\n> +# usage: set_replace_editor <file>\n> +#\n> +# Replace the todo file with the exact contents of the given file.\n> +set_replace_editor () {\n> +\tcat >script <<-\\EOF &&\n> +\tcat FILENAME >\"$1\"\n> +\n> +\techo 'rebase -i script after editing:'\n> +\tcat \"$1\"\n> +\tEOF\n> +\n> +\tsed -e \"s/FILENAME/$1/g\" <script | write_script fake-editor.sh &&\n> +\ttest_set_editor \"$(pwd)/fake-editor.sh\"\n> +}\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index a37820fa728..7bfbd405ab8 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1830,6 +1830,145 @@ test_expect_success '--update-refs updates refs correctly' '\n>   \ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n>   '\n>   \n> +test_expect_success 'respect user edits to update-ref steps' '\n> +\tgit checkout -B update-refs-break no-conflict-branch &&\n> +\tgit branch -f base HEAD~4 &&\n> +\tgit branch -f first HEAD~3 &&\n> +\tgit branch -f second HEAD~3 &&\n> +\tgit branch -f third HEAD~1 &&\n> +\tgit branch -f unseen base &&\n> +\n> +\t# First, we will add breaks to the expected todo file\n> +\tcat >fake-todo-1 <<-EOF &&\n> +\tpick $(git rev-parse HEAD~3)\n> +\tbreak\n> +\tupdate-ref refs/heads/second\n> +\tupdate-ref refs/heads/first\n> +\n> +\tpick $(git rev-parse HEAD~2)\n> +\tpick $(git rev-parse HEAD~1)\n> +\tupdate-ref refs/heads/third\n> +\n> +\tpick $(git rev-parse HEAD)\n> +\tupdate-ref refs/heads/no-conflict-branch\n> +\tEOF\n> +\n> +\t# Second, we will drop some update-refs commands (and move one)\n> +\tcat >fake-todo-2 <<-EOF &&\n> +\tupdate-ref refs/heads/second\n> +\n> +\tpick $(git rev-parse HEAD~2)\n> +\tupdate-ref refs/heads/third\n> +\tpick $(git rev-parse HEAD~1)\n> +\tbreak\n> +\n> +\tpick $(git rev-parse HEAD)\n> +\tEOF\n> +\n> +\t# Third, we will:\n> +\t# * insert a new one (new-branch),\n> +\t# * re-add an old one (first), and\n> +\t# * add a second instance of a previously-stored one (second)\n> +\tcat >fake-todo-3 <<-EOF &&\n> +\tupdate-ref refs/heads/unseen\n> +\tupdate-ref refs/heads/new-branch\n> +\tpick $(git rev-parse HEAD)\n> +\tupdate-ref refs/heads/first\n> +\tupdate-ref refs/heads/second\n> +\tEOF\n> +\n> +\t(\n> +\t\tset_replace_editor fake-todo-1 &&\n> +\t\tgit rebase -i --update-refs primary &&\n> +\n> +\t\t# These branches are currently locked.\n> +\t\tfor b in first second third no-conflict-branch\n> +\t\tdo\n> +\t\t\ttest_must_fail git branch -f $b base || return 1\n> +\t\tdone &&\n> +\n> +\t\tset_replace_editor fake-todo-2 &&\n> +\t\tgit rebase --edit-todo &&\n> +\n> +\t\t# These branches are currently locked.\n> +\t\tfor b in second third\n> +\t\tdo\n> +\t\t\ttest_must_fail git branch -f $b base || return 1\n> +\t\tdone &&\n> +\n> +\t\t# These branches are currently unlocked for checkout.\n> +\t\tfor b in first no-conflict-branch\n> +\t\tdo\n> +\t\t\tgit worktree add wt-$b $b &&\n> +\t\t\tgit worktree remove wt-$b || return 1\n> +\t\tdone &&\n> +\n> +\t\tgit rebase --continue &&\n> +\n> +\t\tset_replace_editor fake-todo-3 &&\n> +\t\tgit rebase --edit-todo &&\n> +\n> +\t\t# These branches are currently locked.\n> +\t\tfor b in second third first unseen\n> +\t\tdo\n> +\t\t\ttest_must_fail git branch -f $b base || return 1\n> +\t\tdone &&\n> +\n> +\t\t# These branches are currently unlocked for checkout.\n> +\t\tfor b in no-conflict-branch\n> +\t\tdo\n> +\t\t\tgit worktree add wt-$b $b &&\n> +\t\t\tgit worktree remove wt-$b || return 1\n> +\t\tdone &&\n> +\n> +\t\tgit rebase --continue\n> +\t) &&\n> +\n> +\ttest_cmp_rev HEAD~2 refs/heads/third &&\n> +\ttest_cmp_rev HEAD~1 refs/heads/unseen &&\n> +\ttest_cmp_rev HEAD~1 refs/heads/new-branch &&\n> +\ttest_cmp_rev HEAD refs/heads/first &&\n> +\ttest_cmp_rev HEAD refs/heads/second &&\n> +\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n> +'\n> +\n> +test_expect_success '--update-refs: check failed ref update' '\n> +\tgit checkout -B update-refs-error no-conflict-branch &&\n> +\tgit branch -f base HEAD~4 &&\n> +\tgit branch -f first HEAD~3 &&\n> +\tgit branch -f second HEAD~2 &&\n> +\tgit branch -f third HEAD~1 &&\n> +\n> +\tcat >fake-todo <<-EOF &&\n> +\tpick $(git rev-parse HEAD~3)\n> +\tbreak\n> +\tupdate-ref refs/heads/first\n> +\n> +\tpick $(git rev-parse HEAD~2)\n> +\tupdate-ref refs/heads/second\n> +\n> +\tpick $(git rev-parse HEAD~1)\n> +\tupdate-ref refs/heads/third\n> +\n> +\tpick $(git rev-parse HEAD)\n> +\tupdate-ref refs/heads/no-conflict-branch\n> +\tEOF\n> +\n> +\t(\n> +\t\tset_replace_editor fake-todo &&\n> +\t\tgit rebase -i --update-refs base\n> +\t) &&\n> +\n> +\t# At this point, the values of first, second, and third are\n> +\t# recorded in the update-refs file. We will force-update the\n> +\t# \"second\" ref, but \"git branch -f\" will not work because of\n> +\t# the lock in the update-refs file.\n> +\tgit rev-parse third >.git/refs/heads/second &&\n> +\n> +\tgit rebase --continue 2>err &&\n> +\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n> +'\n> +\n>   # This must be the last test in this file\n>   test_expect_success '$EDITOR and friends are unchanged' '\n>   \ttest_editor_unchanged\n\n"},{"id":"459143","messageId":"638f6b51-0045-f08e-fa63-d16a571d0099@github.com","threadId":"57949","inReplyTo":"7a471a98-09d1-78cb-d6dd-a7faaa9071ba@gmail.com","subject":"Re: [PATCH v4 09/12] sequencer: rewrite update-refs as user edits todo list","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-15T13:13:29Z","receivedAt":"2022-07-15T13:13:37Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/15/2022 6:27 AM, Phillip Wood wrote:\n\n>> We can test that this works by rewriting the todo-list several times in\n>> the course of a rebase. Check that each ref is locked or unlocked for\n>> updates after each todo-list update. We an also verify that the ref\n>> update fails if a concurrent process updates one of the refs after the\n>> rebase process records the \"locked\" ref location.\n> \n> Thanks for adding this and taking the time to write some good tests. When adding a new update-ref command to the todo list it would be nice to check if the ref is already checked. We could print a warning or force the user to re-edit the todo list as we do if they delete a pick and rebase.missingcommitscheck == error\n\nI agree that that would be a helpful feature instead of the user\ndiscovering the issue at the end of the rebase (as they do with\nthe message from patch 12).\n \n> The checks below are quadratic but we don't expect that many refs so that should be fine. I don't think it is worth the complexity of building a hash table or whatever at this stage.\n\nA hash table would be the natural approach if we see users having\ntrouble from such high use of the feature.\n\nI think both of these concerns would be excellent for a follow-up,\nsince they would shave off some rough edges. I hesitate to add them\nto this series since it has been growing quite a bit already.\n\nThanks,\n-Stolee\n"},{"id":"459144","messageId":"fc2e8f9d-0232-4c19-2e4d-9ba6c9b17ab2@github.com","threadId":"57949","inReplyTo":"11ff21b9-7169-00c5-95c0-130675ffe2ae@gmail.com","subject":"Re: [PATCH v4 12/12] sequencer: notify user of --update-refs activity","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-15T13:20:05Z","receivedAt":"2022-07-15T13:20:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/15/2022 6:12 AM, Phillip Wood wrote:\n> On 12/07/2022 14:07, Derrick Stolee via GitGitGadget wrote:\n>> From: Derrick Stolee <derrickstolee@github.com>\n>>\n>> When the user runs 'git rebase -i --update-refs', the end message still\n>> says only\n>>\n>>    Successfully rebased and updated <HEAD-ref>.\n>>\n>> Update the sequencer to collect the successful (and unsuccessful) ref\n>> updates due to the --update-refs option, so the end message now says\n>>\n>>    Successfully rebased and updated <HEAD-ref>.\n>>    Updated the following refs with --update-refs:\n>>     refs/heads/first\n>>     refs/heads/third\n>>    Failed to update the following refs with --update-refs:\n>>     refs/heads/second\n>>\n>> To test this output, we need to be very careful to format the expected\n>> error to drop the leading tab characters. Also, we need to be aware that\n>> the verbose output from 'git rebase' is writing progress lines which\n> \n> s/is writing/writes/ ?\n\nThat is an improvement. Thanks.\n \n>> don't use traditional newlines but clear the line after every progress\n>> item is complete.\n> \n> I was a bit confused by the reference to \"verbose output\" in this paragraph. When the user passes --verbose then we do actually use NL, it is when the user does not pass verbose that we use CR instead.\n\nI was mostly thinking that the summary message at the end is only shown\nwhen the command has verbose output (which really means the default of\n--no-quiet). There is no current way to have --no-progress --no-quiet\nto keep the progress lines out of the output but still have the summary.\n\n>> When opening the error file in an editor, these lines\n>> are visible, but when looking at the diff in a terminal those lines\n>> disappear because of the characters that delete the previous characters.\n>> Use 'sed' to clear those progress lines and clear the tabs so we can get\n>> an exact match on our expected output.\n> \n> Thanks for the comprehensive commit message and for implementing an excellent suggestion from Elijah. I wonder if it makes sense to distinguish between the current branch and all the others when writing the update message as we do here or if all the refs should just be in a single list. I also think it doesn't matter much and we can change it later if we want.\n\nI'm definitely open to suggestions, but I also think we should start\nsomewhere and see what users think. Since the mechanisms for updating the\nrefs are different, I felt it was appropriate to have different error\nmessages.\n\n>> +    if (!quiet &&\n> \n> As you skip adding anything to the strbufs when quiet is true you don't really need this test\n> \n>> +        (update_msg.len || error_msg.len)) {\n>> +        fprintf(stderr,\n>> +            _(\"Updated the following refs with %s:\\n%s\"),\n>> +            \"--update-refs\",\n>> +            update_msg.buf);\n> \n> This will be printed even if all the updates falied\n\nThese are good points! Instead, I should arrange this as\n\n\tif (update_msg.len)\n\t\t/* send the success message. */\n\tif (error_msg.len)\n\t\t/* send the failure message. */\n\n>> +    cat >expect <<-\\EOF &&\n>> +    Updated the following refs with --update-refs:\n>> +        refs/heads/first\n>> +        refs/heads/no-conflict-branch\n>> +        refs/heads/third\n>> +    Failed to update the following refs with --update-refs:\n>> +        refs/heads/second\n>> +    EOF\n\n(copying this message from earlier in your message for context)\n\n> From the last test it looks like we are already printing something when we fail to update a ref (possibly this comes from the refs backend code) I don't think it hurts to print a summary of them all after that though.\n\n>> +    # Clear \"Rebasing (X/Y)\" progress lines and drop leading tabs.\n>> +    tail -n 6 err >err.last &&\n> \n> I'm curious as to why we need tail here but not in the test above.\n\nI think I was trying to avoid testing the error messages from the\nref backend code. I bet that since this tail is here, the 'sed' is\nno longer required.\n\n>> +    sed -e \"s/Rebasing.*Successfully/Successfully/g\" -e \"s/^\\t//g\" \\\n>> +        <err.last >err.trimmed &&\n>> +    test_cmp expect err.trimmed\n\nThanks!\n-Stolee\n"},{"id":"459145","messageId":"e40df260-6ca7-10f8-c4bf-82c42585dd12@gmail.com","threadId":"57949","inReplyTo":"fb5f64c52010e01ce0b5a886e9611fa2a6ea7833.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 08/12] rebase: update refs from 'update-ref' commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-15T13:25:22Z","receivedAt":"2022-07-15T13:25:30Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 12/07/2022 14:07, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> The previous change introduced the 'git rebase --update-refs' option\n> which added 'update-ref <ref>' commands to the todo list of an\n> interactive rebase.\n> \n> Teach Git to record the HEAD position when reaching these 'update-ref'\n> commands. The ref/before/after triple is stored in the\n> $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n> file to avoid having other processes updating the refs in that file\n> while the rebase is in progress.\n> \n> Not only do we update the file when the sequencer reaches these\n> 'update-ref' commands, we then update the refs themselves at the end of\n> the rebase sequence. If the rebase is aborted before this final step,\n> then the refs are not updated. The 'before' value is used to ensure that\n> we do not accidentally obliterate a ref that was updated concurrently\n> (say, by an older version of Git or a third-party tool).\n> \n> Now that the 'git rebase --update-refs' command is implemented to write\n> to the update-refs file, we can remove the fake construction of the\n> update-refs file from a test in t2407-worktree-heads.sh.\n\nThis is looking good. I've left a few comments, mostly about error \npropagation. It's nice to see us recording the initial value of the ref \nwhen the todo list is created. It's also good to see this using a lock \nfile. We could perhaps lock the file (with a timeout) when we read it in \nsequencer_get_update_refs_state() to avoid a race where a process is \nchecking out a new branch in one worktree and another is preparing to \nrebase that branch in another worktree.\n\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>   sequencer.c                   | 113 +++++++++++++++++++++++++++++++++-\n>   t/t2407-worktree-heads.sh     |  21 ++-----\n>   t/t3404-rebase-interactive.sh |  17 +++++\n>   3 files changed, 134 insertions(+), 17 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index e657862cda2..2d89b3b727a 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -36,6 +36,7 @@\n>   #include \"rebase-interactive.h\"\n>   #include \"reset.h\"\n>   #include \"branch.h\"\n> +#include \"log-tree.h\"\n>   \n>   #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>   \n> @@ -193,6 +194,19 @@ struct update_ref_record {\n>   \tstruct object_id after;\n>   };\n>   \n> +static struct update_ref_record *init_update_ref_record(const char *ref)\n> +{\n> +\tstruct update_ref_record *rec = xmalloc(sizeof(*rec));\n> +\n> +\toidcpy(&rec->before, null_oid());\n> +\toidcpy(&rec->after, null_oid());\n> +\n> +\t/* This may fail, but that's fine, we will keep the null OID. */\n> +\tread_ref(ref, &rec->before);\n> +\n> +\treturn rec;\n> +}\n> +\n>   static int git_sequencer_config(const char *k, const char *v, void *cb)\n>   {\n>   \tstruct replay_opts *opts = cb;\n> @@ -4081,11 +4095,102 @@ leave_merge:\n>   \treturn ret;\n>   }\n>   \n> -static int do_update_ref(struct repository *r, const char *ref_name)\n> +static int write_update_refs_state(struct string_list *refs_to_oids)\n>   {\n> +\tint result = 0;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tFILE *fp = NULL;\n> +\tstruct string_list_item *item;\n> +\tchar *path;\n> +\n> +\tif (!refs_to_oids->nr)\n> +\t\treturn 0;\n> +\n> +\tpath = rebase_path_update_refs(the_repository->gitdir);\n> +\n> +\tif (safe_create_leading_directories(path)) {\n> +\t\tresult = error(_(\"unable to create leading directories of %s\"),\n> +\t\t\t       path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tif (hold_lock_file_for_update(&lock, path, 0) < 0) {\n> +\t\tresult = error(_(\"another 'rebase' process appears to be running; \"\n> +\t\t\t\t \"'%s.lock' already exists\"),\n> +\t\t\t       path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tfp = fdopen_lock_file(&lock, \"w\");\n> +\tif (!fp) {\n> +\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n> +\t\trollback_lock_file(&lock);\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tfor_each_string_list_item(item, refs_to_oids) {\n> +\t\tstruct update_ref_record *rec = item->util;\n> +\t\tfprintf(fp, \"%s\\n%s\\n%s\\n\", item->string,\n> +\t\t\toid_to_hex(&rec->before), oid_to_hex(&rec->after));\n> +\t}\n> +\n> +\tresult = commit_lock_file(&lock);\n> +\n> +cleanup:\n> +\tfree(path);\n> +\treturn result;\n> +}\n> +\n> +static int do_update_ref(struct repository *r, const char *refname)\n> +{\n> +\tstruct string_list_item *item;\n> +\tstruct string_list list = STRING_LIST_INIT_DUP;\n> +\n> +\tsequencer_get_update_refs_state(r->gitdir, &list);\n\nWe're ignoring any errors here and always returning 0 from this function.\n\n> +\n> +\tfor_each_string_list_item(item, &list) {\n> +\t\tif (!strcmp(item->string, refname)) {\n> +\t\t\tstruct update_ref_record *rec = item->util;\n> +\t\t\tread_ref(\"HEAD\", &rec->after);\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\n> +\twrite_update_refs_state(&list);\n> +\tstring_list_clear(&list, 1);\n>   \treturn 0;\n>   }\n>   \n> +static int do_update_refs(struct repository *r)\n> +{\n> +\tint res = 0;\n> +\tstruct string_list_item *item;\n> +\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n> +\tstruct ref_store *refs = get_main_ref_store(r);\n> +\n> +\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n> +\n> +\tfor_each_string_list_item(item, &refs_to_oids) {\n> +\t\tstruct update_ref_record *rec = item->util;\n> +\n> +\t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n> +\t\t\t/*\n> +\t\t\t * Ref was not updated. User may have deleted the\n> +\t\t\t * 'update-ref' step.\n> +\t\t\t */\n\nUnless we want to support users editing the todo list without using \"git \nrebase --edit-todo\" then by the end of the series it is a bug if we \nleave an entry in the update-refs file that has been removed from the \ntodo list so I wander if we should remove this if().\n\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n> +\t\t\t\t       item->string,\n> +\t\t\t\t       &rec->after, &rec->before,\n> +\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n> +\t}\n> +\n> +\tstring_list_clear(&refs_to_oids, 1);\n> +\treturn res;\n> +}\n> +\n>   static int is_final_fixup(struct todo_list *todo_list)\n>   {\n>   \tint i = todo_list->current;\n> @@ -4603,6 +4708,8 @@ cleanup_head_ref:\n>   \t\tstrbuf_release(&head_ref);\n>   \t}\n>   \n> +\tdo_update_refs(r);\n\nShould this be inside the \"if (is_rebase_i(opts))\" that is closed just \nabove it? We're also ignoring the return value.\n\n> +\n>   \t/*\n>   \t * Sequence of picks finished successfully; cleanup by\n>   \t * removing the .git/sequencer directory\n> @@ -5676,7 +5783,7 @@ static int add_decorations_to_list(const struct commit *commit,\n>   \n>   \t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n>   \t\t\t\t\t\t decoration->name);\n> -\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n> +\t\t\tsti->util = init_update_ref_record(decoration->name);\n>   \t\t}\n>   \n>   \t\titem->offset_in_buf = base_offset;\n> @@ -5732,6 +5839,8 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n>   \t\t}\n>   \t}\n>   \n> +\twrite_update_refs_state(&ctx.refs_to_oids);\n\nWe're ignoring the return value. Also I think \ntodo_list_add_update_ref_commands() only ever returns 0.\n\nBest Wishes\n\nPhillip\n\n> +\n>   \tstring_list_clear(&ctx.refs_to_oids, 1);\n>   \tfree(todo_list->items);\n>   \ttodo_list->items = ctx.items;\n> diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\n> index 8a03f14df8d..50815acd3e8 100755\n> --- a/t/t2407-worktree-heads.sh\n> +++ b/t/t2407-worktree-heads.sh\n> @@ -81,25 +81,16 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n>   \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n>   '\n>   \n> -test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n> -\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n> -\n> -\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n> -\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n> +test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' '\n> +\ttest_when_finished git -C wt-3 rebase --abort &&\n>   \n> -\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n> -\trefs/heads/fake-3\n> -\t$(git rev-parse HEAD~1)\n> -\t$(git rev-parse HEAD)\n> -\trefs/heads/fake-4\n> -\t$(git rev-parse HEAD)\n> -\t$(git rev-parse HEAD)\n> -\tEOF\n> +\tgit branch -f can-be-updated wt-3 &&\n> +\ttest_must_fail git -C wt-3 rebase --update-refs conflict-3 &&\n>   \n>   \tfor i in 3 4\n>   \tdo\n> -\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n> -\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n> +\t\ttest_must_fail git branch -f can-be-updated HEAD 2>err &&\n> +\t\tgrep \"cannot force update the branch '\\''can-be-updated'\\'' checked out at.*wt-3\" err ||\n>   \t\t\treturn 1\n>   \tdone\n>   '\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 3cd20733bc8..a37820fa728 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1813,6 +1813,23 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n>   \t)\n>   '\n>   \n> +test_expect_success '--update-refs updates refs correctly' '\n> +\tgit checkout -B update-refs no-conflict-branch &&\n> +\tgit branch -f base HEAD~4 &&\n> +\tgit branch -f first HEAD~3 &&\n> +\tgit branch -f second HEAD~3 &&\n> +\tgit branch -f third HEAD~1 &&\n> +\ttest_commit extra2 fileX &&\n> +\tgit commit --amend --fixup=L &&\n> +\n> +\tgit rebase -i --autosquash --update-refs primary &&\n> +\n> +\ttest_cmp_rev HEAD~3 refs/heads/first &&\n> +\ttest_cmp_rev HEAD~3 refs/heads/second &&\n> +\ttest_cmp_rev HEAD~1 refs/heads/third &&\n> +\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n> +'\n> +\n>   # This must be the last test in this file\n>   test_expect_success '$EDITOR and friends are unchanged' '\n>   \ttest_editor_unchanged\n\n"},{"id":"459150","messageId":"8e533a1d-9d8f-a09f-e036-37ba39023aee@gmail.com","threadId":"57949","inReplyTo":"bf301a054e3903444c2ea1c2e0234209d5ece291.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 03/12] branch: consider refs under 'update-refs'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-15T15:37:34Z","receivedAt":"2022-07-15T15:37:41Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 12/07/2022 14:06, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> The branch_checked_out() helper helps commands like 'git branch' and\n> 'git fetch' from overwriting refs that are currently checked out in\n> other worktrees.\n> \n> A future update to 'git rebase' will introduce a new '--update-refs'\n> option which will update the local refs that point to commits that are\n> being rebased. To avoid collisions as the rebase completes, we want to\n> make the future data store for these refs to be considered by\n> branch_checked_out().\n> \n> The data store is a plaintext file inside the 'rebase-merge' directory\n> for that worktree. The file lists refnames followed by two OIDs, each on\n> separate lines. The OIDs will be used to store the original values of\n> the refs and the to-be-written values as the rebase progresses, but can\n> be ignored at the moment.\n> \n> Create a new sequencer_get_update_refs_state() method that parses this\n> file and populates a struct string_list with the ref-OID pairs. We can\n> then use this list to add to the current_checked_out_branches strmap\n> used by branch_checked_out().\n> \n> To properly navigate to the rebase directory for a given worktree,\n> extract the static strbuf_worktree_gitdir() method to a public API\n> method.\n> \n> We can test that this works without having Git write this file by\n> artificially creating one in our test script, at least until 'git rebase\n> --update-refs' is implemented and we can use it directly.\n\nThis looks good, I've left a couple of comments below but I'm not sure \nthey're even worth reading let alone replying to!\n\nBest Wishes\n\nPhillip\n\n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>   branch.c                  | 13 +++++++\n>   sequencer.c               | 74 +++++++++++++++++++++++++++++++++++++++\n>   sequencer.h               |  9 +++++\n>   t/t2407-worktree-heads.sh | 23 ++++++++++++\n>   4 files changed, 119 insertions(+)\n> \n> diff --git a/branch.c b/branch.c\n> index 526e8237673..f252c4eef6c 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -365,6 +365,7 @@ static void prepare_checked_out_branches(void)\n>   \t\tchar *old;\n>   \t\tstruct wt_status_state state = { 0 };\n>   \t\tstruct worktree *wt = worktrees[i++];\n> +\t\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n>   \n>   \t\tif (wt->is_bare)\n>   \t\t\tcontinue;\n> @@ -400,6 +401,18 @@ static void prepare_checked_out_branches(void)\n>   \t\t\tstrbuf_release(&ref);\n>   \t\t}\n>   \t\twt_status_state_free_buffers(&state);\n> +\n> +\t\tif (!sequencer_get_update_refs_state(get_worktree_git_dir(wt),\n> +\t\t\t\t\t\t     &update_refs)) {\n> +\t\t\tstruct string_list_item *item;\n> +\t\t\tfor_each_string_list_item(item, &update_refs) {\n> +\t\t\t\told = strmap_put(&current_checked_out_branches,\n> +\t\t\t\t\t\t item->string,\n> +\t\t\t\t\t\t xstrdup(wt->path));\n> +\t\t\t\tfree(old);\n> +\t\t\t}\n> +\t\t\tstring_list_clear(&update_refs, 1);\n> +\t\t}\n>   \t}\n>   \n>   \tfree_worktrees(worktrees);\n> diff --git a/sequencer.c b/sequencer.c\n> index 8c3ed3532ac..e93c61cbd25 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -147,6 +147,20 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n>    */\n>   static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n>   \n> +/*\n> + * The update-refs file stores a list of refs that will be updated at the end\n> + * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n> + * update the OIDs for the refs in this file, but the refs are not updated\n> + * until the end of the rebase sequence.\n> + *\n> + * rebase_path_update_refs() returns the path to this file for a given\n> + * worktree directory. For the current worktree, pass the_repository->gitdir.\n> + */\n> +static char *rebase_path_update_refs(const char *wt_dir)\n\nIt is nice to see this function. The naming  of wt_dir confused me \nslightly, wt_git_dir would make it clear that this function takes the \ngit_dir of the worktree and not the worktree directory. Theu \ndocumentation does a good job of clarifying that though.\n\n> +{\n> +\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n> +}\n> +\n>   /*\n>    * The following files are written by git-rebase just after parsing the\n>    * command-line.\n> @@ -169,6 +183,15 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, \"rebase-merge/no-res\n>   static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n>   static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n>   \n> +/**\n> + * A 'struct update_refs_record' represents a value in the update-refs\n> + * list. We use a string_list to map refs to these (before, after) pairs.\n> + */\n> +struct update_ref_record {\n> +\tstruct object_id before;\n> +\tstruct object_id after;\n> +};\n> +\n>   static int git_sequencer_config(const char *k, const char *v, void *cb)\n>   {\n>   \tstruct replay_opts *opts = cb;\n> @@ -5901,3 +5924,54 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)\n>   \n>   \treturn 0;\n>   }\n> +\n> +int sequencer_get_update_refs_state(const char *wt_dir,\n> +\t\t\t\t    struct string_list *refs)\n> +{\n> +\tint result = 0;\n> +\tFILE *fp = NULL;\n> +\tstruct strbuf ref = STRBUF_INIT;\n> +\tstruct strbuf hash = STRBUF_INIT;\n> +\tstruct update_ref_record *rec = NULL;\n> +\n> +\tchar *path = rebase_path_update_refs(wt_dir);\n> +\n> +\tfp = fopen(path, \"r\");\n> +\tif (!fp)\n> +\t\tgoto cleanup;\n> +\n> +\twhile (strbuf_getline(&ref, fp) != EOF) {\n> +\t\tstruct string_list_item *item;\n> +\n> +\t\tCALLOC_ARRAY(rec, 1);\n\nIt does not matter but later in the series we use rec = \nxmalloc(sizeof(*rec)) to do this. I'm not sure the project has a \nstandard way of allocating a single instance of something.\n\n> +\n> +\t\tif (strbuf_getline(&hash, fp) == EOF ||\n> +\t\t    get_oid_hex(hash.buf, &rec->before)) {\n> +\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n> +\t\t\t\t  path);\n> +\t\t\tresult = -1;\n> +\t\t\tgoto cleanup;\n> +\t\t}\n> +\n> +\t\tif (strbuf_getline(&hash, fp) == EOF ||\n> +\t\t    get_oid_hex(hash.buf, &rec->after)) {\n> +\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n> +\t\t\t\t  path);\n> +\t\t\tresult = -1;\n> +\t\t\tgoto cleanup;\n> +\t\t}\n> +\n> +\t\titem = string_list_insert(refs, ref.buf);\n> +\t\titem->util = rec;\n> +\t\trec = NULL;\n> +\t}\n> +\n> +cleanup:\n> +\tif (fp)\n> +\t\tfclose(fp);\n> +\tfree(path);\n> +\tfree(rec);\n> +\tstrbuf_release(&ref);\n> +\tstrbuf_release(&hash);\n> +\treturn result;\n> +}\n> diff --git a/sequencer.h b/sequencer.h\n> index da64473636b..3ae541bb145 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -232,4 +232,13 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose);\n>   int sequencer_get_last_command(struct repository* r,\n>   \t\t\t       enum replay_action *action);\n>   int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);\n> +\n> +/**\n> + * Append the set of ref-OID pairs that are currently stored for the 'git\n> + * rebase --update-refs' feature if such a rebase is currently happening.\n> + *\n> + * Localized to a worktree's git dir.\n> + */\n> +int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs);\n> +\n>   #endif /* SEQUENCER_H */\n> diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\n> index a67ce5fb003..97f5c87f8c8 100755\n> --- a/t/t2407-worktree-heads.sh\n> +++ b/t/t2407-worktree-heads.sh\n> @@ -81,6 +81,29 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n>   \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n>   '\n>   \n> +test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n> +\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n> +\n> +\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n> +\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n> +\n> +\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n> +\trefs/heads/fake-3\n> +\t$(git rev-parse HEAD~1)\n> +\t$(git rev-parse HEAD)\n> +\trefs/heads/fake-4\n> +\t$(git rev-parse HEAD)\n> +\t$(git rev-parse HEAD)\n> +\tEOF\n> +\n> +\tfor i in 3 4\n> +\tdo\n> +\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n> +\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n> +\t\t\treturn 1\n> +\tdone\n> +'\n> +\n>   test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n>   \ttest_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&\n>   \tgrep \"refusing to fetch into branch '\\''refs/heads/wt-3'\\''\" err &&\n\n"},{"id":"459151","messageId":"e620bb51-6c58-cfbb-936b-be46c2f5216a@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/12] rebase: update branches in multi-part topic","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-15T15:41:02Z","receivedAt":"2022-07-15T15:41:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 12/07/2022 14:06, Derrick Stolee via GitGitGadget wrote:\n\nI haven't looked at the first two patches but I think I read everything \nelse that has changed in V4. It all looks good to me. I've left some \ncomments on the later patches but I don't think there are any \nshowstoppers and this version could be merged into next.\n\nThanks for working on this\n\nPhillip\n\n> This series is based on ds/branch-checked-out.\n> \n> This is a feature I've wanted for quite a while. When working on the sparse\n> index topic, I created a long RFC that actually broke into three topics for\n> full review upstream. These topics were sequential, so any feedback on an\n> earlier one required updates to the later ones. I would work on the full\n> feature and use interactive rebase to update the full list of commits.\n> However, I would need to update the branches pointing to those sub-topics.\n> \n> This series adds a new --update-refs option to 'git rebase' (along with a\n> rebase.updateRefs config option) that adds 'update-ref' commands into the\n> TODO list. This is powered by the commit decoration machinery.\n> \n> As an example, here is my in-progress bundle URI RFC split into subtopics as\n> they appear during the TODO list of a git rebase -i --update-refs:\n> \n> pick 2d966282ff3 docs: document bundle URI standard\n> pick 31396e9171a remote-curl: add 'get' capability\n> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n> pick 6adaf842684 fetch: add --bundle-uri option\n> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n> update-ref refs/heads/bundle-redo/fetch\n> \n> pick 1e3f6546632 clone: add --bundle-uri option\n> pick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\n> update-ref refs/heads/bundle-redo/clone\n> \n> pick 5451cb6599c bundle-uri: create bundle_list struct and helpers\n> pick 3029c3aca15 bundle-uri: create base key-value pair parsing\n> pick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\n> pick 92625a47673 bundle-uri: unit test \"key=value\" parsing\n> pick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\n> pick 9d6809a8d53 bundle-uri: parse bundle list in config format\n> pick 287a732b54c bundle-uri: fetch a list of bundles\n> update-ref refs/heads/bundle-redo/list\n> \n> pick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\n> pick 520204dcd1c bundle-uri client: add minimal NOOP client\n> pick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\n> pick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\n> pick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\n> pick caf4599a81d bundle-uri: allow relative URLs in bundle lists\n> pick df255000b7e bundle-uri: download bundles from an advertised list\n> pick d71beabf199 clone: unbundle the advertised bundles\n> pick c9578391976 t5601: basic bundle URI tests\n> # Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n> \n> update-ref refs/heads/bundle-redo/advertise\n> \n> \n> Here is an outline of the series:\n> \n>   * Patch 1 updates some tests for branch_checked_out() to use 'git bisect'\n>     and 'git rebase' as black-boxes instead of manually editing files inside\n>     $GIT_DIR. (Thanks, Junio!)\n>   * Patch 2 updates some tests for branch_checked_out() for the 'apply'\n>     backend.\n>   * Patch 3 updates branch_checked_out() to parse the\n>     rebase-merge/update-refs file to block concurrent ref updates and\n>     checkouts on branches selected by --update-refs.\n>   * Patch 4 updates the todo list documentation to remove some unnecessary\n>     dots in the 'merge' command. This makes it consistent with the 'fixup'\n>     command before we document the 'update-ref' command.\n>   * Patch 5 updates the definition of todo_command_info to use enum values as\n>     array indices.\n>   * Patches 6-8 implement the --update-refs logic itself.\n>   * Patch 9 specifically updates the update-refs file every time the user\n>     edits the todo-list (Thanks Phillip!)\n>   * Patch 10 adds the rebase.updateRefs config option similar to\n>     rebase.autoSquash.\n>   * Patch 11 ignores the HEAD ref when creating the todo list instead of\n>     making a comment (Thanks Elijah!)\n>   * Patch 12 adds messaging to the end of the rebase stating which refs were\n>     updated (Thanks Elijah!)\n> \n> \n> Updates in v4\n> =============\n> \n> This version took longer than I'd hoped (I had less time to work on it than\n> anticipated) but it also has some major updates. These major updates are\n> direct responses to the significant review this series has received. Thank\n> you!\n> \n>   * The update-refs file now stores \"ref/before/after\" triples (still\n>     separated by lines). This allows us to store the \"before\" OID of a ref in\n>     addition to the \"after\" that we will write to that ref at the end of the\n>     rebase. This allows us to do a \"force-with-lease\" update. The\n>     branch_checked_out() updates should prevent Git from updating those refs\n>     while under the rebase, but older versions and third-party tools don't\n>     have that protection.\n>   * The update-refs file is updated with every update to the todo-list file.\n>     This allows for some advanced changes to the file, including removing,\n>     adding, and duplicating 'update-ref' commands.\n>   * The message at the end of the rebase process now lists which refs were\n>     updated with the update-ref steps. This includes any ref updates that\n>     fail.\n>   * The branch_checked_out() tests now use 'git bisect' and 'git rebase' as\n>     black-boxes instead of testing their internals directly.\n> \n> Here are the more minor updates:\n> \n>   * Dropped an unnecessary stat() call.\n>   * Updated commit messages to include extra details, based on confusion in\n>     last round.\n>   * The HEAD branch no longer appears as a comment line in the initial todo\n>     list.\n>   * The update-refs file is now written using a lockfile.\n>   * Tests now use test_cmp_rev.\n>   * A memory leak ('path' variable) is resolved.\n> \n> \n> Updates in v3\n> =============\n> \n>   * The branch_checked_out() API was extracted to its own topic and is now\n>     the ds/branch-checked-out branch. This series is now based on that one.\n>   * The for_each_decoration() API was removed, since it became trivial once\n>     it did not take a commit directly.\n>   * The branch_checked_out() tests did not verify the rebase-apply data (for\n>     the apply backend), so that is fixed.\n>   * Instead of using the 'label' command and a final 'update-refs' command in\n>     the todo list, use a new 'update-ref ' command. This command updates the\n>     rebase-merge/update-refs file with the OID of HEAD at these steps. At the\n>     very end of the rebase sequence, those refs are updated to the stored OID\n>     values (assuming that they were not removed by the user, in which case we\n>     notice that the OID is the null OID and we do nothing).\n>   * New tests are added.\n>   * The todo-list comment documentation has some new formatting updates, but\n>     also includes a description of 'update-refs' in this version.\n> \n> \n> Updates in v2\n> =============\n> \n> As recommended by the excellent feedback, I have removed the 'exec' commands\n> in favor of the 'label' commands and a new 'update-refs' command at the very\n> end. This way, there is only one step that updates all of the refs at the\n> end instead of updating refs during the rebase. If a user runs 'git rebase\n> --abort' in the middle, then their refs are still where they need to be.\n> \n> Based on some of the discussion, it seemed like one way to do this would be\n> to have an 'update-ref ' command that would take the place of these 'label'\n> commands. However, this would require two things that make it a bit awkward:\n> \n>   1. We would need to replicate the storage of those positions during the\n>      rebase. 'label' already does this pretty well. I've added the\n>      \"for-update-refs/\" label to help here.\n>   2. If we want to close out all of the refs as the rebase is finishing, then\n>      that \"step\" becomes invisible to the user (and a bit more complicated to\n>      insert). Thus, the 'update-refs' step performs this action. If the user\n>      wants to do things after that step, then they can do so by editing the\n>      TODO list.\n> \n> Other updates:\n> \n>   * The 'keep_decorations' parameter was renamed to 'update_refs'.\n>   * I added tests for --rebase-merges=rebase-cousins to show how these labels\n>     interact with other labels and merge commands.\n>   * I changed the order of the insertion of these update-refs labels to be\n>     before the fixups are rearranged. This fixes a bug where the tip commit\n>     is a fixup! so its decorations are never inspected (and they would be in\n>     the wrong place even if they were). The fixup! commands are properly\n>     inserted between a pick and its following label command. Tests\n>     demonstrate this is correct.\n>   * Numerous style choices are updated based on feedback.\n> \n> Thank you for all of the detailed review and ideas in this space. I\n> appreciate any more ideas that can make this feature as effective as it can\n> be.\n> \n> Thanks, -Stolee\n> \n> Derrick Stolee (12):\n>    t2407: test bisect and rebase as black-boxes\n>    t2407: test branches currently using apply backend\n>    branch: consider refs under 'update-refs'\n>    rebase-interactive: update 'merge' description\n>    sequencer: define array with enum values\n>    sequencer: add update-ref command\n>    rebase: add --update-refs option\n>    rebase: update refs from 'update-ref' commands\n>    sequencer: rewrite update-refs as user edits todo list\n>    rebase: add rebase.updateRefs config option\n>    sequencer: ignore HEAD ref under --update-refs\n>    sequencer: notify user of --update-refs activity\n> \n>   Documentation/config/rebase.txt |   3 +\n>   Documentation/git-rebase.txt    |  11 +\n>   branch.c                        |  13 +\n>   builtin/rebase.c                |  10 +\n>   rebase-interactive.c            |  15 +-\n>   sequencer.c                     | 469 +++++++++++++++++++++++++++++++-\n>   sequencer.h                     |  23 ++\n>   t/lib-rebase.sh                 |  15 +\n>   t/t2407-worktree-heads.sh       | 103 +++++--\n>   t/t3404-rebase-interactive.sh   | 269 ++++++++++++++++++\n>   10 files changed, 887 insertions(+), 44 deletions(-)\n> \n> \n> base-commit: 9bef0b1e6ec371e786c2fba3edcc06ad040a536c\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v4\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v4\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1247\n> \n> Range-diff vs v3:\n> \n>    -:  ----------- >  1:  9e53a27017a t2407: test bisect and rebase as black-boxes\n>    1:  fbaedc7f1f0 !  2:  540a3be256f t2407: test branches currently using apply backend\n>       @@ Commit message\n>            Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n>        \n>         ## t/t2407-worktree-heads.sh ##\n>       -@@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite: worktree in bisect' '\n>       - \tgrep \"cannot force update the branch '\\''fake-2'\\'' checked out at.*wt-4\" err\n>       +@@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in bisect' '\n>       + \tgrep \"cannot force update the branch '\\''wt-4'\\'' checked out at.*wt-4\" err\n>         '\n>         \n>       --test_expect_success 'refuse to overwrite: worktree in rebase' '\n>       -+test_expect_success 'refuse to overwrite: worktree in rebase (apply)' '\n>       -+\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-apply &&\n>       +-test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase' '\n>       ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (apply)' '\n>       ++\ttest_when_finished git -C wt-2 rebase --abort &&\n>        +\n>       -+\tmkdir -p .git/worktrees/wt-3/rebase-apply &&\n>       -+\techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-apply/head-name &&\n>       -+\techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-apply/onto &&\n>       ++\t# This will fail part-way through due to a conflict.\n>       ++\ttest_must_fail git -C wt-2 rebase --apply conflict-2 &&\n>        +\n>       -+\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n>       -+\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n>       ++\ttest_must_fail git branch -f wt-2 HEAD 2>err &&\n>       ++\tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n>        +'\n>        +\n>       -+test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n>       - \ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n>       ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (merge)' '\n>       + \ttest_when_finished git -C wt-2 rebase --abort &&\n>         \n>       - \tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n>       + \t# This will fail part-way through due to a conflict.\n>    2:  2bc647b6fcd !  3:  bf301a054e3 branch: consider refs under 'update-refs'\n>       @@ Commit message\n>            branch_checked_out().\n>        \n>            The data store is a plaintext file inside the 'rebase-merge' directory\n>       -    for that worktree. The file alternates refnames and OIDs. The OIDs will\n>       -    be used to store the to-be-written values as the rebase progresses, but\n>       -    can be ignored at the moment.\n>       +    for that worktree. The file lists refnames followed by two OIDs, each on\n>       +    separate lines. The OIDs will be used to store the original values of\n>       +    the refs and the to-be-written values as the rebase progresses, but can\n>       +    be ignored at the moment.\n>        \n>            Create a new sequencer_get_update_refs_state() method that parses this\n>            file and populates a struct string_list with the ref-OID pairs. We can\n>       @@ Commit message\n>            method.\n>        \n>            We can test that this works without having Git write this file by\n>       -    artificially creating one in our test script.\n>       +    artificially creating one in our test script, at least until 'git rebase\n>       +    --update-refs' is implemented and we can use it directly.\n>        \n>            Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n>        \n>       @@ branch.c: static void prepare_checked_out_branches(void)\n>         \tfree_worktrees(worktrees);\n>        \n>         ## sequencer.c ##\n>       +@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n>       +  */\n>       + static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n>       +\n>       ++/*\n>       ++ * The update-refs file stores a list of refs that will be updated at the end\n>       ++ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n>       ++ * update the OIDs for the refs in this file, but the refs are not updated\n>       ++ * until the end of the rebase sequence.\n>       ++ *\n>       ++ * rebase_path_update_refs() returns the path to this file for a given\n>       ++ * worktree directory. For the current worktree, pass the_repository->gitdir.\n>       ++ */\n>       ++static char *rebase_path_update_refs(const char *wt_dir)\n>       ++{\n>       ++\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n>       ++}\n>       ++\n>       + /*\n>       +  * The following files are written by git-rebase just after parsing the\n>       +  * command-line.\n>       +@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, \"rebase-merge/no-res\n>       + static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n>       + static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n>       +\n>       ++/**\n>       ++ * A 'struct update_refs_record' represents a value in the update-refs\n>       ++ * list. We use a string_list to map refs to these (before, after) pairs.\n>       ++ */\n>       ++struct update_ref_record {\n>       ++\tstruct object_id before;\n>       ++\tstruct object_id after;\n>       ++};\n>       ++\n>       + static int git_sequencer_config(const char *k, const char *v, void *cb)\n>       + {\n>       + \tstruct replay_opts *opts = cb;\n>        @@ sequencer.c: int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)\n>         \n>         \treturn 0;\n>       @@ sequencer.c: int sequencer_determine_whence(struct repository *r, enum commit_wh\n>        +\t\t\t\t    struct string_list *refs)\n>        +{\n>        +\tint result = 0;\n>       -+\tstruct stat st;\n>        +\tFILE *fp = NULL;\n>        +\tstruct strbuf ref = STRBUF_INIT;\n>        +\tstruct strbuf hash = STRBUF_INIT;\n>       -+\tchar *path = xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n>       ++\tstruct update_ref_record *rec = NULL;\n>        +\n>       -+\tif (stat(path, &st))\n>       -+\t\tgoto cleanup;\n>       ++\tchar *path = rebase_path_update_refs(wt_dir);\n>        +\n>        +\tfp = fopen(path, \"r\");\n>        +\tif (!fp)\n>        +\t\tgoto cleanup;\n>        +\n>        +\twhile (strbuf_getline(&ref, fp) != EOF) {\n>       -+\t\tstruct object_id oid;\n>        +\t\tstruct string_list_item *item;\n>        +\n>       ++\t\tCALLOC_ARRAY(rec, 1);\n>       ++\n>        +\t\tif (strbuf_getline(&hash, fp) == EOF ||\n>       -+\t\t    get_oid_hex(hash.buf, &oid)) {\n>       ++\t\t    get_oid_hex(hash.buf, &rec->before)) {\n>        +\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n>        +\t\t\t\t  path);\n>        +\t\t\tresult = -1;\n>        +\t\t\tgoto cleanup;\n>        +\t\t}\n>        +\n>       -+\t\titem = string_list_append(refs, ref.buf);\n>       -+\t\titem->util = oiddup(&oid);\n>       ++\t\tif (strbuf_getline(&hash, fp) == EOF ||\n>       ++\t\t    get_oid_hex(hash.buf, &rec->after)) {\n>       ++\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n>       ++\t\t\t\t  path);\n>       ++\t\t\tresult = -1;\n>       ++\t\t\tgoto cleanup;\n>       ++\t\t}\n>       ++\n>       ++\t\titem = string_list_insert(refs, ref.buf);\n>       ++\t\titem->util = rec;\n>       ++\t\trec = NULL;\n>        +\t}\n>        +\n>        +cleanup:\n>        +\tif (fp)\n>        +\t\tfclose(fp);\n>        +\tfree(path);\n>       ++\tfree(rec);\n>        +\tstrbuf_release(&ref);\n>        +\tstrbuf_release(&hash);\n>        +\treturn result;\n>       @@ sequencer.h: void sequencer_post_commit_cleanup(struct repository *r, int verbos\n>         #endif /* SEQUENCER_H */\n>        \n>         ## t/t2407-worktree-heads.sh ##\n>       -@@ t/t2407-worktree-heads.sh: TEST_PASSES_SANITIZE_LEAK=true\n>       +@@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n>       + \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n>       + '\n>         \n>       - test_expect_success 'setup' '\n>       - \ttest_commit init &&\n>       --\tgit branch -f fake-1 &&\n>       --\tgit branch -f fake-2 &&\n>       ++test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n>       ++\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n>       ++\n>       ++\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n>       ++\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n>        +\n>       -+\tfor i in 1 2 3 4\n>       -+\tdo\n>       -+\t\tgit branch -f fake-$i || return 1\n>       -+\tdone &&\n>       -\n>       - \tfor i in 1 2 3 4\n>       - \tdo\n>       -@@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite: worktree in rebase (merge)' '\n>       - \techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&\n>       - \techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&\n>       -\n>       --\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n>       --\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n>        +\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n>        +\trefs/heads/fake-3\n>        +\t$(git rev-parse HEAD~1)\n>       ++\t$(git rev-parse HEAD)\n>        +\trefs/heads/fake-4\n>        +\t$(git rev-parse HEAD)\n>       ++\t$(git rev-parse HEAD)\n>        +\tEOF\n>        +\n>       -+\tfor i in 1 3 4\n>       ++\tfor i in 3 4\n>        +\tdo\n>        +\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n>        +\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n>        +\t\t\treturn 1\n>        +\tdone\n>       - '\n>       -\n>       ++'\n>       ++\n>         test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n>       + \ttest_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&\n>       + \tgrep \"refusing to fetch into branch '\\''refs/heads/wt-3'\\''\" err &&\n>    3:  669f4abd59e !  4:  dec95681d2b rebase-interactive: update 'merge' description\n>       @@ Commit message\n>            The 'merge' command description for the todo list documentation in an\n>            interactive rebase has multiple lines. The lines other than the first\n>            one start with dots ('.') while the similar multi-line documentation for\n>       -    'fixup' does not.\n>       +    'fixup' does not. This description only appears in the comment text of\n>       +    the todo file during an interactive rebase.\n>        \n>            The 'merge' command was documented when interactive rebase was first\n>            ported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n>    4:  6528a50343f =  5:  b2c09600918 sequencer: define array with enum values\n>    5:  e95ad41d355 =  6:  fa7ecb718cf sequencer: add update-ref command\n>    6:  918b398d6a2 !  7:  3ec2cc922f9 rebase: add --update-refs option\n>       @@ Commit message\n>            'update-ref' commands. Tests are added to ensure that these todo\n>            commands are added in the correct locations.\n>        \n>       -    A future change will update the behavior to actually update the refs\n>       -    at the end of the rebase sequence.\n>       +    This change does _not_ include the actual behavior of tracking the\n>       +    updated refs and writing the new ref values at the end of the rebase\n>       +    process. That is deferred to a later change.\n>        \n>            Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n>        \n>       @@ sequencer.c: static int skip_unnecessary_picks(struct repository *r,\n>        +\t\t\titem->command = TODO_UPDATE_REF;\n>        +\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n>        +\n>       -+\t\t\tsti = string_list_append(&ctx->refs_to_oids,\n>       ++\t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n>        +\t\t\t\t\t\t decoration->name);\n>        +\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n>        +\t\t}\n>       @@ t/t2407-worktree-heads.sh: test_expect_success 'refuse to overwrite when in erro\n>        +\t\tgrep \"update-ref refs/heads/allow-update\" todo\n>        +\t)\n>        +'\n>       ++\n>       ++# This must be the last test in this file\n>       ++test_expect_success '$EDITOR and friends are unchanged' '\n>       ++\ttest_editor_unchanged\n>       ++'\n>        +\n>         test_done\n>        \n>    7:  72e0481b643 !  8:  fb5f64c5201 rebase: update refs from 'update-ref' commands\n>       @@ Commit message\n>            interactive rebase.\n>        \n>            Teach Git to record the HEAD position when reaching these 'update-ref'\n>       -    commands. The ref/OID pair is stored in the\n>       +    commands. The ref/before/after triple is stored in the\n>            $GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\n>            file to avoid having other processes updating the refs in that file\n>            while the rebase is in progress.\n>       @@ Commit message\n>            Not only do we update the file when the sequencer reaches these\n>            'update-ref' commands, we then update the refs themselves at the end of\n>            the rebase sequence. If the rebase is aborted before this final step,\n>       -    then the refs are not updated.\n>       +    then the refs are not updated. The 'before' value is used to ensure that\n>       +    we do not accidentally obliterate a ref that was updated concurrently\n>       +    (say, by an older version of Git or a third-party tool).\n>       +\n>       +    Now that the 'git rebase --update-refs' command is implemented to write\n>       +    to the update-refs file, we can remove the fake construction of the\n>       +    update-refs file from a test in t2407-worktree-heads.sh.\n>        \n>            Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n>        \n>       @@ sequencer.c\n>         \n>         #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>         \n>       -@@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n>       -  */\n>       - static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n>       +@@ sequencer.c: struct update_ref_record {\n>       + \tstruct object_id after;\n>       + };\n>         \n>       -+/*\n>       -+ * The update-refs file stores a list of refs that will be updated at the end\n>       -+ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n>       -+ * update the OIDs for the refs in this file, but the refs are not updated\n>       -+ * until the end of the rebase sequence.\n>       -+ */\n>       -+static GIT_PATH_FUNC(rebase_path_update_refs, \"rebase-merge/update-refs\")\n>       -+\n>       - /*\n>       -  * The following files are written by git-rebase just after parsing the\n>       -  * command-line.\n>       ++static struct update_ref_record *init_update_ref_record(const char *ref)\n>       ++{\n>       ++\tstruct update_ref_record *rec = xmalloc(sizeof(*rec));\n>       ++\n>       ++\toidcpy(&rec->before, null_oid());\n>       ++\toidcpy(&rec->after, null_oid());\n>       ++\n>       ++\t/* This may fail, but that's fine, we will keep the null OID. */\n>       ++\tread_ref(ref, &rec->before);\n>       ++\n>       ++\treturn rec;\n>       ++}\n>       ++\n>       + static int git_sequencer_config(const char *k, const char *v, void *cb)\n>       + {\n>       + \tstruct replay_opts *opts = cb;\n>        @@ sequencer.c: leave_merge:\n>         \treturn ret;\n>         }\n>         \n>        -static int do_update_ref(struct repository *r, const char *ref_name)\n>        +static int write_update_refs_state(struct string_list *refs_to_oids)\n>       -+{\n>       + {\n>        +\tint result = 0;\n>       ++\tstruct lock_file lock = LOCK_INIT;\n>        +\tFILE *fp = NULL;\n>        +\tstruct string_list_item *item;\n>       -+\tchar *path = xstrdup(rebase_path_update_refs());\n>       ++\tchar *path;\n>       ++\n>       ++\tif (!refs_to_oids->nr)\n>       ++\t\treturn 0;\n>       ++\n>       ++\tpath = rebase_path_update_refs(the_repository->gitdir);\n>        +\n>        +\tif (safe_create_leading_directories(path)) {\n>        +\t\tresult = error(_(\"unable to create leading directories of %s\"),\n>       @@ sequencer.c: leave_merge:\n>        +\t\tgoto cleanup;\n>        +\t}\n>        +\n>       -+\tfp = fopen(path, \"w\");\n>       ++\tif (hold_lock_file_for_update(&lock, path, 0) < 0) {\n>       ++\t\tresult = error(_(\"another 'rebase' process appears to be running; \"\n>       ++\t\t\t\t \"'%s.lock' already exists\"),\n>       ++\t\t\t       path);\n>       ++\t\tgoto cleanup;\n>       ++\t}\n>       ++\n>       ++\tfp = fdopen_lock_file(&lock, \"w\");\n>        +\tif (!fp) {\n>        +\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n>       ++\t\trollback_lock_file(&lock);\n>        +\t\tgoto cleanup;\n>        +\t}\n>        +\n>       -+\tfor_each_string_list_item(item, refs_to_oids)\n>       -+\t\tfprintf(fp, \"%s\\n%s\\n\", item->string, oid_to_hex(item->util));\n>       ++\tfor_each_string_list_item(item, refs_to_oids) {\n>       ++\t\tstruct update_ref_record *rec = item->util;\n>       ++\t\tfprintf(fp, \"%s\\n%s\\n%s\\n\", item->string,\n>       ++\t\t\toid_to_hex(&rec->before), oid_to_hex(&rec->after));\n>       ++\t}\n>       ++\n>       ++\tresult = commit_lock_file(&lock);\n>        +\n>        +cleanup:\n>       -+\tif (fp)\n>       -+\t\tfclose(fp);\n>       ++\tfree(path);\n>        +\treturn result;\n>        +}\n>        +\n>        +static int do_update_ref(struct repository *r, const char *refname)\n>       - {\n>       ++{\n>        +\tstruct string_list_item *item;\n>        +\tstruct string_list list = STRING_LIST_INIT_DUP;\n>       -+\tint found = 0;\n>        +\n>        +\tsequencer_get_update_refs_state(r->gitdir, &list);\n>        +\n>        +\tfor_each_string_list_item(item, &list) {\n>        +\t\tif (!strcmp(item->string, refname)) {\n>       -+\t\t\tstruct object_id oid;\n>       -+\t\t\tfree(item->util);\n>       -+\t\t\tfound = 1;\n>       -+\n>       -+\t\t\tif (!read_ref(\"HEAD\", &oid)) {\n>       -+\t\t\t\titem->util = oiddup(&oid);\n>       -+\t\t\t\tbreak;\n>       -+\t\t\t}\n>       ++\t\t\tstruct update_ref_record *rec = item->util;\n>       ++\t\t\tread_ref(\"HEAD\", &rec->after);\n>       ++\t\t\tbreak;\n>        +\t\t}\n>        +\t}\n>        +\n>       -+\tif (!found) {\n>       -+\t\tstruct object_id oid;\n>       -+\t\titem = string_list_append(&list, refname);\n>       -+\n>       -+\t\tif (!read_ref(\"HEAD\", &oid))\n>       -+\t\t\titem->util = oiddup(&oid);\n>       -+\t\telse\n>       -+\t\t\titem->util = oiddup(the_hash_algo->null_oid);\n>       -+\t}\n>       -+\n>        +\twrite_update_refs_state(&list);\n>        +\tstring_list_clear(&list, 1);\n>         \treturn 0;\n>       @@ sequencer.c: leave_merge:\n>        +\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n>        +\n>        +\tfor_each_string_list_item(item, &refs_to_oids) {\n>       -+\t\tstruct object_id *oid_to = item->util;\n>       -+\t\tstruct object_id oid_from;\n>       ++\t\tstruct update_ref_record *rec = item->util;\n>        +\n>       -+\t\tif (oideq(oid_to, the_hash_algo->null_oid)) {\n>       ++\t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n>        +\t\t\t/*\n>        +\t\t\t * Ref was not updated. User may have deleted the\n>        +\t\t\t * 'update-ref' step.\n>       @@ sequencer.c: leave_merge:\n>        +\t\t\tcontinue;\n>        +\t\t}\n>        +\n>       -+\t\tif (read_ref(item->string, &oid_from)) {\n>       -+\t\t\t/*\n>       -+\t\t\t * The ref does not exist. The user probably\n>       -+\t\t\t * inserted a new 'update-ref' step with a new\n>       -+\t\t\t * branch name.\n>       -+\t\t\t */\n>       -+\t\t\toidcpy(&oid_from, the_hash_algo->null_oid);\n>       -+\t\t}\n>       -+\n>        +\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n>       -+\t\t\t\titem->string,\n>       -+\t\t\t\toid_to, &oid_from,\n>       -+\t\t\t\t0, UPDATE_REFS_MSG_ON_ERR);\n>       ++\t\t\t\t       item->string,\n>       ++\t\t\t\t       &rec->after, &rec->before,\n>       ++\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n>        +\t}\n>        +\n>        +\tstring_list_clear(&refs_to_oids, 1);\n>       @@ sequencer.c: cleanup_head_ref:\n>         \t/*\n>         \t * Sequence of picks finished successfully; cleanup by\n>         \t * removing the .git/sequencer directory\n>       +@@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n>       +\n>       + \t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n>       + \t\t\t\t\t\t decoration->name);\n>       +-\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n>       ++\t\t\tsti->util = init_update_ref_record(decoration->name);\n>       + \t\t}\n>       +\n>       + \t\titem->offset_in_buf = base_offset;\n>        @@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n>         \t\t}\n>         \t}\n>       @@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo\n>         \tfree(todo_list->items);\n>         \ttodo_list->items = ctx.items;\n>        \n>       + ## t/t2407-worktree-heads.sh ##\n>       +@@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n>       + \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n>       + '\n>       +\n>       +-test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n>       +-\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n>       +-\n>       +-\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n>       +-\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n>       ++test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' '\n>       ++\ttest_when_finished git -C wt-3 rebase --abort &&\n>       +\n>       +-\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n>       +-\trefs/heads/fake-3\n>       +-\t$(git rev-parse HEAD~1)\n>       +-\t$(git rev-parse HEAD)\n>       +-\trefs/heads/fake-4\n>       +-\t$(git rev-parse HEAD)\n>       +-\t$(git rev-parse HEAD)\n>       +-\tEOF\n>       ++\tgit branch -f can-be-updated wt-3 &&\n>       ++\ttest_must_fail git -C wt-3 rebase --update-refs conflict-3 &&\n>       +\n>       + \tfor i in 3 4\n>       + \tdo\n>       +-\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n>       +-\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n>       ++\t\ttest_must_fail git branch -f can-be-updated HEAD 2>err &&\n>       ++\t\tgrep \"cannot force update the branch '\\''can-be-updated'\\'' checked out at.*wt-3\" err ||\n>       + \t\t\treturn 1\n>       + \tdone\n>       + '\n>       +\n>         ## t/t3404-rebase-interactive.sh ##\n>        @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands with --rebase-merges' '\n>         \t)\n>         '\n>         \n>       -+compare_two_refs () {\n>       -+\tgit rev-parse $1 >expect &&\n>       -+\tgit rev-parse $2 >actual &&\n>       -+\ttest_cmp expect actual\n>       -+}\n>       -+\n>        +test_expect_success '--update-refs updates refs correctly' '\n>        +\tgit checkout -B update-refs no-conflict-branch &&\n>        +\tgit branch -f base HEAD~4 &&\n>       @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs adds commands\n>        +\n>        +\tgit rebase -i --autosquash --update-refs primary &&\n>        +\n>       -+\tcompare_two_refs HEAD~3 refs/heads/first &&\n>       -+\tcompare_two_refs HEAD~3 refs/heads/second &&\n>       -+\tcompare_two_refs HEAD~1 refs/heads/third &&\n>       -+\tcompare_two_refs HEAD refs/heads/no-conflict-branch\n>       ++\ttest_cmp_rev HEAD~3 refs/heads/first &&\n>       ++\ttest_cmp_rev HEAD~3 refs/heads/second &&\n>       ++\ttest_cmp_rev HEAD~1 refs/heads/third &&\n>       ++\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n>        +'\n>        +\n>         # This must be the last test in this file\n>    -:  ----------- >  9:  29c7c76805a sequencer: rewrite update-refs as user edits todo list\n>    8:  d2cfdbfc431 = 10:  c0022d07579 rebase: add rebase.updateRefs config option\n>    -:  ----------- > 11:  d53b4ff2cee sequencer: ignore HEAD ref under --update-refs\n>    -:  ----------- > 12:  d5cd4b49e46 sequencer: notify user of --update-refs activity\n> \n\n"},{"id":"459208","messageId":"CABPp-BF_F3X4ZMQpV7DRkt-ZzLS2erMKHJAbL6s2eQRxahEg=Q@mail.gmail.com","threadId":"57949","inReplyTo":"29c7c76805abb9b8afd44a65cb39d8f9d3c95892.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 09/12] sequencer: rewrite update-refs as user edits todo list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-16T19:20:23Z","receivedAt":"2022-07-16T19:20:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 12, 2022 at 6:07 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> An interactive rebase provides opportunities for the user to edit the\n> todo list. The --update-refs option initializes the list with some\n> 'update-ref <ref>' steps, but the user could add these manually.\n> Further, the user could add or remove these steps during pauses in the\n> interactive rebase.\n>\n> Add a new method, todo_list_filter_update_refs(), that scans a todo_list\n> and compares it to the stored update-refs file. There are two actions\n> that can happen at this point:\n>\n> 1. If a '<ref>/<before>/<after>' triple in the update-refs file does not\n>    have a matching 'update-ref <ref>' command in the todo-list _and_ the\n>    <after> value is the null OID, then remove that triple. Here, the\n>    user removed the 'update-ref <ref>' command before it was executed,\n>    since if it was executed then the <after> value would store the\n>    commit at that position.\n>\n> 2. If a 'update-ref <ref>' command in the todo-list does not have a\n>    matching '<ref>/<before>/<after>' triple in the update-refs file,\n>    then insert a new one. Store the <before> value to be the current\n>    OID pointed at by <ref>. This is handled inside of the\n>    init_update_ref_record() helper method.\n>\n> We can test that this works by rewriting the todo-list several times in\n> the course of a rebase. Check that each ref is locked or unlocked for\n> updates after each todo-list update. We an also verify that the ref\n\ns/an/can/ ?\n"},{"id":"459209","messageId":"CABPp-BENsp3uc7J_xS0gr_yjWQ25NVuJbdHzCMEcaaGYvLEiKA@mail.gmail.com","threadId":"57949","inReplyTo":"3ec2cc922f971af4e4a558188cf139cc0c0150d6.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-16T19:30:34Z","receivedAt":"2022-07-16T19:30:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 12, 2022 at 6:07 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n[...]\n>\n> +--update-refs::\n> +--no-update-refs::\n> +       Automatically force-update any branches that point to commits that\n> +       are being rebased. Any branches that are checked out in a worktree\n> +       or point to a `squash! ...` or `fixup! ...` commit are not updated\n> +       in this way.\n\nI think the second sentence here should be split.  In particular, I\ndon't think I understand the second half of the second sentence.  Do\nyou intend to say here that branches pointing to a `squash!` or\n`fixup!` will instead update the first `pick` in the ancestry of such\na commit, rather than that such branches are entirely excluded from\nany updates?  That's what I observed in my testing of your v3, at\nleast, and that's the behavior I'd expect this feature to implement,\nbut this documentation doesn't match.\n\n[...]\n> @@ -5660,6 +5764,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>                 item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n>         }\n>\n> +       if (update_refs && todo_list_add_update_ref_commands(todo_list))\n> +               return -1;\n> +\n\nAs a tangent, I find it interesting that you add the update-ref\ncommands as a post-processing step rather than as a part of\nsequencer_make_script().  I don't think you need to change anything,\nbut I am curious due to my git-replay work if you find it advantageous\nto do it this way.\n"},{"id":"459211","messageId":"CABPp-BHjWQahfHMOvvGL2WdReCpHgfjF9Yp5DHPiKebk0iSHbg@mail.gmail.com","threadId":"57949","inReplyTo":"fc2e8f9d-0232-4c19-2e4d-9ba6c9b17ab2@github.com","subject":"Re: [PATCH v4 12/12] sequencer: notify user of --update-refs activity","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-16T20:51:01Z","receivedAt":"2022-07-16T20:51:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 15, 2022 at 6:20 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 7/15/2022 6:12 AM, Phillip Wood wrote:\n>\n> > Thanks for the comprehensive commit message and for implementing an excellent suggestion from Elijah. I wonder if it makes sense to distinguish between the current branch and all the others when writing the update message as we do here or if all the refs should just be in a single list. I also think it doesn't matter much and we can change it later if we want.\n>\n> I'm definitely open to suggestions, but I also think we should start\n> somewhere and see what users think. Since the mechanisms for updating the\n> refs are different, I felt it was appropriate to have different error\n> messages.\n\nI think the separate messages are fine, but I'm a little surprised by\nthe wording of this rationale.  The mechanisms for updating the refs\nare implementational details that are likely not known by the end\nuser, and aren't something they can tweak either.  As such, I don't\nthink the mechanism used should result in any differences in end-user\nmessages here.\n\nHowever, one difference here is that one of the refs' changes (namely,\nthe one corresponding to HEAD) will also have its changes be reflected\nin the current working directory and index.  That may be a good reason\nto keep its update message separate from the rest of the refs.\n\nBut on a related tangent...\n\nI'm still curious if people are going to be surprised to not see an\n\"update-ref\" line in the todo list for the HEAD ref; I was at first,\nand only understood the rationale for excluding it based on the\nimplementational details.  I know as far as the implementation goes\nthat if such a line did appear in the todo list, then you'd probably\njust ignore it (other then ensuring it was the last line) and then let\nthe pre-existing mechanisms in rebase update the HEAD ref...but even\nwith that implementation \"weirdness\", I think incorporating it might\nbe less surprising to the end user than the current behavior.  Or\nmaybe I'm just knee-deep in implementation details anyway, and I'm\njust guessing what others might think.  I don't feel strongly about\nit, just thought I'd surface the thought.\n"},{"id":"459212","messageId":"CABPp-BFe7tWVe2v2y_QXJ_yDD8DDqApkvFAyVx-ACXkb-CcNsw@mail.gmail.com","threadId":"57949","inReplyTo":"73311eba-244b-3c88-d0b9-f97f426f1de6@github.com","subject":"Re: [PATCH v4 00/12] rebase: update branches in multi-part topic","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-16T20:56:34Z","receivedAt":"2022-07-16T20:56:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 14, 2022 at 7:50 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 7/12/22 11:37 AM, Junio C Hamano wrote:> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > This is a tangent, but may serve as some food for thought.\n> >\n> > When I queue (or develop myself) a topic that depends on another\n> > topic, I often do\n> >\n> >     $ git checkout --detach vX.Y.Z ;# choose an appropriate base\n> >     $ git merge --into derived-topic base-topic\n> >     $ develop develop (or \"git am\")\n> >\n> > which would end up in\n> >\n> >     vX.Y.Z -----M---A---B---C derived-topic\n> >                /\n> >      base-topic\n> >\n> > so that \"git log --first-parent --oneline master..\" would show the\n> > commits in the topic plus the fact that it depends on which other\n> > topic recorded in a single merge commit.  A topic that depends on\n> > two or more topics can be handled the same way.\n> >\n> > One good thing about this arrangement, unlike the \"totally linear\"\n> > one depicted at the top of your cover letter, is that it is easier\n> > to rebuild each topic independently and the first-parent view is\n> > still useful.  If you futz with the base topic in a totally linear\n> > history, \"log --decorate\" of the derived topic would no longer tell\n> > you where the old iteration of the base topic ended.\n> >\n> > It would be very nice to see the update-ref feature (or something\n> > like that) makes it easy to deal with such a topology, too.\n>\n> Your topology is an interesting one, but --update-refs isn't limited to\n> linear history.\n>\n> (Begin aside)\n>\n> One goal of this feature is to make it easier to manage the topics that\n> are being juggled in friendly forks. For example, git-for-windows/git and\n> microsoft/git have some topics that evolve version-to-version but might be\n> cleaned up and sent upstream. Both of these forks use 'git rebase\n> --rebase-merges=rebase-cousins' when consuming new upstream versions to\n> keep the merge structure of these new topics. However, we lose the branch\n> names and need to reconstruct them from the context of the merge commits.\n>\n> With --update-refs, we can automatically rewrite the branches that are\n> included in these individual topics. That might make it simpler to extract\n> a series to send upstream.\n>\n> One test in this series does test such a case with the --rebase-merges\n> option.\n>\n> (End aside)\n>\n> Back to your topology, I wonder what your rebase command looks like when\n> tracking those topics.\n>\n> The goal of --update-refs is to help rebase multiple branches at the same\n> time, and with your example here, it would imply you want to rebase both\n> dependent topics.\n>\n>  Before:\n>\n>   A---B----C---M---Q1---Q2---Q3 <-- refs/heads/Q\n>    \\          /\n>     P1--P2--P3 <-- refs/heads/P\n>\n>  After rebasing both topics simultaneously (with 'git rebase --update-refs\n>  C' while Q is checked out):\n>\n>   A---B---C---D---P1---P2---P3---Q1---Q2---Q3\n>                             ^              ^\n>                         refs/heads/P  refs/heads/Q\n>\n> But it seems what you mean to say is to update the merge commit M, which\n> means that the 'P' branch above has been updated independently of the 'Q'\n> branch, so we need to update 'Q' after-the-fact. I'm not sure what that\n> rebase would look like, indepdendent of updating refs.\n\nSince this is an aside, I'll take a chance to talk about stuff I'm\nworking on.  After\n\n  git replay --onto main --contained main..Q\n\n(where \"--contained\" seems to be similar to rebase's --update-refs\noption, in that it assumes all refs pointing to history being\nrewritten should also be updated), or the more explicit\n\n  git replay --onto main ^main P Q\n\nand assuming 'main' has one additional commit 'D' on top of 'C', you'd see\n\n   A---B---C---D-----------M---Q1---Q2---Q3 <-- refs/heads/Q\n               \\          /\n                P1--P2--P3 <-- refs/heads/P\n\nIn other words, both P and Q would be replayed but with relative\ntopology preserved.  Also, important changes in M (conflict fixups or\nsemantic fixes, etc.) would be preserved -- at least that's the plan.\n\n> Do you have an example rebase command that manipulates the commits the\n> way you want? Then I can better understand how the --update-refs could fit\n> in with that. (Or maybe the point of your tangent is that there isn't an\n> option.)\n\nThis would be nice.\n\n> If instead we thought about an example like re-rolling the 'next' branch\n> entirely on top of the 'master' branch, then we have an example that is\n> closer to the friendly fork example. (I know this isn't a realistic\n> scenario since we don't rewrite the commits already merged to 'next', but\n> it's an interesting stress test.)\n>\n> While having 'next' checked out, I ran\n>\n>   git rebase -i --update-refs --rebase-merges=rebase-cousins master\n\nRe-rolling 'next' might not be realistic, but re-rolling 'seen' isn't\nquite as far fetched.  I think it'd be more likely to use\nno-rebase-cousins, so:\n\n    git checkout seen && git rebase -i --update-refs\n[--rebase-merges=no-rebase-cousins] next\n\nor\n\n    git replay -i --contained --keep-base next..seen\n\n> and it updated all of the branches currently in the region master..next.\n> I've attached the output of\n>\n>   git -c log.excludeDecoration=\"refs/remotes/*\" \\\n>         log --oneline --graph --boundary master..next\n>\n> just to show what this looks like.\n\nOne example that I thought might be useful, is tweaking a single topic\nin 'seen', and updating 'seen' to reflect those updates.  This would\nbe something like\n\n    git replay --keep-base --first-parent ^next seen topic_branch\n\nWhich would show you all the commits in the first-line history of\nnext..topic_branch plus the first-line history of next..seen, and let\nyou tweak that todo list.\n"},{"id":"459213","messageId":"CABPp-BHARfYcsEM7Daeb7+vYxeB9Awo8=qbrOMXG6BQ0gX1RiA@mail.gmail.com","threadId":"57949","inReplyTo":"xmqqv8rz5ztg.fsf@gitster.g","subject":"Re: [PATCH v4 00/12] rebase: update branches in multi-part topic","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-16T21:23:06Z","receivedAt":"2022-07-16T21:23:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 14, 2022 at 11:11 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Derrick Stolee <derrickstolee@github.com> writes:\n>\n> >  Before:\n> >\n> >   A---B----C---M---Q1---Q2---Q3 <-- refs/heads/Q\n> >    \\          /\n> >     P1--P2--P3 <-- refs/heads/P\n> >\n> >  After rebasing both topics simultaneously (with 'git rebase --update-refs\n> >  C' while Q is checked out):\n> >\n> >   A---B---C---D---P1---P2---P3---Q1---Q2---Q3\n> >                           ^              ^\n> >                       refs/heads/P  refs/heads/Q\n> >\n> > But it seems what you mean to say is to update the merge commit M, which\n> > means that the 'P' branch above has been updated independently of the 'Q'\n> > branch, so we need to update 'Q' after-the-fact.\n>\n> I am not aiming to flatten P and Q into a single strand of pearls.\n> That would defeat the point of \"git log --oneline --first-parent\"\n> that can be used to view \"master..Q\", whose output would be \"at the\n> bottom the topic P lies there, and on top there are 3 patches\".\n>\n> P's may be your ds/branch-checked-out topic while Q's may be this\n> topic.  Other people may find bugs, improvements and a room for\n> unwanted churns in the former, and P may gain a few more commit,\n> in which case M thru Q3 needs to be rebuilt.\n>\n> In a manual procedure, when I realize that P will gain a few\n> more patches (or gets rewritten):\n>\n>  * find what other topics depend on P and make a mental note (i.e. Q\n>    needs to be rebuilt)\n>\n>  * perform an equivalent of \"git rebase -i --onto A P\", but without\n>    using \"git rebase\".\n>\n>    - git checkout P\n>    - git checkout --detach master...    ;# reuse the same base\n>    - git am -s                          ;# apply\n>    - git rebase -i                      ;# minor fix-up while queuing\n>    - git range-diff @{-1}...            ;# sanity check\n>    - make test                          ;# further sanity check\n>    - git checkout -B @{-1}              ;# update P to the new round\n>    - git range-diff @{1}...             ;# final sanity check\n>\n>    this rebuilds P1, P2, and P3 into a new series on A\n>\n>  * for each topic that needs rebuilding (i.e. Q), find M and rebuild\n>    it\n>\n>    - git checkout Q\n>    - git checkout --detach master...    ;# reuse the same base\n>    - git merge --into Q P               ;# recreate M with updated P\n>    - git rebase --onto HEAD M Q         ;# rebuild Q\n>    - git checkout -B @{-1}              ;# update Q to sit on top of new P\n>    - git range-diff @{1}...             ;# sanity check (should be empty)\n\nThanks for sharing all these details, and the ones below.\n\n> > I'm not sure what that rebase would look like, indepdendent of\n> > updating refs.\n>\n> I suspect that a creative use of \"git rebase --rebase-merge master\n> Q\" should allow me to get there.  Here is an outline of the todo\n> list you'd get out of \"git rebase --rebase-merge -i v2.37.0\" while\n> the topic ds/rebase-update-ref is checked out:\n>\n> ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----\n> label onto\n>\n> # Branch ds-branch-checked-out\n> reset 5ed49a75f3 # Merge branch 'os/fetch-check-not-current-branch'\n> pick 31ad6b61bd branch: add branch_checked_out() helper\n> pick d2ba271aad branch: check for bisects and rebases\n> ...\n> pick 9bef0b1e6e branch: drop unused worktrees variable\n> label ds-branch-checked-out\n>\n> reset onto\n> merge -C 7fefa1b68e ds-branch-checked-out # Merge branch 'ds/branch-checked-out' into ds/rebase-update-ref\n> pick a0bfa0ec53 t2407: test bisect and rebase as black-boxes\n> pick 43547f7a52 t2407: test branches currently using apply backend\n> ...\n> pick 8b2a776cab sequencer: notify user of --update-refs activity\n> ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----\n>\n> So it would likely involve\n>\n>  - \"git checkout Q && git rebase -i --rebase-merge master\" to get\n>    the todo list.\n>\n>  - remove all \"pick\"s for existing commits on branch P, and replace\n>    them with a \"break\"\n>\n>  - add an update-ref insn to update ds/branch-checked-out topic\n>    using the ds-branch-checked-out label at the end of the todo\n>    list.\n>\n>  - exit the editor, and in the \"break\" session, run \"am\" to accept\n>    the new round of patches for P.\n>\n>  - \"git rebase --continue\" to let the tip labeled as P and let the\n>    rest of the todo list rebuild Q\n>\n> but I am not sure what should happen when there are more than one\n> dependent topic (i.e. in addition to Q, topic R also depends on P).\n\nYeah, rebase's hard-coded HEAD assumption and including it in an\nimplicit range, makes it hard to get more general revision range\nexpressions.  If we had something that just let you specify your\nrevision range, then you could handle multiple topics, using syntax\nsomething like:\n\n   git replay --keep-base ^master P Q R\n\nand then edit the resulting todo file (where --interactive and an\nequivalent of --rebase-merges are both assumed).  You don't even\nnecessarily have to have any of P, Q, or R checked out.  In fact, if\nwe can use revision ranges, then perhaps it'd be even nicer to run\n\n   git replay --keep-base --contained --ancestry-path P..seen\n\n(where --contained says update all refs pointing to any commit\ncontained in the revision range, instead of just the explicitly listed\nrefs).  This would also remove all the picks for P for you, since it's\nthe left side of that range.  So, you'd just need to adjust the first\n\"reset\" directive to an older commit, and add a \"break\" directive\nright after it followed by an \"update-ref\" directive for P.  During\nthe \"break\", you can \"git-am\" the new patches to accept the new\nversion of P.  You wouldn't have to make a mental note of any other\nbranches (Q, R, S, etc.) that also need to be included, since they are\ncontained in seen and the --ancestry-path gets you just the right\nrange of commits, replaying them all including merges (and I've got\nplans for improving the replaying of merges too).\n\nHowever...\n\nWhat if someone wants to edit the patches in topic P too, rather than\njust eject and replace them?  There's no <revision range> I know of to\nget that if the base of P is some very common commit, because\n\"--ancestry-path master..seen\" will suddenly balloon the number of\ntopics involved rather dramatically.  I guess you could just take that\ntodo list and edit it (since most commits in the list would just be\nreplayed on the same base and end up fast-forwarding and thus not be\nchanged; only commits the user edited and their descendants would\nchange), but that todo list could be unworkably long  to meddle with.\nI'm curious if anyone has ideas for that.\n\n> It also is unclear in the above procedure with \"rebase-merge\" what\n> to feed to \"range-diff\" in the sanity-checking step.  I could type\n> \"git range-diff P...\" but being able to use @{-1} is a lot handier.\n\nI think you could use \"git range-diff seen@{1}...seen\" after the\noperation I outlined above.\n\nGranted, that only helps sanity check the non-merge commits, since\nrange-diff currently ignores merges, but perhaps if we teach\nrange-diff to make use of --diff-merges=remerge then it could?\n"},{"id":"459216","messageId":"CABPp-BE++xWxWg5bJy9xj1i2QvFyGf+mKQvObdujNoPFU6M-Wg@mail.gmail.com","threadId":"57949","inReplyTo":"d5cd4b49e46bc2c186c6e89333360a975700c99a.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 12/12] sequencer: notify user of --update-refs activity","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-16T22:09:09Z","receivedAt":"2022-07-16T22:09:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 12, 2022 at 6:07 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <derrickstolee@github.com>\n>\n> When the user runs 'git rebase -i --update-refs', the end message still\n> says only\n>\n>   Successfully rebased and updated <HEAD-ref>.\n>\n> Update the sequencer to collect the successful (and unsuccessful) ref\n> updates due to the --update-refs option, so the end message now says\n>\n>   Successfully rebased and updated <HEAD-ref>.\n>   Updated the following refs with --update-refs:\n>         refs/heads/first\n>         refs/heads/third\n\nThis seems good.\n\n>   Failed to update the following refs with --update-refs:\n>         refs/heads/second\n\nThis is good, but I think it could be improved.  Could we also include\nthe commit to which rebase would have updated the branch to?  That\nwould allow the user to manually update it if they want, or at least\nsee a range-diff between what we would have updated it to and what it\nnow has.  Without that information, the user might have difficulty\ncorrecting that branch.\n\n\nAnyway, I've only been able to find a few minor things in reviewing\nthis series.  It looks really good overall, and I'm sure lots of\npeople will be making use of it!\n"},{"id":"459247","messageId":"20220718090557.GA5616@szeder.dev","threadId":"57949","inReplyTo":"3ec2cc922f971af4e4a558188cf139cc0c0150d6.1657631226.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2022-07-18T09:05:57Z","receivedAt":"2022-07-18T09:06:03Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Jul 12, 2022 at 01:07:00PM +0000, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> When working on a large feature, it can be helpful to break that feature\n> into multiple smaller parts that become reviewed in sequence. During\n> development or during review, a change to one part of the feature could\n> affect multiple of these parts. An interactive rebase can help adjust\n> the multi-part \"story\" of the branch.\n> \n> However, if there are branches tracking the different parts of the\n> feature, then rebasing the entire list of commits can create commits not\n> reachable from those \"sub branches\". It can take a manual step to update\n> those branches.\n> \n> Add a new --update-refs option to 'git rebase -i' that adds 'update-ref\n> <ref>' steps to the todo file whenever a commit that is being rebased is\n> decorated with that <ref>. At the very end, the rebase process updates\n> all of the listed refs to the values stored during the rebase operation.\n> \n> Be sure to iterate after any squashing or fixups are placed. Update the\n> branch only after those squashes and fixups are complete. This allows a\n> --fixup commit at the tip of the feature to apply correctly to the sub\n> branch, even if it is fixing up the most-recent commit in that part.\n> \n> One potential problem here is that refs decorating commits that are\n> already marked as \"fixup!\" or \"squash!\" will not be included in this\n> list. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\n> to change the relative order of these refs, so it is not recommended.\n> The workflow here is intended to allow these kinds of commits at the tip\n> of the rebased branch while the other sub branches come along for the\n> ride without intervention.\n> \n> This change update the documentation and builtin to accept the\n> --update-refs option as well as updating the todo file with the\n> 'update-ref' commands. Tests are added to ensure that these todo\n> commands are added in the correct locations.\n> \n> This change does _not_ include the actual behavior of tracking the\n> updated refs and writing the new ref values at the end of the rebase\n> process. That is deferred to a later change.\n> \n> Signed-off-by: Derrick Stolee <derrickstolee@github.com>\n> ---\n>  Documentation/git-rebase.txt  |   8 +++\n>  builtin/rebase.c              |   5 ++\n>  sequencer.c                   | 107 ++++++++++++++++++++++++++++++++++\n>  sequencer.h                   |   1 +\n>  t/t2407-worktree-heads.sh     |  22 +++++++\n>  t/t3404-rebase-interactive.sh |  70 ++++++++++++++++++++++\n>  6 files changed, 213 insertions(+)\n> \n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 262fb01aec0..e7611b4089c 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -609,6 +609,13 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n>  start would be overridden by the presence of\n>  `rebase.rescheduleFailedExec=true` configuration.\n>  \n> +--update-refs::\n\nSo the option is called '--update-refs', but ...\n\n> +--no-update-refs::\n> +\tAutomatically force-update any branches that point to commits that\n\n... its description talks about \"branches\".\n\n> +\tare being rebased. Any branches that are checked out in a worktree\n> +\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n> +\tin this way.\n> +\n>  INCOMPATIBLE OPTIONS\n>  --------------------\n>  \n\n> @@ -1124,6 +1126,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n>  \t\t\t N_(\"move commits that begin with \"\n>  \t\t\t    \"squash!/fixup! under -i\")),\n> +\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n> +\t\t\t N_(\"update local refs that point to commits \"\n\nAnd its short help talks about \"local refs\".\n\nI think at least the documentation and short help should use\nconsistent terminology.\n\n> +\t\t\t    \"that are being rebased\")),\n>  \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n>  \t\t\tN_(\"GPG-sign commits\"),\n>  \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n"},{"id":"459258","messageId":"9f295edc-a821-ab46-3a93-e4b6d72a816a@gmail.com","threadId":"57949","inReplyTo":"638f6b51-0045-f08e-fa63-d16a571d0099@github.com","subject":"Re: [PATCH v4 09/12] sequencer: rewrite update-refs as user edits todo list","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-18T13:09:07Z","receivedAt":"2022-07-18T13:09:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 15/07/2022 14:13, Derrick Stolee wrote:\n> On 7/15/2022 6:27 AM, Phillip Wood wrote:\n> [...]\n> I think both of these concerns would be excellent for a follow-up,\n> since they would shave off some rough edges. I hesitate to add them\n> to this series since it has been growing quite a bit already.\n\nYes that makes sense (and applies to pretty much all of my other \nsuggestions as well). This series is looking good and it makes sense to \nget it merged and hopefully get some user feedback before making any \ntweaks. I'm going to off the list most of the next three weeks, so I \nprobably wont reply promptly to any reroll. I think that what you've got \nin this version is pretty much ready so I'd be very happy to see this \ntopic merged by the time I get back on the list.\n\nBest Wishes\n\nPhillip\n\n> Thanks,\n> -Stolee\n"},{"id":"459298","messageId":"11aa9b43-adea-4e90-9c70-ab3579aa445f@github.com","threadId":"57949","inReplyTo":"20220718090557.GA5616@szeder.dev","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-18T16:55:54Z","receivedAt":"2022-07-18T16:56:05Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/18/2022 5:05 AM, SZEDER Gábor wrote:\n> On Tue, Jul 12, 2022 at 01:07:00PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> From: Derrick Stolee <derrickstolee@github.com>\n\n>> +--update-refs::\n> \n> So the option is called '--update-refs', but ...\n> \n>> +--no-update-refs::\n>> +\tAutomatically force-update any branches that point to commits that\n> \n> ... its description talks about \"branches\".\n\n>> +\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n>> +\t\t\t N_(\"update local refs that point to commits \"\n> \n> And its short help talks about \"local refs\".\n> \n> I think at least the documentation and short help should use\n> consistent terminology.\n\nThanks for catching this. I think I should use \"branches\" here, but\nkeep the name \"--update-refs\". The biggest reason is that it provides\na nice parallel with the \"update-ref\" sequencer command. This command\nallows updating _any_ ref, such as lightweight tags in refs/tags/*\nor even refs in refs/my/namespace/*.\n\nThe --update-refs option doesn't create the commands to update tags\nor refs in places other than refs/heads/*.\n\nThanks,\n-Stolee\n"},{"id":"459317","messageId":"xmqq4jzefc3e.fsf@gitster.g","threadId":"57949","inReplyTo":"11aa9b43-adea-4e90-9c70-ab3579aa445f@github.com","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-18T19:35:33Z","receivedAt":"2022-07-18T19:35:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n> ... I think I should use \"branches\" here, but\n> keep the name \"--update-refs\". The biggest reason is that it provides\n> a nice parallel with the \"update-ref\" sequencer command. This command\n> allows updating _any_ ref, such as lightweight tags in refs/tags/*\n> or even refs in refs/my/namespace/*.\n>\n> The --update-refs option doesn't create the commands to update tags\n> or refs in places other than refs/heads/*.\n\nI guess it would make the choice of \"branch\" the most appropriate.\n\nI was hoping that we can repoint refs in private namespaces that are\nnot branches with the option.  But as long as the underlying\n\"update-ref\" instruction can be used by advanced users, that is OK.\n"},{"id":"459376","messageId":"01247261-dbf0-f834-965b-a4c02c4ef256@github.com","threadId":"57949","inReplyTo":"CABPp-BENsp3uc7J_xS0gr_yjWQ25NVuJbdHzCMEcaaGYvLEiKA@mail.gmail.com","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-19T15:50:39Z","receivedAt":"2022-07-19T15:51:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/16/2022 3:30 PM, Elijah Newren wrote:\n> On Tue, Jul 12, 2022 at 6:07 AM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Derrick Stolee <derrickstolee@github.com>\n>>\n> [...]\n>>\n>> +--update-refs::\n>> +--no-update-refs::\n>> +       Automatically force-update any branches that point to commits that\n>> +       are being rebased. Any branches that are checked out in a worktree\n>> +       or point to a `squash! ...` or `fixup! ...` commit are not updated\n>> +       in this way.\n> \n> I think the second sentence here should be split.  In particular, I\n> don't think I understand the second half of the second sentence.  Do\n> you intend to say here that branches pointing to a `squash!` or\n> `fixup!` will instead update the first `pick` in the ancestry of such\n> a commit, rather than that such branches are entirely excluded from\n> any updates?  That's what I observed in my testing of your v3, at\n> least, and that's the behavior I'd expect this feature to implement,\n> but this documentation doesn't match.\n\nGood find. You're right that these don't match, and its in fact that\nI expected it to work this way, but it doesn't.\n\nI've added a branch to my tests that points to a fixup! that is not\nthe tip commit and use it to demonstrate that it is not reordered, but\n_does_ appear in the 'update-ref <ref>' list.\n\nI'll update the documentation to match this behavior, too. This case\nis unlikely to happen much in practice, but I now believe it's better\nto include the ref than to ignore it.\n \n> [...]\n>> @@ -5660,6 +5764,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>>                 item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n>>         }\n>>\n>> +       if (update_refs && todo_list_add_update_ref_commands(todo_list))\n>> +               return -1;\n>> +\n> \n> As a tangent, I find it interesting that you add the update-ref\n> commands as a post-processing step rather than as a part of\n> sequencer_make_script().  I don't think you need to change anything,\n> but I am curious due to my git-replay work if you find it advantageous\n> to do it this way.\n\nI found it to be simple enough to do a scan of the steps directly\ninstead of injecting extra logic into the _make_script() method.\nThe simplest reason is that we would need to inject \"update_refs\"\ninto that method.\n\nThanks,\n-Stolee\n"},{"id":"459377","messageId":"6b6746cb-2456-0733-32ed-c0a7a3f57087@github.com","threadId":"57949","inReplyTo":"xmqq4jzefc3e.fsf@gitster.g","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-19T15:53:01Z","receivedAt":"2022-07-19T15:53:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/18/2022 3:35 PM, Junio C Hamano wrote:\n> Derrick Stolee <derrickstolee@github.com> writes:\n> \n>> ... I think I should use \"branches\" here, but\n>> keep the name \"--update-refs\". The biggest reason is that it provides\n>> a nice parallel with the \"update-ref\" sequencer command. This command\n>> allows updating _any_ ref, such as lightweight tags in refs/tags/*\n>> or even refs in refs/my/namespace/*.\n>>\n>> The --update-refs option doesn't create the commands to update tags\n>> or refs in places other than refs/heads/*.\n> \n> I guess it would make the choice of \"branch\" the most appropriate.\n> \n> I was hoping that we can repoint refs in private namespaces that are\n> not branches with the option.  But as long as the underlying\n> \"update-ref\" instruction can be used by advanced users, that is OK.\n\nI would like to keep the --update-refs name for a couple reasons:\n\n1. 'update-ref' is the right name for the sequencer command. Having\n   a parallel there is helpful for learning about the option.\n\n2. We could extend the boolean '--update-refs' option into a more\n   advanced multi-valued '--update-refs=<refspec>' option to allow\n   advanced users to specify a ref namespace that they want included.\n\nThanks,\n-Stolee\n"},{"id":"459379","messageId":"91c9b9d1-ae9d-658a-fca6-8bb2e75607e3@github.com","threadId":"57949","inReplyTo":"e40df260-6ca7-10f8-c4bf-82c42585dd12@gmail.com","subject":"Re: [PATCH v4 08/12] rebase: update refs from 'update-ref' commands","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-19T16:04:05Z","receivedAt":"2022-07-19T16:04:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/15/2022 9:25 AM, Phillip Wood wrote:\n>> Now that the 'git rebase --update-refs' command is implemented to write\n>> to the update-refs file, we can remove the fake construction of the\n>> update-refs file from a test in t2407-worktree-heads.sh.\n> \n> This is looking good. I've left a few comments, mostly about error propagation. It's nice to see us recording the initial value of the ref when the todo list is created. It's also good to see this using a lock file. We could perhaps lock the file (with a timeout) when we read it in sequencer_get_update_refs_state() to avoid a race where a process is checking out a new branch in one worktree and another is preparing to rebase that branch in another worktree.\n\n>> +static int do_update_ref(struct repository *r, const char *refname)\n>> +{\n>> +    struct string_list_item *item;\n>> +    struct string_list list = STRING_LIST_INIT_DUP;\n>> +\n>> +    sequencer_get_update_refs_state(r->gitdir, &list);\n> \n> We're ignoring any errors here and always returning 0 from this function.\n\nThanks. Will fix.\n \n>> +\n>> +    for_each_string_list_item(item, &list) {\n>> +        if (!strcmp(item->string, refname)) {\n>> +            struct update_ref_record *rec = item->util;\n>> +            read_ref(\"HEAD\", &rec->after);\n\nThis is the other place where we could have a failure.\n\n>>   +static int do_update_refs(struct repository *r)\n>> +{\n>> +    int res = 0;\n>> +    struct string_list_item *item;\n>> +    struct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n>> +    struct ref_store *refs = get_main_ref_store(r);\n>> +\n>> +    sequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n\nWe need to check for failure here, too.\n\n>> +    for_each_string_list_item(item, &refs_to_oids) {\n>> +        struct update_ref_record *rec = item->util;\n>> +\n>> +        if (oideq(&rec->after, the_hash_algo->null_oid)) {\n>> +            /*\n>> +             * Ref was not updated. User may have deleted the\n>> +             * 'update-ref' step.\n>> +             */\n> \n> Unless we want to support users editing the todo list without using \"git rebase --edit-todo\" then by the end of the series it is a bug if we leave an entry in the update-refs file that has been removed from the todo list so I wander if we should remove this if().\n\nI think this is leftover from the previous version and will\nnever happen. If rec->after is null, then it would be removed\nearlier when parsing the todo list.\n\n>>   +    do_update_refs(r);\n> \n> Should this be inside the \"if (is_rebase_i(opts))\" that is closed just above it? We're also ignoring the return value.\n\nCouldn't hurt. Should be a no-op if not in interactive mode.\n\n>>   +    write_update_refs_state(&ctx.refs_to_oids);\n> \n> We're ignoring the return value. Also I think todo_list_add_update_ref_commands() only ever returns 0.\n\nThanks for your attention to detail here.\n\n-Stolee\n"},{"id":"459380","messageId":"eec844fd-53be-960f-c806-5b97e8705229@github.com","threadId":"57949","inReplyTo":"CABPp-BE++xWxWg5bJy9xj1i2QvFyGf+mKQvObdujNoPFU6M-Wg@mail.gmail.com","subject":"Re: [PATCH v4 12/12] sequencer: notify user of --update-refs activity","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-19T16:09:31Z","receivedAt":"2022-07-19T16:09:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/16/2022 6:09 PM, Elijah Newren wrote:\n> On Tue, Jul 12, 2022 at 6:07 AM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Derrick Stolee <derrickstolee@github.com>\n>>\n>> When the user runs 'git rebase -i --update-refs', the end message still\n>> says only\n>>\n>>   Successfully rebased and updated <HEAD-ref>.\n>>\n>> Update the sequencer to collect the successful (and unsuccessful) ref\n>> updates due to the --update-refs option, so the end message now says\n>>\n>>   Successfully rebased and updated <HEAD-ref>.\n>>   Updated the following refs with --update-refs:\n>>         refs/heads/first\n>>         refs/heads/third\n> \n> This seems good.\n> \n>>   Failed to update the following refs with --update-refs:\n>>         refs/heads/second\n> \n> This is good, but I think it could be improved.  Could we also include\n> the commit to which rebase would have updated the branch to?  That\n> would allow the user to manually update it if they want, or at least\n> see a range-diff between what we would have updated it to and what it\n> now has.  Without that information, the user might have difficulty\n> correcting that branch.\n\nWould you mind if I left this as something for #leftoverbits? I\nexpect that a follow-up series will be necessary once we have more\nuser feedback. This isn't the only item delayed until after more\nfeedback.\n\nThanks,\n-Stolee\n"},{"id":"459384","messageId":"106a3a5c-ab47-69b2-1432-43b12baf20e6@github.com","threadId":"57949","inReplyTo":"xmqqo7xlaw7z.fsf@gitster.g","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-19T16:47:35Z","receivedAt":"2022-07-19T16:47:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/19/2022 12:44 PM, Junio C Hamano wrote:\n> Derrick Stolee <derrickstolee@github.com> writes:\n> \n>> On 7/18/2022 3:35 PM, Junio C Hamano wrote:\n>>> Derrick Stolee <derrickstolee@github.com> writes:\n>>>\n>>>> ... I think I should use \"branches\" here, but\n>>>> keep the name \"--update-refs\". The biggest reason is that it provides\n>>>> a nice parallel with the \"update-ref\" sequencer command. This command\n>>>> allows updating _any_ ref, such as lightweight tags in refs/tags/*\n>>>> or even refs in refs/my/namespace/*.\n>>>>\n>>>> The --update-refs option doesn't create the commands to update tags\n>>>> or refs in places other than refs/heads/*.\n>>>\n>>> I guess it would make the choice of \"branch\" the most appropriate.\n>>>\n>>> I was hoping that we can repoint refs in private namespaces that are\n>>> not branches with the option.  But as long as the underlying\n>>> \"update-ref\" instruction can be used by advanced users, that is OK.\n>>\n>> I would like to keep the --update-refs name for a couple reasons:\n> \n> I do not think anybody proposed to change the name of that option.\n> \n> I was reacting to your \"I should use branches here\", with the\n> understanding that \"here\" is this place where you used \"local refs\".\n> \n>>> +\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n>>> +\t\t\t N_(\"update local refs that point to commits \"\n> \n> If \"rebase --update-refs\" uses \"update-ref\" insn (which is capable\n> of repointing non-branch refs) only for local branches, then the\n> help text for the \"--update-refs\" option can safely say \"update\n> local branches\" without being inaccurate.  That is where my \"branch\n> is the most appropriate\" comes from.\n\nThanks for the clarification. Sorry for misunderstanding.\n\nThanks,\n-Stolee\n"},{"id":"459385","messageId":"xmqqo7xlaw7z.fsf@gitster.g","threadId":"57949","inReplyTo":"6b6746cb-2456-0733-32ed-c0a7a3f57087@github.com","subject":"Re: [PATCH v4 07/12] rebase: add --update-refs option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-19T16:44:16Z","receivedAt":"2022-07-19T16:47:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <derrickstolee@github.com> writes:\n\n> On 7/18/2022 3:35 PM, Junio C Hamano wrote:\n>> Derrick Stolee <derrickstolee@github.com> writes:\n>> \n>>> ... I think I should use \"branches\" here, but\n>>> keep the name \"--update-refs\". The biggest reason is that it provides\n>>> a nice parallel with the \"update-ref\" sequencer command. This command\n>>> allows updating _any_ ref, such as lightweight tags in refs/tags/*\n>>> or even refs in refs/my/namespace/*.\n>>>\n>>> The --update-refs option doesn't create the commands to update tags\n>>> or refs in places other than refs/heads/*.\n>> \n>> I guess it would make the choice of \"branch\" the most appropriate.\n>> \n>> I was hoping that we can repoint refs in private namespaces that are\n>> not branches with the option.  But as long as the underlying\n>> \"update-ref\" instruction can be used by advanced users, that is OK.\n>\n> I would like to keep the --update-refs name for a couple reasons:\n\nI do not think anybody proposed to change the name of that option.\n\nI was reacting to your \"I should use branches here\", with the\nunderstanding that \"here\" is this place where you used \"local refs\".\n\n>> +\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n>> +\t\t\t N_(\"update local refs that point to commits \"\n\nIf \"rebase --update-refs\" uses \"update-ref\" insn (which is capable\nof repointing non-branch refs) only for local branches, then the\nhelp text for the \"--update-refs\" option can safely say \"update\nlocal branches\" without being inaccurate.  That is where my \"branch\nis the most appropriate\" comes from.\n\n\n"},{"id":"459412","messageId":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v4.git.1657631225.gitgitgadget@gmail.com","subject":"[PATCH v5 00/12] rebase: update branches in multi-part topic","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:32Z","receivedAt":"2022-07-19T18:33:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This series is based on ds/branch-checked-out.\n\nThis is a feature I've wanted for quite a while. When working on the sparse\nindex topic, I created a long RFC that actually broke into three topics for\nfull review upstream. These topics were sequential, so any feedback on an\nearlier one required updates to the later ones. I would work on the full\nfeature and use interactive rebase to update the full list of commits.\nHowever, I would need to update the branches pointing to those sub-topics.\n\nThis series adds a new --update-refs option to 'git rebase' (along with a\nrebase.updateRefs config option) that adds 'update-ref' commands into the\nTODO list. This is powered by the commit decoration machinery.\n\nAs an example, here is my in-progress bundle URI RFC split into subtopics as\nthey appear during the TODO list of a git rebase -i --update-refs:\n\npick 2d966282ff3 docs: document bundle URI standard\npick 31396e9171a remote-curl: add 'get' capability\npick 54c6ab70f67 bundle-uri: create basic file-copy logic\npick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\npick 6adaf842684 fetch: add --bundle-uri option\npick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\nupdate-ref refs/heads/bundle-redo/fetch\n\npick 1e3f6546632 clone: add --bundle-uri option\npick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\nupdate-ref refs/heads/bundle-redo/clone\n\npick 5451cb6599c bundle-uri: create bundle_list struct and helpers\npick 3029c3aca15 bundle-uri: create base key-value pair parsing\npick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\npick 92625a47673 bundle-uri: unit test \"key=value\" parsing\npick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\npick 9d6809a8d53 bundle-uri: parse bundle list in config format\npick 287a732b54c bundle-uri: fetch a list of bundles\nupdate-ref refs/heads/bundle-redo/list\n\npick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\npick 520204dcd1c bundle-uri client: add minimal NOOP client\npick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\npick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\npick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\npick caf4599a81d bundle-uri: allow relative URLs in bundle lists\npick df255000b7e bundle-uri: download bundles from an advertised list\npick d71beabf199 clone: unbundle the advertised bundles\npick c9578391976 t5601: basic bundle URI tests\n# Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n\nupdate-ref refs/heads/bundle-redo/advertise\n\n\nHere is an outline of the series:\n\n * Patch 1 updates some tests for branch_checked_out() to use 'git bisect'\n   and 'git rebase' as black-boxes instead of manually editing files inside\n   $GIT_DIR. (Thanks, Junio!)\n * Patch 2 updates some tests for branch_checked_out() for the 'apply'\n   backend.\n * Patch 3 updates branch_checked_out() to parse the\n   rebase-merge/update-refs file to block concurrent ref updates and\n   checkouts on branches selected by --update-refs.\n * Patch 4 updates the todo list documentation to remove some unnecessary\n   dots in the 'merge' command. This makes it consistent with the 'fixup'\n   command before we document the 'update-ref' command.\n * Patch 5 updates the definition of todo_command_info to use enum values as\n   array indices.\n * Patches 6-8 implement the --update-refs logic itself.\n * Patch 9 specifically updates the update-refs file every time the user\n   edits the todo-list (Thanks Phillip!)\n * Patch 10 adds the rebase.updateRefs config option similar to\n   rebase.autoSquash.\n * Patch 11 ignores the HEAD ref when creating the todo list instead of\n   making a comment (Thanks Elijah!)\n * Patch 12 adds messaging to the end of the rebase stating which refs were\n   updated (Thanks Elijah!)\n\nDuring review, we have identified some areas that would be good for\n#leftoverbits:\n\n * Warn the user when they add an 'update-ref ' command but is checked out\n   in another worktree.\n * The checks in patch 9 are quadratic. They could be sped up using\n   hashtables.\n * Consider whether we should include an 'update-ref ' command for the HEAD\n   ref, so that all refs are updated in the same way. This might help\n   confused users.\n * The error message for failed ref updates could include information on the\n   commit IDs that would have been used. This can help the user fix the\n   situation by updating the refs manually.\n * Modify the --update-refs option from a boolean to an\n   optionally-string-parameter that specifies refspecs for the 'update-ref'\n   commands.\n\n\nUpdates in v5\n=============\n\n * Rename 'wt_dir' to 'wt_git_dir' for clarity.\n * The documented behavior around 'fixup!' and 'squash!' commits was\n   incorrect, so update the commit message, documentation, and test to\n   demonstrate the actual behavior.\n * Use CALLOC_ARRAY() to be more idiomatic.\n * Be much more careful about propagating errors.\n * Commit message typo: \"We an\" to \"We can\"\n * Remove unnecessary null OID check when writing refs, since those would\n   already be removed by a previous step.\n\n\nUpdates in v4\n=============\n\nThis version took longer than I'd hoped (I had less time to work on it than\nanticipated) but it also has some major updates. These major updates are\ndirect responses to the significant review this series has received. Thank\nyou!\n\n * The update-refs file now stores \"ref/before/after\" triples (still\n   separated by lines). This allows us to store the \"before\" OID of a ref in\n   addition to the \"after\" that we will write to that ref at the end of the\n   rebase. This allows us to do a \"force-with-lease\" update. The\n   branch_checked_out() updates should prevent Git from updating those refs\n   while under the rebase, but older versions and third-party tools don't\n   have that protection.\n * The update-refs file is updated with every update to the todo-list file.\n   This allows for some advanced changes to the file, including removing,\n   adding, and duplicating 'update-ref' commands.\n * The message at the end of the rebase process now lists which refs were\n   updated with the update-ref steps. This includes any ref updates that\n   fail.\n * The branch_checked_out() tests now use 'git bisect' and 'git rebase' as\n   black-boxes instead of testing their internals directly.\n\nHere are the more minor updates:\n\n * Dropped an unnecessary stat() call.\n * Updated commit messages to include extra details, based on confusion in\n   last round.\n * The HEAD branch no longer appears as a comment line in the initial todo\n   list.\n * The update-refs file is now written using a lockfile.\n * Tests now use test_cmp_rev.\n * A memory leak ('path' variable) is resolved.\n\n\nUpdates in v3\n=============\n\n * The branch_checked_out() API was extracted to its own topic and is now\n   the ds/branch-checked-out branch. This series is now based on that one.\n * The for_each_decoration() API was removed, since it became trivial once\n   it did not take a commit directly.\n * The branch_checked_out() tests did not verify the rebase-apply data (for\n   the apply backend), so that is fixed.\n * Instead of using the 'label' command and a final 'update-refs' command in\n   the todo list, use a new 'update-ref ' command. This command updates the\n   rebase-merge/update-refs file with the OID of HEAD at these steps. At the\n   very end of the rebase sequence, those refs are updated to the stored OID\n   values (assuming that they were not removed by the user, in which case we\n   notice that the OID is the null OID and we do nothing).\n * New tests are added.\n * The todo-list comment documentation has some new formatting updates, but\n   also includes a description of 'update-refs' in this version.\n\n\nUpdates in v2\n=============\n\nAs recommended by the excellent feedback, I have removed the 'exec' commands\nin favor of the 'label' commands and a new 'update-refs' command at the very\nend. This way, there is only one step that updates all of the refs at the\nend instead of updating refs during the rebase. If a user runs 'git rebase\n--abort' in the middle, then their refs are still where they need to be.\n\nBased on some of the discussion, it seemed like one way to do this would be\nto have an 'update-ref ' command that would take the place of these 'label'\ncommands. However, this would require two things that make it a bit awkward:\n\n 1. We would need to replicate the storage of those positions during the\n    rebase. 'label' already does this pretty well. I've added the\n    \"for-update-refs/\" label to help here.\n 2. If we want to close out all of the refs as the rebase is finishing, then\n    that \"step\" becomes invisible to the user (and a bit more complicated to\n    insert). Thus, the 'update-refs' step performs this action. If the user\n    wants to do things after that step, then they can do so by editing the\n    TODO list.\n\nOther updates:\n\n * The 'keep_decorations' parameter was renamed to 'update_refs'.\n * I added tests for --rebase-merges=rebase-cousins to show how these labels\n   interact with other labels and merge commands.\n * I changed the order of the insertion of these update-refs labels to be\n   before the fixups are rearranged. This fixes a bug where the tip commit\n   is a fixup! so its decorations are never inspected (and they would be in\n   the wrong place even if they were). The fixup! commands are properly\n   inserted between a pick and its following label command. Tests\n   demonstrate this is correct.\n * Numerous style choices are updated based on feedback.\n\nThank you for all of the detailed review and ideas in this space. I\nappreciate any more ideas that can make this feature as effective as it can\nbe.\n\nThanks, -Stolee\n\nDerrick Stolee (12):\n  t2407: test bisect and rebase as black-boxes\n  t2407: test branches currently using apply backend\n  branch: consider refs under 'update-refs'\n  rebase-interactive: update 'merge' description\n  sequencer: define array with enum values\n  sequencer: add update-ref command\n  rebase: add --update-refs option\n  rebase: update refs from 'update-ref' commands\n  sequencer: rewrite update-refs as user edits todo list\n  rebase: add rebase.updateRefs config option\n  sequencer: ignore HEAD ref under --update-refs\n  sequencer: notify user of --update-refs activity\n\n Documentation/config/rebase.txt |   3 +\n Documentation/git-rebase.txt    |  10 +\n branch.c                        |  13 +\n builtin/rebase.c                |  10 +\n rebase-interactive.c            |  15 +-\n sequencer.c                     | 474 +++++++++++++++++++++++++++++++-\n sequencer.h                     |  23 ++\n t/lib-rebase.sh                 |  15 +\n t/t2407-worktree-heads.sh       | 103 +++++--\n t/t3404-rebase-interactive.sh   | 273 ++++++++++++++++++\n 10 files changed, 895 insertions(+), 44 deletions(-)\n\n\nbase-commit: 9bef0b1e6ec371e786c2fba3edcc06ad040a536c\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1247\n\nRange-diff vs v4:\n\n  1:  9e53a27017a =  1:  9e53a27017a t2407: test bisect and rebase as black-boxes\n  2:  540a3be256f =  2:  540a3be256f t2407: test branches currently using apply backend\n  3:  bf301a054e3 !  3:  1089a0edb73 branch: consider refs under 'update-refs'\n     @@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-\n      + * rebase_path_update_refs() returns the path to this file for a given\n      + * worktree directory. For the current worktree, pass the_repository->gitdir.\n      + */\n     -+static char *rebase_path_update_refs(const char *wt_dir)\n     ++static char *rebase_path_update_refs(const char *wt_git_dir)\n      +{\n     -+\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n     ++\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_git_dir);\n      +}\n      +\n       /*\n  4:  dec95681d2b =  4:  d1cce4f06aa rebase-interactive: update 'merge' description\n  5:  b2c09600918 =  5:  4c086d477f0 sequencer: define array with enum values\n  6:  fa7ecb718cf =  6:  7b3d6601960 sequencer: add update-ref command\n  7:  3ec2cc922f9 !  7:  7efb55e4f14 rebase: add --update-refs option\n     @@ Commit message\n          --fixup commit at the tip of the feature to apply correctly to the sub\n          branch, even if it is fixing up the most-recent commit in that part.\n      \n     -    One potential problem here is that refs decorating commits that are\n     -    already marked as \"fixup!\" or \"squash!\" will not be included in this\n     -    list. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\n     -    to change the relative order of these refs, so it is not recommended.\n     -    The workflow here is intended to allow these kinds of commits at the tip\n     -    of the rebased branch while the other sub branches come along for the\n     -    ride without intervention.\n     -\n          This change update the documentation and builtin to accept the\n          --update-refs option as well as updating the todo file with the\n          'update-ref' commands. Tests are added to ensure that these todo\n     @@ Documentation/git-rebase.txt: provided. Otherwise an explicit `--no-reschedule-f\n      +--no-update-refs::\n      +\tAutomatically force-update any branches that point to commits that\n      +\tare being rebased. Any branches that are checked out in a worktree\n     -+\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n     -+\tin this way.\n     ++\tare not updated in this way.\n      +\n       INCOMPATIBLE OPTIONS\n       --------------------\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n       \t\t\t N_(\"move commits that begin with \"\n       \t\t\t    \"squash!/fixup! under -i\")),\n      +\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n     -+\t\t\t N_(\"update local refs that point to commits \"\n     ++\t\t\t N_(\"update branches that point to commits \"\n      +\t\t\t    \"that are being rebased\")),\n       \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n       \t\t\tN_(\"GPG-sign commits\"),\n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correct\n      +\tgit branch -f second HEAD~3 &&\n      +\tgit branch -f third HEAD~1 &&\n      +\tgit commit --allow-empty --fixup=third &&\n     ++\tgit branch -f is-not-reordered &&\n     ++\tgit commit --allow-empty --fixup=HEAD~4 &&\n      +\tgit branch -f shared-tip &&\n      +\t(\n      +\t\tset_cat_todo_editor &&\n      +\n      +\t\tcat >expect <<-EOF &&\n      +\t\tpick $(git log -1 --format=%h J) J\n     ++\t\tfixup $(git log -1 --format=%h update-refs) fixup! J # empty\n      +\t\tupdate-ref refs/heads/second\n      +\t\tupdate-ref refs/heads/first\n      +\t\tpick $(git log -1 --format=%h K) K\n      +\t\tpick $(git log -1 --format=%h L) L\n     -+\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n     ++\t\tfixup $(git log -1 --format=%h is-not-reordered) fixup! L # empty\n      +\t\tupdate-ref refs/heads/third\n      +\t\tpick $(git log -1 --format=%h M) M\n      +\t\tupdate-ref refs/heads/no-conflict-branch\n     ++\t\tupdate-ref refs/heads/is-not-reordered\n      +\t\tupdate-ref refs/heads/shared-tip\n      +\t\tEOF\n      +\n  8:  fb5f64c5201 !  8:  e7a91bdffbd rebase: update refs from 'update-ref' commands\n     @@ sequencer.c: struct update_ref_record {\n       \n      +static struct update_ref_record *init_update_ref_record(const char *ref)\n      +{\n     -+\tstruct update_ref_record *rec = xmalloc(sizeof(*rec));\n     ++\tstruct update_ref_record *rec;\n     ++\n     ++\tCALLOC_ARRAY(rec, 1);\n      +\n      +\toidcpy(&rec->before, null_oid());\n      +\toidcpy(&rec->after, null_oid());\n     @@ sequencer.c: leave_merge:\n       \n      -static int do_update_ref(struct repository *r, const char *ref_name)\n      +static int write_update_refs_state(struct string_list *refs_to_oids)\n     - {\n     ++{\n      +\tint result = 0;\n      +\tstruct lock_file lock = LOCK_INIT;\n      +\tFILE *fp = NULL;\n     @@ sequencer.c: leave_merge:\n      +}\n      +\n      +static int do_update_ref(struct repository *r, const char *refname)\n     -+{\n     + {\n      +\tstruct string_list_item *item;\n      +\tstruct string_list list = STRING_LIST_INIT_DUP;\n      +\n     -+\tsequencer_get_update_refs_state(r->gitdir, &list);\n     ++\tif (sequencer_get_update_refs_state(r->gitdir, &list))\n     ++\t\treturn -1;\n      +\n      +\tfor_each_string_list_item(item, &list) {\n      +\t\tif (!strcmp(item->string, refname)) {\n      +\t\t\tstruct update_ref_record *rec = item->util;\n     -+\t\t\tread_ref(\"HEAD\", &rec->after);\n     ++\t\t\tif (read_ref(\"HEAD\", &rec->after))\n     ++\t\t\t\treturn -1;\n      +\t\t\tbreak;\n      +\t\t}\n      +\t}\n     @@ sequencer.c: leave_merge:\n      +\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n      +\tstruct ref_store *refs = get_main_ref_store(r);\n      +\n     -+\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n     ++\tif ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))\n     ++\t\treturn res;\n      +\n      +\tfor_each_string_list_item(item, &refs_to_oids) {\n      +\t\tstruct update_ref_record *rec = item->util;\n      +\n     -+\t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n     -+\t\t\t/*\n     -+\t\t\t * Ref was not updated. User may have deleted the\n     -+\t\t\t * 'update-ref' step.\n     -+\t\t\t */\n     -+\t\t\tcontinue;\n     -+\t\t}\n     -+\n      +\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n      +\t\t\t\t       item->string,\n      +\t\t\t\t       &rec->after, &rec->before,\n     @@ sequencer.c: leave_merge:\n       {\n       \tint i = todo_list->current;\n      @@ sequencer.c: cleanup_head_ref:\n     + \n     + \t\tstrbuf_release(&buf);\n       \t\tstrbuf_release(&head_ref);\n     ++\n     ++\t\tif (do_update_refs(r))\n     ++\t\t\treturn -1;\n       \t}\n       \n     -+\tdo_update_refs(r);\n     -+\n       \t/*\n     - \t * Sequence of picks finished successfully; cleanup by\n     - \t * removing the .git/sequencer directory\n      @@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n       \n       \t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n     @@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n       \t\t}\n       \n       \t\titem->offset_in_buf = base_offset;\n     +@@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n     +  */\n     + static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n     + {\n     +-\tint i;\n     ++\tint i, res;\n     + \tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n     + \tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n     + \tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n      @@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n       \t\t}\n       \t}\n       \n     -+\twrite_update_refs_state(&ctx.refs_to_oids);\n     ++\tres = write_update_refs_state(&ctx.refs_to_oids);\n      +\n       \tstring_list_clear(&ctx.refs_to_oids, 1);\n     ++\n     ++\tif (res) {\n     ++\t\t/* we failed, so clean up the new list. */\n     ++\t\tfree(ctx.items);\n     ++\t\treturn res;\n     ++\t}\n     ++\n       \tfree(todo_list->items);\n       \ttodo_list->items = ctx.items;\n     + \ttodo_list->nr = ctx.items_nr;\n      \n       ## t/t2407-worktree-heads.sh ##\n      @@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n  9:  29c7c76805a !  9:  95e2bbcedb1 sequencer: rewrite update-refs as user edits todo list\n     @@ Commit message\n      \n          We can test that this works by rewriting the todo-list several times in\n          the course of a rebase. Check that each ref is locked or unlocked for\n     -    updates after each todo-list update. We an also verify that the ref\n     +    updates after each todo-list update. We can also verify that the ref\n          update fails if a concurrent process updates one of the refs after the\n          rebase process records the \"locked\" ref location.\n      \n 10:  c0022d07579 ! 10:  a73b02568f3 rebase: add rebase.updateRefs config option\n     @@ Documentation/config/rebase.txt: rebase.autoStash::\n      \n       ## Documentation/git-rebase.txt ##\n      @@ Documentation/git-rebase.txt: start would be overridden by the presence of\n     + \tAutomatically force-update any branches that point to commits that\n       \tare being rebased. Any branches that are checked out in a worktree\n     - \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n     - \tin this way.\n     + \tare not updated in this way.\n      ++\n      +If the configuration variable `rebase.updateRefs` is set, then this option\n      +can be used to override and disable this setting.\n 11:  d53b4ff2cee = 11:  2a6577974c7 sequencer: ignore HEAD ref under --update-refs\n 12:  d5cd4b49e46 ! 12:  ec080ce1e90 sequencer: notify user of --update-refs activity\n     @@ sequencer.c: static int do_update_ref(struct repository *r, const char *refname)\n      +\tstruct strbuf update_msg = STRBUF_INIT;\n      +\tstruct strbuf error_msg = STRBUF_INIT;\n       \n     - \tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n     + \tif ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))\n     + \t\treturn res;\n       \n       \tfor_each_string_list_item(item, &refs_to_oids) {\n       \t\tstruct update_ref_record *rec = item->util;\n      +\t\tint loop_res;\n       \n     - \t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n     - \t\t\t/*\n     -@@ sequencer.c: static int do_update_refs(struct repository *r)\n     - \t\t\tcontinue;\n     - \t\t}\n     - \n      -\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n      -\t\t\t\t       item->string,\n      -\t\t\t\t       &rec->after, &rec->before,\n     @@ sequencer.c: static int do_update_refs(struct repository *r)\n       }\n       \n      @@ sequencer.c: cleanup_head_ref:\n     + \t\tstrbuf_release(&buf);\n       \t\tstrbuf_release(&head_ref);\n     - \t}\n       \n     --\tdo_update_refs(r);\n     -+\tdo_update_refs(r, opts->quiet);\n     +-\t\tif (do_update_refs(r))\n     ++\t\tif (do_update_refs(r, opts->quiet))\n     + \t\t\treturn -1;\n     + \t}\n       \n     - \t/*\n     - \t * Sequence of picks finished successfully; cleanup by\n      \n       ## t/t3404-rebase-interactive.sh ##\n      @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs updates refs correctly' '\n     @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs updates refs c\n       \n       test_expect_success 'respect user edits to update-ref steps' '\n      @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs: check failed ref update' '\n     + \t# the lock in the update-refs file.\n       \tgit rev-parse third >.git/refs/heads/second &&\n       \n     - \tgit rebase --continue 2>err &&\n     +-\tgit rebase --continue 2>err &&\n      -\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n     ++\ttest_must_fail git rebase --continue 2>err &&\n      +\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err &&\n      +\n      +\tcat >expect <<-\\EOF &&\n\n-- \ngitgitgadget\n"},{"id":"459413","messageId":"9e53a27017ac18eefef2c077a905efecab07861d.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 01/12] t2407: test bisect and rebase as black-boxes","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:33Z","receivedAt":"2022-07-19T18:33:53Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe tests added by d2ba271aad0 (branch: check for bisects and rebases,\n2022-06-14) modified hidden state to verify the branch_checked_out()\nhelper. While this indeed checks that the method implementation is _as\ndesigned_, it doesn't show that it is _correct_. Specifically, if 'git\nbisect' or 'git rebase' change their back-end for preserving refs, then\nthese tests do not demonstrate that drift as a bug in\nbranch_checked_out().\n\nModify the tests in t2407 to actually rely on a paused bisect or rebase.\nThis requires adding the !SANITIZE_LEAK prereq for tests using those\nbuiltins. The logic is still tested for leaks in the final test which\ndoes set up that back-end directly for an error state that should not be\npossible using Git commands.\n\nReported-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n t/t2407-worktree-heads.sh | 57 +++++++++++++++++++++------------------\n 1 file changed, 31 insertions(+), 26 deletions(-)\n\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex b6be42f74a2..100ab286d5c 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -7,13 +7,18 @@ TEST_PASSES_SANITIZE_LEAK=true\n \n test_expect_success 'setup' '\n \ttest_commit init &&\n-\tgit branch -f fake-1 &&\n-\tgit branch -f fake-2 &&\n \n \tfor i in 1 2 3 4\n \tdo\n+\t\tgit checkout -b conflict-$i &&\n+\t\techo \"not I\" >$i.t &&\n+\t\tgit add $i.t &&\n+\t\tgit commit -m \"will conflict\" &&\n+\n+\t\tgit checkout - &&\n \t\ttest_commit $i &&\n \t\tgit branch wt-$i &&\n+\t\tgit branch fake-$i &&\n \t\tgit worktree add wt-$i wt-$i || return 1\n \tdone &&\n \n@@ -44,26 +49,26 @@ test_expect_success 'refuse to overwrite: checked out in worktree' '\n \tdone\n '\n \n-test_expect_success 'refuse to overwrite: worktree in bisect' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in bisect' '\n+\ttest_when_finished git -C wt-4 bisect reset &&\n \n-\ttouch .git/worktrees/wt-4/BISECT_LOG &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&\n+\t# Set up a bisect so HEAD no longer points to wt-4.\n+\tgit -C wt-4 bisect start &&\n+\tgit -C wt-4 bisect bad wt-4 &&\n+\tgit -C wt-4 bisect good wt-1 &&\n \n-\ttest_must_fail git branch -f fake-2 HEAD 2>err &&\n-\tgrep \"cannot force update the branch '\\''fake-2'\\'' checked out at.*wt-4\" err\n+\ttest_must_fail git branch -f wt-4 HEAD 2>err &&\n+\tgrep \"cannot force update the branch '\\''wt-4'\\'' checked out at.*wt-4\" err\n '\n \n-test_expect_success 'refuse to overwrite: worktree in rebase' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase' '\n+\ttest_when_finished git -C wt-2 rebase --abort &&\n \n-\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n-\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n-\techo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&\n+\t# This will fail part-way through due to a conflict.\n+\ttest_must_fail git -C wt-2 rebase conflict-2 &&\n \n-\ttest_must_fail git branch -f fake-1 HEAD 2>err &&\n-\tgrep \"cannot force update the branch '\\''fake-1'\\'' checked out at.*wt-3\" err\n+\ttest_must_fail git branch -f wt-2 HEAD 2>err &&\n+\tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n '\n \n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n@@ -77,24 +82,24 @@ test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n '\n \n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in bisect' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&\n+\ttest_when_finished git -C wt-4 bisect reset &&\n \n-\ttouch .git/worktrees/wt-4/BISECT_LOG &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&\n+\t# Set up a bisect so HEAD no longer points to wt-4.\n+\tgit -C wt-4 bisect start &&\n+\tgit -C wt-4 bisect bad wt-4 &&\n+\tgit -C wt-4 bisect good wt-1 &&\n \n-\ttest_must_fail git fetch server +refs/heads/fake-2:refs/heads/fake-2 2>err &&\n+\ttest_must_fail git fetch server +refs/heads/wt-4:refs/heads/wt-4 2>err &&\n \tgrep \"refusing to fetch into branch\" err\n '\n \n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&\n+\ttest_when_finished git -C wt-3 rebase --abort &&\n \n-\tmkdir -p .git/worktrees/wt-4/rebase-merge &&\n-\ttouch .git/worktrees/wt-4/rebase-merge/interactive &&\n-\techo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/head-name &&\n-\techo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/onto &&\n+\t# This will fail part-way through due to a conflict.\n+\ttest_must_fail git -C wt-3 rebase conflict-3 &&\n \n-\ttest_must_fail git fetch server +refs/heads/fake-1:refs/heads/fake-1 2>err &&\n+\ttest_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&\n \tgrep \"refusing to fetch into branch\" err\n '\n \n-- \ngitgitgadget\n\n"},{"id":"459414","messageId":"540a3be256f8a0ca57ae0ed90cc04190a28e91e1.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 02/12] t2407: test branches currently using apply backend","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:34Z","receivedAt":"2022-07-19T18:33:56Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe tests in t2407 that verify the branch_checked_out() helper in the\ncase of bisects and rebases were added by 9347303db89 (branch: check for\nbisects and rebases, 2022-06-08). However, that commit failed to check\nfor rebases that are using the 'apply' backend.\n\nAdd a test that checks the apply backend. The implementation was already\ncorrect here, but it is good to have regression tests before modifying\nthe implementation further.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n t/t2407-worktree-heads.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex 100ab286d5c..a67ce5fb003 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -61,7 +61,17 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in bisect' '\n \tgrep \"cannot force update the branch '\\''wt-4'\\'' checked out at.*wt-4\" err\n '\n \n-test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase' '\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (apply)' '\n+\ttest_when_finished git -C wt-2 rebase --abort &&\n+\n+\t# This will fail part-way through due to a conflict.\n+\ttest_must_fail git -C wt-2 rebase --apply conflict-2 &&\n+\n+\ttest_must_fail git branch -f wt-2 HEAD 2>err &&\n+\tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n+'\n+\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (merge)' '\n \ttest_when_finished git -C wt-2 rebase --abort &&\n \n \t# This will fail part-way through due to a conflict.\n-- \ngitgitgadget\n\n"},{"id":"459415","messageId":"1089a0edb7318c3102af26e98ce1837a402bf455.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 03/12] branch: consider refs under 'update-refs'","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:35Z","receivedAt":"2022-07-19T18:34:04Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe branch_checked_out() helper helps commands like 'git branch' and\n'git fetch' from overwriting refs that are currently checked out in\nother worktrees.\n\nA future update to 'git rebase' will introduce a new '--update-refs'\noption which will update the local refs that point to commits that are\nbeing rebased. To avoid collisions as the rebase completes, we want to\nmake the future data store for these refs to be considered by\nbranch_checked_out().\n\nThe data store is a plaintext file inside the 'rebase-merge' directory\nfor that worktree. The file lists refnames followed by two OIDs, each on\nseparate lines. The OIDs will be used to store the original values of\nthe refs and the to-be-written values as the rebase progresses, but can\nbe ignored at the moment.\n\nCreate a new sequencer_get_update_refs_state() method that parses this\nfile and populates a struct string_list with the ref-OID pairs. We can\nthen use this list to add to the current_checked_out_branches strmap\nused by branch_checked_out().\n\nTo properly navigate to the rebase directory for a given worktree,\nextract the static strbuf_worktree_gitdir() method to a public API\nmethod.\n\nWe can test that this works without having Git write this file by\nartificially creating one in our test script, at least until 'git rebase\n--update-refs' is implemented and we can use it directly.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n branch.c                  | 13 +++++++\n sequencer.c               | 74 +++++++++++++++++++++++++++++++++++++++\n sequencer.h               |  9 +++++\n t/t2407-worktree-heads.sh | 23 ++++++++++++\n 4 files changed, 119 insertions(+)\n\ndiff --git a/branch.c b/branch.c\nindex 526e8237673..f252c4eef6c 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -365,6 +365,7 @@ static void prepare_checked_out_branches(void)\n \t\tchar *old;\n \t\tstruct wt_status_state state = { 0 };\n \t\tstruct worktree *wt = worktrees[i++];\n+\t\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n \n \t\tif (wt->is_bare)\n \t\t\tcontinue;\n@@ -400,6 +401,18 @@ static void prepare_checked_out_branches(void)\n \t\t\tstrbuf_release(&ref);\n \t\t}\n \t\twt_status_state_free_buffers(&state);\n+\n+\t\tif (!sequencer_get_update_refs_state(get_worktree_git_dir(wt),\n+\t\t\t\t\t\t     &update_refs)) {\n+\t\t\tstruct string_list_item *item;\n+\t\t\tfor_each_string_list_item(item, &update_refs) {\n+\t\t\t\told = strmap_put(&current_checked_out_branches,\n+\t\t\t\t\t\t item->string,\n+\t\t\t\t\t\t xstrdup(wt->path));\n+\t\t\t\tfree(old);\n+\t\t\t}\n+\t\t\tstring_list_clear(&update_refs, 1);\n+\t\t}\n \t}\n \n \tfree_worktrees(worktrees);\ndiff --git a/sequencer.c b/sequencer.c\nindex 8c3ed3532ac..7d131d06cc3 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -147,6 +147,20 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-onto\")\n  */\n static GIT_PATH_FUNC(rebase_path_refs_to_delete, \"rebase-merge/refs-to-delete\")\n \n+/*\n+ * The update-refs file stores a list of refs that will be updated at the end\n+ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file\n+ * update the OIDs for the refs in this file, but the refs are not updated\n+ * until the end of the rebase sequence.\n+ *\n+ * rebase_path_update_refs() returns the path to this file for a given\n+ * worktree directory. For the current worktree, pass the_repository->gitdir.\n+ */\n+static char *rebase_path_update_refs(const char *wt_git_dir)\n+{\n+\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_git_dir);\n+}\n+\n /*\n  * The following files are written by git-rebase just after parsing the\n  * command-line.\n@@ -169,6 +183,15 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, \"rebase-merge/no-res\n static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n \n+/**\n+ * A 'struct update_refs_record' represents a value in the update-refs\n+ * list. We use a string_list to map refs to these (before, after) pairs.\n+ */\n+struct update_ref_record {\n+\tstruct object_id before;\n+\tstruct object_id after;\n+};\n+\n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n@@ -5901,3 +5924,54 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)\n \n \treturn 0;\n }\n+\n+int sequencer_get_update_refs_state(const char *wt_dir,\n+\t\t\t\t    struct string_list *refs)\n+{\n+\tint result = 0;\n+\tFILE *fp = NULL;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf hash = STRBUF_INIT;\n+\tstruct update_ref_record *rec = NULL;\n+\n+\tchar *path = rebase_path_update_refs(wt_dir);\n+\n+\tfp = fopen(path, \"r\");\n+\tif (!fp)\n+\t\tgoto cleanup;\n+\n+\twhile (strbuf_getline(&ref, fp) != EOF) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tCALLOC_ARRAY(rec, 1);\n+\n+\t\tif (strbuf_getline(&hash, fp) == EOF ||\n+\t\t    get_oid_hex(hash.buf, &rec->before)) {\n+\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n+\t\t\t\t  path);\n+\t\t\tresult = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (strbuf_getline(&hash, fp) == EOF ||\n+\t\t    get_oid_hex(hash.buf, &rec->after)) {\n+\t\t\twarning(_(\"update-refs file at '%s' is invalid\"),\n+\t\t\t\t  path);\n+\t\t\tresult = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\titem = string_list_insert(refs, ref.buf);\n+\t\titem->util = rec;\n+\t\trec = NULL;\n+\t}\n+\n+cleanup:\n+\tif (fp)\n+\t\tfclose(fp);\n+\tfree(path);\n+\tfree(rec);\n+\tstrbuf_release(&ref);\n+\tstrbuf_release(&hash);\n+\treturn result;\n+}\ndiff --git a/sequencer.h b/sequencer.h\nindex da64473636b..3ae541bb145 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -232,4 +232,13 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose);\n int sequencer_get_last_command(struct repository* r,\n \t\t\t       enum replay_action *action);\n int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);\n+\n+/**\n+ * Append the set of ref-OID pairs that are currently stored for the 'git\n+ * rebase --update-refs' feature if such a rebase is currently happening.\n+ *\n+ * Localized to a worktree's git dir.\n+ */\n+int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs);\n+\n #endif /* SEQUENCER_H */\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex a67ce5fb003..97f5c87f8c8 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -81,6 +81,29 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n '\n \n+test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n+\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n+\n+\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n+\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n+\n+\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n+\trefs/heads/fake-3\n+\t$(git rev-parse HEAD~1)\n+\t$(git rev-parse HEAD)\n+\trefs/heads/fake-4\n+\t$(git rev-parse HEAD)\n+\t$(git rev-parse HEAD)\n+\tEOF\n+\n+\tfor i in 3 4\n+\tdo\n+\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n+\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n+\t\t\treturn 1\n+\tdone\n+'\n+\n test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '\n \ttest_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&\n \tgrep \"refusing to fetch into branch '\\''refs/heads/wt-3'\\''\" err &&\n-- \ngitgitgadget\n\n"},{"id":"459416","messageId":"d1cce4f06aa440cd0fb7992a35a9a28e9407e19b.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 04/12] rebase-interactive: update 'merge' description","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:36Z","receivedAt":"2022-07-19T18:34:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe 'merge' command description for the todo list documentation in an\ninteractive rebase has multiple lines. The lines other than the first\none start with dots ('.') while the similar multi-line documentation for\n'fixup' does not. This description only appears in the comment text of\nthe todo file during an interactive rebase.\n\nThe 'merge' command was documented when interactive rebase was first\nported to C in 145e05ac44b (rebase -i: rewrite append_todo_help() in C,\n2018-08-10). These dots might have been carried over from the previous\nshell implementation.\n\nThe 'fixup' command was documented more recently in 9e3cebd97cb (rebase\n-i: add fixup [-C | -c] command, 2021-01-29).\n\nLooking at the output in an editor, my personal opinion is that the dots\nare unnecessary and noisy. Remove them now before adding more commands\nwith multi-line documentation.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 87649d0c016..22394224faa 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -54,9 +54,9 @@ void append_todo_help(int command_count,\n \"l, label <label> = label current HEAD with a name\\n\"\n \"t, reset <label> = reset HEAD to a label\\n\"\n \"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\\n\"\n-\".       create a merge commit using the original merge commit's\\n\"\n-\".       message (or the oneline, if no original merge commit was\\n\"\n-\".       specified); use -c <commit> to reword the commit message\\n\"\n+\"        create a merge commit using the original merge commit's\\n\"\n+\"        message (or the oneline, if no original merge commit was\\n\"\n+\"        specified); use -c <commit> to reword the commit message\\n\"\n \"\\n\"\n \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n \tunsigned edit_todo = !(shortrevisions && shortonto);\n-- \ngitgitgadget\n\n"},{"id":"459417","messageId":"4c086d477f08d8e73a59861d34251aa94a26ba13.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 05/12] sequencer: define array with enum values","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:37Z","receivedAt":"2022-07-19T18:34:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe todo_command_info array defines which strings match with which\ntodo_command enum values. The array is defined in the same order as the\nenum values, but if one changed without the other, then we would have\nunexpected results.\n\nMake it easier to see changes to the enum and this array by using the\nenum values as the indices of the array.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 7d131d06cc3..2711182e43f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1708,20 +1708,20 @@ static struct {\n \tchar c;\n \tconst char *str;\n } todo_command_info[] = {\n-\t{ 'p', \"pick\" },\n-\t{ 0,   \"revert\" },\n-\t{ 'e', \"edit\" },\n-\t{ 'r', \"reword\" },\n-\t{ 'f', \"fixup\" },\n-\t{ 's', \"squash\" },\n-\t{ 'x', \"exec\" },\n-\t{ 'b', \"break\" },\n-\t{ 'l', \"label\" },\n-\t{ 't', \"reset\" },\n-\t{ 'm', \"merge\" },\n-\t{ 0,   \"noop\" },\n-\t{ 'd', \"drop\" },\n-\t{ 0,   NULL }\n+\t[TODO_PICK] = { 'p', \"pick\" },\n+\t[TODO_REVERT] = { 0,   \"revert\" },\n+\t[TODO_EDIT] = { 'e', \"edit\" },\n+\t[TODO_REWORD] = { 'r', \"reword\" },\n+\t[TODO_FIXUP] = { 'f', \"fixup\" },\n+\t[TODO_SQUASH] = { 's', \"squash\" },\n+\t[TODO_EXEC] = { 'x', \"exec\" },\n+\t[TODO_BREAK] = { 'b', \"break\" },\n+\t[TODO_LABEL] = { 'l', \"label\" },\n+\t[TODO_RESET] = { 't', \"reset\" },\n+\t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_NOOP] = { 0,   \"noop\" },\n+\t[TODO_DROP] = { 'd', \"drop\" },\n+\t[TODO_COMMENT] = { 0,   NULL },\n };\n \n static const char *command_to_string(const enum todo_command command)\n-- \ngitgitgadget\n\n"},{"id":"459418","messageId":"7b3d660196062d169627024e838be1ae6b905bd2.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 06/12] sequencer: add update-ref command","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:38Z","receivedAt":"2022-07-19T18:34:11Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nAdd the boilerplate for an \"update-ref\" command in the sequencer. This\nconnects to the current no-op do_update_ref() which will be filled in\nafter more connections are created.\n\nThe syntax in the todo list will be \"update-ref <ref-name>\" to signal\nthat we should store the current commit as the value for updating\n<ref-name> at the end of the rebase.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c |  3 +++\n sequencer.c          | 14 +++++++++++++-\n sequencer.h          |  1 +\n 3 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 22394224faa..1ff07647af3 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -57,6 +57,9 @@ void append_todo_help(int command_count,\n \"        create a merge commit using the original merge commit's\\n\"\n \"        message (or the oneline, if no original merge commit was\\n\"\n \"        specified); use -c <commit> to reword the commit message\\n\"\n+\"u, update-ref <ref> = track a placeholder for the <ref> to be updated\\n\"\n+\"                      to this position in the new commits. The <ref> is\\n\"\n+\"                      updated at the end of the rebase\\n\"\n \"\\n\"\n \"These lines can be re-ordered; they are executed from top to bottom.\\n\");\n \tunsigned edit_todo = !(shortrevisions && shortonto);\ndiff --git a/sequencer.c b/sequencer.c\nindex 2711182e43f..0dc9c05c5bb 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1719,6 +1719,7 @@ static struct {\n \t[TODO_LABEL] = { 'l', \"label\" },\n \t[TODO_RESET] = { 't', \"reset\" },\n \t[TODO_MERGE] = { 'm', \"merge\" },\n+\t[TODO_UPDATE_REF] = { 'u', \"update-ref\" },\n \t[TODO_NOOP] = { 0,   \"noop\" },\n \t[TODO_DROP] = { 'd', \"drop\" },\n \t[TODO_COMMENT] = { 0,   NULL },\n@@ -2480,7 +2481,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,\n \t\t\t     command_to_string(item->command));\n \n \tif (item->command == TODO_EXEC || item->command == TODO_LABEL ||\n-\t    item->command == TODO_RESET) {\n+\t    item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {\n \t\titem->commit = NULL;\n \t\titem->arg_offset = bol - buf;\n \t\titem->arg_len = (int)(eol - bol);\n@@ -4079,6 +4080,11 @@ leave_merge:\n \treturn ret;\n }\n \n+static int do_update_ref(struct repository *r, const char *ref_name)\n+{\n+\treturn 0;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -4454,6 +4460,12 @@ static int pick_commits(struct repository *r,\n \t\t\t\treturn error_with_patch(r, item->commit,\n \t\t\t\t\t\t\targ, item->arg_len,\n \t\t\t\t\t\t\topts, res, 0);\n+\t\t} else if (item->command == TODO_UPDATE_REF) {\n+\t\t\tstruct strbuf ref = STRBUF_INIT;\n+\t\t\tstrbuf_add(&ref, arg, item->arg_len);\n+\t\t\tif ((res = do_update_ref(r, ref.buf)))\n+\t\t\t\treschedule = 1;\n+\t\t\tstrbuf_release(&ref);\n \t\t} else if (!is_noop(item->command))\n \t\t\treturn error(_(\"unknown command %d\"), item->command);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 3ae541bb145..2cf5c1b9a38 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -95,6 +95,7 @@ enum todo_command {\n \tTODO_LABEL,\n \tTODO_RESET,\n \tTODO_MERGE,\n+\tTODO_UPDATE_REF,\n \t/* commands that do nothing but are counted for reporting progress */\n \tTODO_NOOP,\n \tTODO_DROP,\n-- \ngitgitgadget\n\n"},{"id":"459419","messageId":"7efb55e4f1429bd8a3673171d4ddf05470e7b21a.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 07/12] rebase: add --update-refs option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:39Z","receivedAt":"2022-07-19T18:34:12Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen working on a large feature, it can be helpful to break that feature\ninto multiple smaller parts that become reviewed in sequence. During\ndevelopment or during review, a change to one part of the feature could\naffect multiple of these parts. An interactive rebase can help adjust\nthe multi-part \"story\" of the branch.\n\nHowever, if there are branches tracking the different parts of the\nfeature, then rebasing the entire list of commits can create commits not\nreachable from those \"sub branches\". It can take a manual step to update\nthose branches.\n\nAdd a new --update-refs option to 'git rebase -i' that adds 'update-ref\n<ref>' steps to the todo file whenever a commit that is being rebased is\ndecorated with that <ref>. At the very end, the rebase process updates\nall of the listed refs to the values stored during the rebase operation.\n\nBe sure to iterate after any squashing or fixups are placed. Update the\nbranch only after those squashes and fixups are complete. This allows a\n--fixup commit at the tip of the feature to apply correctly to the sub\nbranch, even if it is fixing up the most-recent commit in that part.\n\nThis change update the documentation and builtin to accept the\n--update-refs option as well as updating the todo file with the\n'update-ref' commands. Tests are added to ensure that these todo\ncommands are added in the correct locations.\n\nThis change does _not_ include the actual behavior of tracking the\nupdated refs and writing the new ref values at the end of the rebase\nprocess. That is deferred to a later change.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/git-rebase.txt  |   7 +++\n builtin/rebase.c              |   5 ++\n sequencer.c                   | 107 ++++++++++++++++++++++++++++++++++\n sequencer.h                   |   1 +\n t/t2407-worktree-heads.sh     |  22 +++++++\n t/t3404-rebase-interactive.sh |  74 +++++++++++++++++++++++\n 6 files changed, 216 insertions(+)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 262fb01aec0..dfd847d6615 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -609,6 +609,12 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the\n start would be overridden by the presence of\n `rebase.rescheduleFailedExec=true` configuration.\n \n+--update-refs::\n+--no-update-refs::\n+\tAutomatically force-update any branches that point to commits that\n+\tare being rebased. Any branches that are checked out in a worktree\n+\tare not updated in this way.\n+\n INCOMPATIBLE OPTIONS\n --------------------\n \n@@ -632,6 +638,7 @@ are incompatible with the following options:\n  * --empty=\n  * --reapply-cherry-picks\n  * --edit-todo\n+ * --update-refs\n  * --root when used in combination with --onto\n \n In addition, the following pairs of options are incompatible:\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7ab50cda2ad..fd64897132a 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -102,6 +102,7 @@ struct rebase_options {\n \tint reschedule_failed_exec;\n \tint reapply_cherry_picks;\n \tint fork_point;\n+\tint update_refs;\n };\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n@@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \t\tret = complete_action(the_repository, &replay, flags,\n \t\t\tshortrevisions, opts->onto_name, opts->onto,\n \t\t\t&opts->orig_head, &commands, opts->autosquash,\n+\t\t\topts->update_refs,\n \t\t\t&todo_list);\n \t}\n \n@@ -1124,6 +1126,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n+\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n+\t\t\t N_(\"update branches that point to commits \"\n+\t\t\t    \"that are being rebased\")),\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n \t\t\tN_(\"GPG-sign commits\"),\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\ndiff --git a/sequencer.c b/sequencer.c\nindex 0dc9c05c5bb..a4ee2799d6f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -35,6 +35,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"branch.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -5638,10 +5639,113 @@ static int skip_unnecessary_picks(struct repository *r,\n \treturn 0;\n }\n \n+struct todo_add_branch_context {\n+\tstruct todo_item *items;\n+\tsize_t items_nr;\n+\tsize_t items_alloc;\n+\tstruct strbuf *buf;\n+\tstruct commit *commit;\n+\tstruct string_list refs_to_oids;\n+};\n+\n+static int add_decorations_to_list(const struct commit *commit,\n+\t\t\t\t   struct todo_add_branch_context *ctx)\n+{\n+\tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n+\n+\twhile (decoration) {\n+\t\tstruct todo_item *item;\n+\t\tconst char *path;\n+\t\tsize_t base_offset = ctx->buf->len;\n+\n+\t\tALLOC_GROW(ctx->items,\n+\t\t\tctx->items_nr + 1,\n+\t\t\tctx->items_alloc);\n+\t\titem = &ctx->items[ctx->items_nr];\n+\t\tmemset(item, 0, sizeof(*item));\n+\n+\t\t/* If the branch is checked out, then leave a comment instead. */\n+\t\tif ((path = branch_checked_out(decoration->name))) {\n+\t\t\titem->command = TODO_COMMENT;\n+\t\t\tstrbuf_addf(ctx->buf, \"# Ref %s checked out at '%s'\\n\",\n+\t\t\t\t    decoration->name, path);\n+\t\t} else {\n+\t\t\tstruct string_list_item *sti;\n+\t\t\titem->command = TODO_UPDATE_REF;\n+\t\t\tstrbuf_addf(ctx->buf, \"%s\\n\", decoration->name);\n+\n+\t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n+\t\t\t\t\t\t decoration->name);\n+\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n+\t\t}\n+\n+\t\titem->offset_in_buf = base_offset;\n+\t\titem->arg_offset = base_offset;\n+\t\titem->arg_len = ctx->buf->len - base_offset;\n+\t\tctx->items_nr++;\n+\n+\t\tdecoration = decoration->next;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * For each 'pick' command, find out if the commit has a decoration in\n+ * refs/heads/. If so, then add a 'label for-update-refs/' command.\n+ */\n+static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n+{\n+\tint i;\n+\tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n+\tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n+\tstruct decoration_filter decoration_filter = {\n+\t\t.include_ref_pattern = &decorate_refs_include,\n+\t\t.exclude_ref_pattern = &decorate_refs_exclude,\n+\t\t.exclude_ref_config_pattern = &decorate_refs_exclude_config,\n+\t};\n+\tstruct todo_add_branch_context ctx = {\n+\t\t.buf = &todo_list->buf,\n+\t\t.refs_to_oids = STRING_LIST_INIT_DUP,\n+\t};\n+\n+\tctx.items_alloc = 2 * todo_list->nr + 1;\n+\tALLOC_ARRAY(ctx.items, ctx.items_alloc);\n+\n+\tstring_list_append(&decorate_refs_include, \"refs/heads/\");\n+\tload_ref_decorations(&decoration_filter, 0);\n+\n+\tfor (i = 0; i < todo_list->nr; ) {\n+\t\tstruct todo_item *item = &todo_list->items[i];\n+\n+\t\t/* insert ith item into new list */\n+\t\tALLOC_GROW(ctx.items,\n+\t\t\t   ctx.items_nr + 1,\n+\t\t\t   ctx.items_alloc);\n+\n+\t\tctx.items[ctx.items_nr++] = todo_list->items[i++];\n+\n+\t\tif (item->commit) {\n+\t\t\tctx.commit = item->commit;\n+\t\t\tadd_decorations_to_list(item->commit, &ctx);\n+\t\t}\n+\t}\n+\n+\tstring_list_clear(&ctx.refs_to_oids, 1);\n+\tfree(todo_list->items);\n+\ttodo_list->items = ctx.items;\n+\ttodo_list->nr = ctx.items_nr;\n+\ttodo_list->alloc = ctx.items_alloc;\n+\n+\treturn 0;\n+}\n+\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list)\n {\n \tchar shortonto[GIT_MAX_HEXSZ + 1];\n@@ -5660,6 +5764,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\titem->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;\n \t}\n \n+\tif (update_refs && todo_list_add_update_ref_commands(todo_list))\n+\t\treturn -1;\n+\n \tif (autosquash && todo_list_rearrange_squash(todo_list))\n \t\treturn -1;\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 2cf5c1b9a38..e671d7e0743 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -167,6 +167,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const struct object_id *orig_head,\n \t\t    struct string_list *commands, unsigned autosquash,\n+\t\t    unsigned update_refs,\n \t\t    struct todo_list *todo_list);\n int todo_list_rearrange_squash(struct todo_list *todo_list);\n \ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex 97f5c87f8c8..8a03f14df8d 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -164,4 +164,26 @@ test_expect_success 'refuse to overwrite when in error states' '\n \tdone\n '\n \n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite during rebase with --update-refs' '\n+\tgit commit --fixup HEAD~2 --allow-empty &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n+\t\t! grep \"update-refs\" todo\n+\t) &&\n+\tgit branch -f allow-update HEAD~2 &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\t\ttest_must_fail git rebase -i --update-refs HEAD~3 >todo &&\n+\t\tgrep \"update-ref refs/heads/allow-update\" todo\n+\t)\n+'\n+\n+# This must be the last test in this file\n+test_expect_success '$EDITOR and friends are unchanged' '\n+\ttest_editor_unchanged\n+'\n+\n test_done\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f31afd4a547..75c0b90fae1 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1743,6 +1743,80 @@ test_expect_success 'ORIG_HEAD is updated correctly' '\n \ttest_cmp_rev ORIG_HEAD test-orig-head@{1}\n '\n \n+test_expect_success '--update-refs adds label and update-ref commands' '\n+\tgit checkout -b update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit commit --allow-empty --fixup=third &&\n+\tgit branch -f is-not-reordered &&\n+\tgit commit --allow-empty --fixup=HEAD~4 &&\n+\tgit branch -f shared-tip &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tpick $(git log -1 --format=%h J) J\n+\t\tfixup $(git log -1 --format=%h update-refs) fixup! J # empty\n+\t\tupdate-ref refs/heads/second\n+\t\tupdate-ref refs/heads/first\n+\t\tpick $(git log -1 --format=%h K) K\n+\t\tpick $(git log -1 --format=%h L) L\n+\t\tfixup $(git log -1 --format=%h is-not-reordered) fixup! L # empty\n+\t\tupdate-ref refs/heads/third\n+\t\tpick $(git log -1 --format=%h M) M\n+\t\tupdate-ref refs/heads/no-conflict-branch\n+\t\tupdate-ref refs/heads/is-not-reordered\n+\t\tupdate-ref refs/heads/shared-tip\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n+test_expect_success '--update-refs adds commands with --rebase-merges' '\n+\tgit checkout -b update-refs-with-merge no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit merge -m merge branch2 &&\n+\tgit branch -f merge-branch &&\n+\tgit commit --fixup=third --allow-empty &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tlabel onto\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h branch2~1) F\n+\t\tpick $(git log -1 --format=%h branch2) I\n+\t\tupdate-ref refs/heads/branch2\n+\t\tlabel merge\n+\t\treset onto\n+\t\tpick $(git log -1 --format=%h refs/heads/second) J\n+\t\tupdate-ref refs/heads/second\n+\t\tupdate-ref refs/heads/first\n+\t\tpick $(git log -1 --format=%h refs/heads/third~1) K\n+\t\tpick $(git log -1 --format=%h refs/heads/third) L\n+\t\tfixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty\n+\t\tupdate-ref refs/heads/third\n+\t\tpick $(git log -1 --format=%h HEAD~2) M\n+\t\tupdate-ref refs/heads/no-conflict-branch\n+\t\tmerge -C $(git log -1 --format=%h HEAD~1) merge # merge\n+\t\tupdate-ref refs/heads/merge-branch\n+\t\tEOF\n+\n+\t\ttest_must_fail git rebase -i --autosquash \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   --update-refs primary >todo &&\n+\n+\t\ttest_cmp expect todo\n+\t)\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"459420","messageId":"e7a91bdffbd9a1fe0eda9a5a264f7734aadbf688.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 08/12] rebase: update refs from 'update-ref' commands","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:40Z","receivedAt":"2022-07-19T18:34:15Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change introduced the 'git rebase --update-refs' option\nwhich added 'update-ref <ref>' commands to the todo list of an\ninteractive rebase.\n\nTeach Git to record the HEAD position when reaching these 'update-ref'\ncommands. The ref/before/after triple is stored in the\n$GIT_DIR/rebase-merge/update-refs file. A previous change parsed this\nfile to avoid having other processes updating the refs in that file\nwhile the rebase is in progress.\n\nNot only do we update the file when the sequencer reaches these\n'update-ref' commands, we then update the refs themselves at the end of\nthe rebase sequence. If the rebase is aborted before this final step,\nthen the refs are not updated. The 'before' value is used to ensure that\nwe do not accidentally obliterate a ref that was updated concurrently\n(say, by an older version of Git or a third-party tool).\n\nNow that the 'git rebase --update-refs' command is implemented to write\nto the update-refs file, we can remove the fake construction of the\nupdate-refs file from a test in t2407-worktree-heads.sh.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c                   | 120 +++++++++++++++++++++++++++++++++-\n t/t2407-worktree-heads.sh     |  21 ++----\n t/t3404-rebase-interactive.sh |  17 +++++\n 3 files changed, 140 insertions(+), 18 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex a4ee2799d6f..ce8c7d8cd3a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -36,6 +36,7 @@\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n #include \"branch.h\"\n+#include \"log-tree.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -193,6 +194,21 @@ struct update_ref_record {\n \tstruct object_id after;\n };\n \n+static struct update_ref_record *init_update_ref_record(const char *ref)\n+{\n+\tstruct update_ref_record *rec;\n+\n+\tCALLOC_ARRAY(rec, 1);\n+\n+\toidcpy(&rec->before, null_oid());\n+\toidcpy(&rec->after, null_oid());\n+\n+\t/* This may fail, but that's fine, we will keep the null OID. */\n+\tread_ref(ref, &rec->before);\n+\n+\treturn rec;\n+}\n+\n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n@@ -4081,11 +4097,97 @@ leave_merge:\n \treturn ret;\n }\n \n-static int do_update_ref(struct repository *r, const char *ref_name)\n+static int write_update_refs_state(struct string_list *refs_to_oids)\n+{\n+\tint result = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tFILE *fp = NULL;\n+\tstruct string_list_item *item;\n+\tchar *path;\n+\n+\tif (!refs_to_oids->nr)\n+\t\treturn 0;\n+\n+\tpath = rebase_path_update_refs(the_repository->gitdir);\n+\n+\tif (safe_create_leading_directories(path)) {\n+\t\tresult = error(_(\"unable to create leading directories of %s\"),\n+\t\t\t       path);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (hold_lock_file_for_update(&lock, path, 0) < 0) {\n+\t\tresult = error(_(\"another 'rebase' process appears to be running; \"\n+\t\t\t\t \"'%s.lock' already exists\"),\n+\t\t\t       path);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfp = fdopen_lock_file(&lock, \"w\");\n+\tif (!fp) {\n+\t\tresult = error_errno(_(\"could not open '%s' for writing\"), path);\n+\t\trollback_lock_file(&lock);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfor_each_string_list_item(item, refs_to_oids) {\n+\t\tstruct update_ref_record *rec = item->util;\n+\t\tfprintf(fp, \"%s\\n%s\\n%s\\n\", item->string,\n+\t\t\toid_to_hex(&rec->before), oid_to_hex(&rec->after));\n+\t}\n+\n+\tresult = commit_lock_file(&lock);\n+\n+cleanup:\n+\tfree(path);\n+\treturn result;\n+}\n+\n+static int do_update_ref(struct repository *r, const char *refname)\n {\n+\tstruct string_list_item *item;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\n+\tif (sequencer_get_update_refs_state(r->gitdir, &list))\n+\t\treturn -1;\n+\n+\tfor_each_string_list_item(item, &list) {\n+\t\tif (!strcmp(item->string, refname)) {\n+\t\t\tstruct update_ref_record *rec = item->util;\n+\t\t\tif (read_ref(\"HEAD\", &rec->after))\n+\t\t\t\treturn -1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\twrite_update_refs_state(&list);\n+\tstring_list_clear(&list, 1);\n \treturn 0;\n }\n \n+static int do_update_refs(struct repository *r)\n+{\n+\tint res = 0;\n+\tstruct string_list_item *item;\n+\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(r);\n+\n+\tif ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))\n+\t\treturn res;\n+\n+\tfor_each_string_list_item(item, &refs_to_oids) {\n+\t\tstruct update_ref_record *rec = item->util;\n+\n+\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n+\t\t\t\t       item->string,\n+\t\t\t\t       &rec->after, &rec->before,\n+\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n+\t}\n+\n+\tstring_list_clear(&refs_to_oids, 1);\n+\treturn res;\n+}\n+\n static int is_final_fixup(struct todo_list *todo_list)\n {\n \tint i = todo_list->current;\n@@ -4601,6 +4703,9 @@ cleanup_head_ref:\n \n \t\tstrbuf_release(&buf);\n \t\tstrbuf_release(&head_ref);\n+\n+\t\tif (do_update_refs(r))\n+\t\t\treturn -1;\n \t}\n \n \t/*\n@@ -5676,7 +5781,7 @@ static int add_decorations_to_list(const struct commit *commit,\n \n \t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n \t\t\t\t\t\t decoration->name);\n-\t\t\tsti->util = oiddup(the_hash_algo->null_oid);\n+\t\t\tsti->util = init_update_ref_record(decoration->name);\n \t\t}\n \n \t\titem->offset_in_buf = base_offset;\n@@ -5696,7 +5801,7 @@ static int add_decorations_to_list(const struct commit *commit,\n  */\n static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n {\n-\tint i;\n+\tint i, res;\n \tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n \tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n \tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n@@ -5732,7 +5837,16 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n \t\t}\n \t}\n \n+\tres = write_update_refs_state(&ctx.refs_to_oids);\n+\n \tstring_list_clear(&ctx.refs_to_oids, 1);\n+\n+\tif (res) {\n+\t\t/* we failed, so clean up the new list. */\n+\t\tfree(ctx.items);\n+\t\treturn res;\n+\t}\n+\n \tfree(todo_list->items);\n \ttodo_list->items = ctx.items;\n \ttodo_list->nr = ctx.items_nr;\ndiff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh\nindex 8a03f14df8d..50815acd3e8 100755\n--- a/t/t2407-worktree-heads.sh\n+++ b/t/t2407-worktree-heads.sh\n@@ -81,25 +81,16 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n \tgrep \"cannot force update the branch '\\''wt-2'\\'' checked out at.*wt-2\" err\n '\n \n-test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' '\n-\ttest_when_finished rm -rf .git/worktrees/wt-3/rebase-merge &&\n-\n-\tmkdir -p .git/worktrees/wt-3/rebase-merge &&\n-\ttouch .git/worktrees/wt-3/rebase-merge/interactive &&\n+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' '\n+\ttest_when_finished git -C wt-3 rebase --abort &&\n \n-\tcat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF &&\n-\trefs/heads/fake-3\n-\t$(git rev-parse HEAD~1)\n-\t$(git rev-parse HEAD)\n-\trefs/heads/fake-4\n-\t$(git rev-parse HEAD)\n-\t$(git rev-parse HEAD)\n-\tEOF\n+\tgit branch -f can-be-updated wt-3 &&\n+\ttest_must_fail git -C wt-3 rebase --update-refs conflict-3 &&\n \n \tfor i in 3 4\n \tdo\n-\t\ttest_must_fail git branch -f fake-$i HEAD 2>err &&\n-\t\tgrep \"cannot force update the branch '\\''fake-$i'\\'' checked out at.*wt-3\" err ||\n+\t\ttest_must_fail git branch -f can-be-updated HEAD 2>err &&\n+\t\tgrep \"cannot force update the branch '\\''can-be-updated'\\'' checked out at.*wt-3\" err ||\n \t\t\treturn 1\n \tdone\n '\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 75c0b90fae1..2fffcf5d5fb 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1817,6 +1817,23 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t)\n '\n \n+test_expect_success '--update-refs updates refs correctly' '\n+\tgit checkout -B update-refs no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\ttest_commit extra2 fileX &&\n+\tgit commit --amend --fixup=L &&\n+\n+\tgit rebase -i --autosquash --update-refs primary &&\n+\n+\ttest_cmp_rev HEAD~3 refs/heads/first &&\n+\ttest_cmp_rev HEAD~3 refs/heads/second &&\n+\ttest_cmp_rev HEAD~1 refs/heads/third &&\n+\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"459421","messageId":"95e2bbcedb1740f240a0a470822549d63075cc46.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 09/12] sequencer: rewrite update-refs as user edits todo list","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:41Z","receivedAt":"2022-07-19T18:34:16Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nAn interactive rebase provides opportunities for the user to edit the\ntodo list. The --update-refs option initializes the list with some\n'update-ref <ref>' steps, but the user could add these manually.\nFurther, the user could add or remove these steps during pauses in the\ninteractive rebase.\n\nAdd a new method, todo_list_filter_update_refs(), that scans a todo_list\nand compares it to the stored update-refs file. There are two actions\nthat can happen at this point:\n\n1. If a '<ref>/<before>/<after>' triple in the update-refs file does not\n   have a matching 'update-ref <ref>' command in the todo-list _and_ the\n   <after> value is the null OID, then remove that triple. Here, the\n   user removed the 'update-ref <ref>' command before it was executed,\n   since if it was executed then the <after> value would store the\n   commit at that position.\n\n2. If a 'update-ref <ref>' command in the todo-list does not have a\n   matching '<ref>/<before>/<after>' triple in the update-refs file,\n   then insert a new one. Store the <before> value to be the current\n   OID pointed at by <ref>. This is handled inside of the\n   init_update_ref_record() helper method.\n\nWe can test that this works by rewriting the todo-list several times in\nthe course of a rebase. Check that each ref is locked or unlocked for\nupdates after each todo-list update. We can also verify that the ref\nupdate fails if a concurrent process updates one of the refs after the\nrebase process records the \"locked\" ref location.\n\nTo help these tests, add a new 'set_replace_editor' helper that will\nreplace the todo-list with an exact file.\n\nReported-by: Phillip Wood <phillip.wood123@gmail.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n rebase-interactive.c          |   6 ++\n sequencer.c                   |  96 +++++++++++++++++++++++\n sequencer.h                   |  12 +++\n t/lib-rebase.sh               |  15 ++++\n t/t3404-rebase-interactive.sh | 139 ++++++++++++++++++++++++++++++++++\n 5 files changed, 268 insertions(+)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 1ff07647af3..7407c593191 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -146,6 +146,12 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\treturn -4;\n \t}\n \n+\t/*\n+\t * See if branches need to be added or removed from the update-refs\n+\t * file based on the new todo list.\n+\t */\n+\ttodo_list_filter_update_refs(r, new_todo);\n+\n \treturn 0;\n }\n \ndiff --git a/sequencer.c b/sequencer.c\nindex ce8c7d8cd3a..67812c0294f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4143,6 +4143,102 @@ cleanup:\n \treturn result;\n }\n \n+/*\n+ * Parse the update-refs file for the current rebase, then remove the\n+ * refs that do not appear in the todo_list (and have not had updated\n+ * values stored) and add refs that are in the todo_list but not\n+ * represented in the update-refs file.\n+ *\n+ * If there are changes to the update-refs list, then write the new state\n+ * to disk.\n+ */\n+void todo_list_filter_update_refs(struct repository *r,\n+\t\t\t\t  struct todo_list *todo_list)\n+{\n+\tint i;\n+\tint updated = 0;\n+\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n+\n+\tsequencer_get_update_refs_state(r->gitdir, &update_refs);\n+\n+\t/*\n+\t * For each item in the update_refs list, if it has no updated\n+\t * value and does not appear in the todo_list, then remove it\n+\t * from the update_refs list.\n+\t */\n+\tfor (i = 0; i < update_refs.nr; i++) {\n+\t\tint j;\n+\t\tint found = 0;\n+\t\tconst char *ref = update_refs.items[i].string;\n+\t\tsize_t reflen = strlen(ref);\n+\t\tstruct update_ref_record *rec = update_refs.items[i].util;\n+\n+\t\t/* OID already stored as updated. */\n+\t\tif (!is_null_oid(&rec->after))\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; !found && j < todo_list->total_nr; j++) {\n+\t\t\tstruct todo_item *item = &todo_list->items[j];\n+\t\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n+\n+\t\t\tif (item->command != TODO_UPDATE_REF)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (item->arg_len != reflen ||\n+\t\t\t    strncmp(arg, ref, reflen))\n+\t\t\t\tcontinue;\n+\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found) {\n+\t\t\tfree(update_refs.items[i].string);\n+\t\t\tfree(update_refs.items[i].util);\n+\n+\t\t\tupdate_refs.nr--;\n+\t\t\tMOVE_ARRAY(update_refs.items + i, update_refs.items + i + 1, update_refs.nr - i);\n+\n+\t\t\tupdated = 1;\n+\t\t\ti--;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * For each todo_item, check if its ref is in the update_refs list.\n+\t * If not, then add it as an un-updated ref.\n+\t */\n+\tfor (i = 0; i < todo_list->total_nr; i++) {\n+\t\tstruct todo_item *item = &todo_list->items[i];\n+\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n+\t\tint j, found = 0;\n+\n+\t\tif (item->command != TODO_UPDATE_REF)\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; !found && j < update_refs.nr; j++) {\n+\t\t\tconst char *ref = update_refs.items[j].string;\n+\n+\t\t\tfound = strlen(ref) == item->arg_len &&\n+\t\t\t\t!strncmp(ref, arg, item->arg_len);\n+\t\t}\n+\n+\t\tif (!found) {\n+\t\t\tstruct string_list_item *inserted;\n+\t\t\tstruct strbuf argref = STRBUF_INIT;\n+\n+\t\t\tstrbuf_add(&argref, arg, item->arg_len);\n+\t\t\tinserted = string_list_insert(&update_refs, argref.buf);\n+\t\t\tinserted->util = init_update_ref_record(argref.buf);\n+\t\t\tstrbuf_release(&argref);\n+\t\t\tupdated = 1;\n+\t\t}\n+\t}\n+\n+\tif (updated)\n+\t\twrite_update_refs_state(&update_refs);\n+\tstring_list_clear(&update_refs, 1);\n+}\n+\n static int do_update_ref(struct repository *r, const char *refname)\n {\n \tstruct string_list_item *item;\ndiff --git a/sequencer.h b/sequencer.h\nindex e671d7e0743..98f3bcc1658 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -132,6 +132,18 @@ void todo_list_release(struct todo_list *todo_list);\n const char *todo_item_get_arg(struct todo_list *todo_list,\n \t\t\t      struct todo_item *item);\n \n+/*\n+ * Parse the update-refs file for the current rebase, then remove the\n+ * refs that do not appear in the todo_list (and have not had updated\n+ * values stored) and add refs that are in the todo_list but not\n+ * represented in the update-refs file.\n+ *\n+ * If there are changes to the update-refs list, then write the new state\n+ * to disk.\n+ */\n+void todo_list_filter_update_refs(struct repository *r,\n+\t\t\t\t  struct todo_list *todo_list);\n+\n /* Call this to setup defaults before parsing command line options */\n void sequencer_init_config(struct replay_opts *opts);\n int sequencer_pick_revisions(struct repository *repo,\ndiff --git a/t/lib-rebase.sh b/t/lib-rebase.sh\nindex ec6b9b107da..b57541356bd 100644\n--- a/t/lib-rebase.sh\n+++ b/t/lib-rebase.sh\n@@ -207,3 +207,18 @@ check_reworded_commits () {\n \t\t>reword-log &&\n \ttest_cmp reword-expected reword-log\n }\n+\n+# usage: set_replace_editor <file>\n+#\n+# Replace the todo file with the exact contents of the given file.\n+set_replace_editor () {\n+\tcat >script <<-\\EOF &&\n+\tcat FILENAME >\"$1\"\n+\n+\techo 'rebase -i script after editing:'\n+\tcat \"$1\"\n+\tEOF\n+\n+\tsed -e \"s/FILENAME/$1/g\" <script | write_script fake-editor.sh &&\n+\ttest_set_editor \"$(pwd)/fake-editor.sh\"\n+}\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 2fffcf5d5fb..bebf9b4def7 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1834,6 +1834,145 @@ test_expect_success '--update-refs updates refs correctly' '\n \ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n '\n \n+test_expect_success 'respect user edits to update-ref steps' '\n+\tgit checkout -B update-refs-break no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~3 &&\n+\tgit branch -f third HEAD~1 &&\n+\tgit branch -f unseen base &&\n+\n+\t# First, we will add breaks to the expected todo file\n+\tcat >fake-todo-1 <<-EOF &&\n+\tpick $(git rev-parse HEAD~3)\n+\tbreak\n+\tupdate-ref refs/heads/second\n+\tupdate-ref refs/heads/first\n+\n+\tpick $(git rev-parse HEAD~2)\n+\tpick $(git rev-parse HEAD~1)\n+\tupdate-ref refs/heads/third\n+\n+\tpick $(git rev-parse HEAD)\n+\tupdate-ref refs/heads/no-conflict-branch\n+\tEOF\n+\n+\t# Second, we will drop some update-refs commands (and move one)\n+\tcat >fake-todo-2 <<-EOF &&\n+\tupdate-ref refs/heads/second\n+\n+\tpick $(git rev-parse HEAD~2)\n+\tupdate-ref refs/heads/third\n+\tpick $(git rev-parse HEAD~1)\n+\tbreak\n+\n+\tpick $(git rev-parse HEAD)\n+\tEOF\n+\n+\t# Third, we will:\n+\t# * insert a new one (new-branch),\n+\t# * re-add an old one (first), and\n+\t# * add a second instance of a previously-stored one (second)\n+\tcat >fake-todo-3 <<-EOF &&\n+\tupdate-ref refs/heads/unseen\n+\tupdate-ref refs/heads/new-branch\n+\tpick $(git rev-parse HEAD)\n+\tupdate-ref refs/heads/first\n+\tupdate-ref refs/heads/second\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor fake-todo-1 &&\n+\t\tgit rebase -i --update-refs primary &&\n+\n+\t\t# These branches are currently locked.\n+\t\tfor b in first second third no-conflict-branch\n+\t\tdo\n+\t\t\ttest_must_fail git branch -f $b base || return 1\n+\t\tdone &&\n+\n+\t\tset_replace_editor fake-todo-2 &&\n+\t\tgit rebase --edit-todo &&\n+\n+\t\t# These branches are currently locked.\n+\t\tfor b in second third\n+\t\tdo\n+\t\t\ttest_must_fail git branch -f $b base || return 1\n+\t\tdone &&\n+\n+\t\t# These branches are currently unlocked for checkout.\n+\t\tfor b in first no-conflict-branch\n+\t\tdo\n+\t\t\tgit worktree add wt-$b $b &&\n+\t\t\tgit worktree remove wt-$b || return 1\n+\t\tdone &&\n+\n+\t\tgit rebase --continue &&\n+\n+\t\tset_replace_editor fake-todo-3 &&\n+\t\tgit rebase --edit-todo &&\n+\n+\t\t# These branches are currently locked.\n+\t\tfor b in second third first unseen\n+\t\tdo\n+\t\t\ttest_must_fail git branch -f $b base || return 1\n+\t\tdone &&\n+\n+\t\t# These branches are currently unlocked for checkout.\n+\t\tfor b in no-conflict-branch\n+\t\tdo\n+\t\t\tgit worktree add wt-$b $b &&\n+\t\t\tgit worktree remove wt-$b || return 1\n+\t\tdone &&\n+\n+\t\tgit rebase --continue\n+\t) &&\n+\n+\ttest_cmp_rev HEAD~2 refs/heads/third &&\n+\ttest_cmp_rev HEAD~1 refs/heads/unseen &&\n+\ttest_cmp_rev HEAD~1 refs/heads/new-branch &&\n+\ttest_cmp_rev HEAD refs/heads/first &&\n+\ttest_cmp_rev HEAD refs/heads/second &&\n+\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n+'\n+\n+test_expect_success '--update-refs: check failed ref update' '\n+\tgit checkout -B update-refs-error no-conflict-branch &&\n+\tgit branch -f base HEAD~4 &&\n+\tgit branch -f first HEAD~3 &&\n+\tgit branch -f second HEAD~2 &&\n+\tgit branch -f third HEAD~1 &&\n+\n+\tcat >fake-todo <<-EOF &&\n+\tpick $(git rev-parse HEAD~3)\n+\tbreak\n+\tupdate-ref refs/heads/first\n+\n+\tpick $(git rev-parse HEAD~2)\n+\tupdate-ref refs/heads/second\n+\n+\tpick $(git rev-parse HEAD~1)\n+\tupdate-ref refs/heads/third\n+\n+\tpick $(git rev-parse HEAD)\n+\tupdate-ref refs/heads/no-conflict-branch\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor fake-todo &&\n+\t\tgit rebase -i --update-refs base\n+\t) &&\n+\n+\t# At this point, the values of first, second, and third are\n+\t# recorded in the update-refs file. We will force-update the\n+\t# \"second\" ref, but \"git branch -f\" will not work because of\n+\t# the lock in the update-refs file.\n+\tgit rev-parse third >.git/refs/heads/second &&\n+\n+\tgit rebase --continue 2>err &&\n+\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n+'\n+\n # This must be the last test in this file\n test_expect_success '$EDITOR and friends are unchanged' '\n \ttest_editor_unchanged\n-- \ngitgitgadget\n\n"},{"id":"459422","messageId":"a73b02568f3aa35c6db219b01040379c4d120067.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 10/12] rebase: add rebase.updateRefs config option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:42Z","receivedAt":"2022-07-19T18:34:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nThe previous change added the --update-refs command-line option.  For\nusers who always want this mode, create the rebase.updateRefs config\noption which behaves the same way as rebase.autoSquash does with the\n--autosquash option.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/config/rebase.txt |  3 +++\n Documentation/git-rebase.txt    |  3 +++\n builtin/rebase.c                |  5 +++++\n t/t3404-rebase-interactive.sh   | 14 ++++++++++++++\n 4 files changed, 25 insertions(+)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex 8c979cb20f2..f19bd0e0407 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -21,6 +21,9 @@ rebase.autoStash::\n \t`--autostash` options of linkgit:git-rebase[1].\n \tDefaults to false.\n \n+rebase.updateRefs::\n+\tIf set to true enable `--update-refs` option by default.\n+\n rebase.missingCommitsCheck::\n \tIf set to \"warn\", git rebase -i will print a warning if some\n \tcommits are removed (e.g. a line was deleted), however the\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex dfd847d6615..305255f1e15 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -614,6 +614,9 @@ start would be overridden by the presence of\n \tAutomatically force-update any branches that point to commits that\n \tare being rebased. Any branches that are checked out in a worktree\n \tare not updated in this way.\n++\n+If the configuration variable `rebase.updateRefs` is set, then this option\n+can be used to override and disable this setting.\n \n INCOMPATIBLE OPTIONS\n --------------------\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex fd64897132a..dbb91895687 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -802,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"rebase.updaterefs\")) {\n+\t\topts->update_refs = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"rebase.reschedulefailedexec\")) {\n \t\topts->reschedule_failed_exec = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex bebf9b4def7..1a27bb0626d 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1772,6 +1772,12 @@ test_expect_success '--update-refs adds label and update-ref commands' '\n \t\tEOF\n \n \t\ttest_must_fail git rebase -i --autosquash --update-refs primary >todo &&\n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n@@ -1813,6 +1819,14 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t\t\t\t   --rebase-merges=rebase-cousins \\\n \t\t\t\t   --update-refs primary >todo &&\n \n+\t\ttest_cmp expect todo &&\n+\n+\t\ttest_must_fail git -c rebase.autosquash=true \\\n+\t\t\t\t   -c rebase.updaterefs=true \\\n+\t\t\t\t   rebase -i \\\n+\t\t\t\t   --rebase-merges=rebase-cousins \\\n+\t\t\t\t   primary >todo &&\n+\n \t\ttest_cmp expect todo\n \t)\n '\n-- \ngitgitgadget\n\n"},{"id":"459423","messageId":"ec080ce1e90c2593a7d2f44e2cc1655aad5756eb.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 12/12] sequencer: notify user of --update-refs activity","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:44Z","receivedAt":"2022-07-19T18:34:21Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen the user runs 'git rebase -i --update-refs', the end message still\nsays only\n\n  Successfully rebased and updated <HEAD-ref>.\n\nUpdate the sequencer to collect the successful (and unsuccessful) ref\nupdates due to the --update-refs option, so the end message now says\n\n  Successfully rebased and updated <HEAD-ref>.\n  Updated the following refs with --update-refs:\n\trefs/heads/first\n\trefs/heads/third\n  Failed to update the following refs with --update-refs:\n\trefs/heads/second\n\nTo test this output, we need to be very careful to format the expected\nerror to drop the leading tab characters. Also, we need to be aware that\nthe verbose output from 'git rebase' is writing progress lines which\ndon't use traditional newlines but clear the line after every progress\nitem is complete. When opening the error file in an editor, these lines\nare visible, but when looking at the diff in a terminal those lines\ndisappear because of the characters that delete the previous characters.\nUse 'sed' to clear those progress lines and clear the tabs so we can get\nan exact match on our expected output.\n\nReported-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c                   | 40 +++++++++++++++++++++++++++++------\n t/t3404-rebase-interactive.sh | 37 ++++++++++++++++++++++++++++----\n 2 files changed, 67 insertions(+), 10 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1602649332b..96e1d58ede8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4261,26 +4261,54 @@ static int do_update_ref(struct repository *r, const char *refname)\n \treturn 0;\n }\n \n-static int do_update_refs(struct repository *r)\n+static int do_update_refs(struct repository *r, int quiet)\n {\n \tint res = 0;\n \tstruct string_list_item *item;\n \tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n \tstruct ref_store *refs = get_main_ref_store(r);\n+\tstruct strbuf update_msg = STRBUF_INIT;\n+\tstruct strbuf error_msg = STRBUF_INIT;\n \n \tif ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))\n \t\treturn res;\n \n \tfor_each_string_list_item(item, &refs_to_oids) {\n \t\tstruct update_ref_record *rec = item->util;\n+\t\tint loop_res;\n \n-\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n-\t\t\t\t       item->string,\n-\t\t\t\t       &rec->after, &rec->before,\n-\t\t\t\t       0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tloop_res = refs_update_ref(refs, \"rewritten during rebase\",\n+\t\t\t\t\t   item->string,\n+\t\t\t\t\t   &rec->after, &rec->before,\n+\t\t\t\t\t   0, UPDATE_REFS_MSG_ON_ERR);\n+\t\tres |= loop_res;\n+\n+\t\tif (quiet)\n+\t\t\tcontinue;\n+\n+\t\tif (loop_res)\n+\t\t\tstrbuf_addf(&error_msg, \"\\t%s\\n\", item->string);\n+\t\telse\n+\t\t\tstrbuf_addf(&update_msg, \"\\t%s\\n\", item->string);\n+\t}\n+\n+\tif (!quiet &&\n+\t    (update_msg.len || error_msg.len)) {\n+\t\tfprintf(stderr,\n+\t\t\t_(\"Updated the following refs with %s:\\n%s\"),\n+\t\t\t\"--update-refs\",\n+\t\t\tupdate_msg.buf);\n+\n+\t\tif (res)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Failed to update the following refs with %s:\\n%s\"),\n+\t\t\t\t\"--update-refs\",\n+\t\t\t\terror_msg.buf);\n \t}\n \n \tstring_list_clear(&refs_to_oids, 1);\n+\tstrbuf_release(&update_msg);\n+\tstrbuf_release(&error_msg);\n \treturn res;\n }\n \n@@ -4800,7 +4828,7 @@ cleanup_head_ref:\n \t\tstrbuf_release(&buf);\n \t\tstrbuf_release(&head_ref);\n \n-\t\tif (do_update_refs(r))\n+\t\tif (do_update_refs(r, opts->quiet))\n \t\t\treturn -1;\n \t}\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 1a27bb0626d..688b01e3eb6 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1840,12 +1840,26 @@ test_expect_success '--update-refs updates refs correctly' '\n \ttest_commit extra2 fileX &&\n \tgit commit --amend --fixup=L &&\n \n-\tgit rebase -i --autosquash --update-refs primary &&\n+\tgit rebase -i --autosquash --update-refs primary 2>err &&\n \n \ttest_cmp_rev HEAD~3 refs/heads/first &&\n \ttest_cmp_rev HEAD~3 refs/heads/second &&\n \ttest_cmp_rev HEAD~1 refs/heads/third &&\n-\ttest_cmp_rev HEAD refs/heads/no-conflict-branch\n+\ttest_cmp_rev HEAD refs/heads/no-conflict-branch &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tSuccessfully rebased and updated refs/heads/update-refs.\n+\tUpdated the following refs with --update-refs:\n+\t\trefs/heads/first\n+\t\trefs/heads/no-conflict-branch\n+\t\trefs/heads/second\n+\t\trefs/heads/third\n+\tEOF\n+\n+\t# Clear \"Rebasing (X/Y)\" progress lines and drop leading tabs.\n+\tsed -e \"s/Rebasing.*Successfully/Successfully/g\" -e \"s/^\\t//g\" \\\n+\t\t<err >err.trimmed &&\n+\ttest_cmp expect err.trimmed\n '\n \n test_expect_success 'respect user edits to update-ref steps' '\n@@ -1983,8 +1997,23 @@ test_expect_success '--update-refs: check failed ref update' '\n \t# the lock in the update-refs file.\n \tgit rev-parse third >.git/refs/heads/second &&\n \n-\tgit rebase --continue 2>err &&\n-\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tUpdated the following refs with --update-refs:\n+\t\trefs/heads/first\n+\t\trefs/heads/no-conflict-branch\n+\t\trefs/heads/third\n+\tFailed to update the following refs with --update-refs:\n+\t\trefs/heads/second\n+\tEOF\n+\n+\t# Clear \"Rebasing (X/Y)\" progress lines and drop leading tabs.\n+\ttail -n 6 err >err.last &&\n+\tsed -e \"s/Rebasing.*Successfully/Successfully/g\" -e \"s/^\\t//g\" \\\n+\t\t<err.last >err.trimmed &&\n+\ttest_cmp expect err.trimmed\n '\n \n # This must be the last test in this file\n-- \ngitgitgadget\n"},{"id":"459424","messageId":"2a6577974c7f72c075cb403b4b2573c6d1c7f033.1658255624.git.gitgitgadget@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"[PATCH v5 11/12] sequencer: ignore HEAD ref under --update-refs","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-07-19T18:33:43Z","receivedAt":"2022-07-19T18:34:22Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nWhen using the 'git rebase -i --update-refs' option, the todo list is\npopulated with 'update-ref' commands for all tip refs in the history\nthat is being rebased. Refs that are checked out by some worktree are\ninstead added as a comment to warn the user that they will not be\nupdated.\n\nUntil now, this included the HEAD ref, which is being updated by the\nrebase process itself, regardless of the --update-refs option. Remove\nthe comment in this case by ignoring any decorations that match the HEAD\nref.\n\nReported-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n sequencer.c | 13 +++++++++++++\n 1 file changed, 13 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 67812c0294f..1602649332b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5853,12 +5853,25 @@ static int add_decorations_to_list(const struct commit *commit,\n \t\t\t\t   struct todo_add_branch_context *ctx)\n {\n \tconst struct name_decoration *decoration = get_name_decoration(&commit->object);\n+\tconst char *head_ref = resolve_ref_unsafe(\"HEAD\",\n+\t\t\t\t\t\t  RESOLVE_REF_READING,\n+\t\t\t\t\t\t  NULL,\n+\t\t\t\t\t\t  NULL);\n \n \twhile (decoration) {\n \t\tstruct todo_item *item;\n \t\tconst char *path;\n \t\tsize_t base_offset = ctx->buf->len;\n \n+\t\t/*\n+\t\t * If the branch is the current HEAD, then it will be\n+\t\t * updated by the default rebase behavior.\n+\t\t */\n+\t\tif (head_ref && !strcmp(head_ref, decoration->name)) {\n+\t\t\tdecoration = decoration->next;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tALLOC_GROW(ctx->items,\n \t\t\tctx->items_nr + 1,\n \t\t\tctx->items_alloc);\n-- \ngitgitgadget\n\n"},{"id":"459567","messageId":"CABPp-BGKzcVatwoLhzXiJ3jkKXgWne+=5xNj+VmNO=pL5Kr1Og@mail.gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/12] rebase: update branches in multi-part topic","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-21T04:35:08Z","receivedAt":"2022-07-21T04:35:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 19, 2022 at 11:33 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> This series is based on ds/branch-checked-out.\n>\n> This is a feature I've wanted for quite a while. When working on the sparse\n> index topic, I created a long RFC that actually broke into three topics for\n> full review upstream. These topics were sequential, so any feedback on an\n> earlier one required updates to the later ones. I would work on the full\n> feature and use interactive rebase to update the full list of commits.\n> However, I would need to update the branches pointing to those sub-topics.\n>\n> This series adds a new --update-refs option to 'git rebase' (along with a\n> rebase.updateRefs config option) that adds 'update-ref' commands into the\n> TODO list. This is powered by the commit decoration machinery.\n>\n> As an example, here is my in-progress bundle URI RFC split into subtopics as\n> they appear during the TODO list of a git rebase -i --update-refs:\n>\n> pick 2d966282ff3 docs: document bundle URI standard\n> pick 31396e9171a remote-curl: add 'get' capability\n> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n> pick 6adaf842684 fetch: add --bundle-uri option\n> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n> update-ref refs/heads/bundle-redo/fetch\n>\n> pick 1e3f6546632 clone: add --bundle-uri option\n> pick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\n> update-ref refs/heads/bundle-redo/clone\n>\n> pick 5451cb6599c bundle-uri: create bundle_list struct and helpers\n> pick 3029c3aca15 bundle-uri: create base key-value pair parsing\n> pick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\n> pick 92625a47673 bundle-uri: unit test \"key=value\" parsing\n> pick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\n> pick 9d6809a8d53 bundle-uri: parse bundle list in config format\n> pick 287a732b54c bundle-uri: fetch a list of bundles\n> update-ref refs/heads/bundle-redo/list\n>\n> pick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\n> pick 520204dcd1c bundle-uri client: add minimal NOOP client\n> pick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\n> pick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\n> pick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\n> pick caf4599a81d bundle-uri: allow relative URLs in bundle lists\n> pick df255000b7e bundle-uri: download bundles from an advertised list\n> pick d71beabf199 clone: unbundle the advertised bundles\n> pick c9578391976 t5601: basic bundle URI tests\n> # Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n>\n> update-ref refs/heads/bundle-redo/advertise\n>\n>\n> Here is an outline of the series:\n>\n>  * Patch 1 updates some tests for branch_checked_out() to use 'git bisect'\n>    and 'git rebase' as black-boxes instead of manually editing files inside\n>    $GIT_DIR. (Thanks, Junio!)\n>  * Patch 2 updates some tests for branch_checked_out() for the 'apply'\n>    backend.\n>  * Patch 3 updates branch_checked_out() to parse the\n>    rebase-merge/update-refs file to block concurrent ref updates and\n>    checkouts on branches selected by --update-refs.\n>  * Patch 4 updates the todo list documentation to remove some unnecessary\n>    dots in the 'merge' command. This makes it consistent with the 'fixup'\n>    command before we document the 'update-ref' command.\n>  * Patch 5 updates the definition of todo_command_info to use enum values as\n>    array indices.\n>  * Patches 6-8 implement the --update-refs logic itself.\n>  * Patch 9 specifically updates the update-refs file every time the user\n>    edits the todo-list (Thanks Phillip!)\n>  * Patch 10 adds the rebase.updateRefs config option similar to\n>    rebase.autoSquash.\n>  * Patch 11 ignores the HEAD ref when creating the todo list instead of\n>    making a comment (Thanks Elijah!)\n>  * Patch 12 adds messaging to the end of the rebase stating which refs were\n>    updated (Thanks Elijah!)\n>\n> During review, we have identified some areas that would be good for\n> #leftoverbits:\n>\n>  * Warn the user when they add an 'update-ref ' command but is checked out\n>    in another worktree.\n>  * The checks in patch 9 are quadratic. They could be sped up using\n>    hashtables.\n>  * Consider whether we should include an 'update-ref ' command for the HEAD\n>    ref, so that all refs are updated in the same way. This might help\n>    confused users.\n\nNot necessarily so they are updated in the same way; the behind the\nscenes mechanism could perhaps still be different.  Just so that if\nthe user looks for the \"list of things being updated\" they don't get\nsurprised that HEAD is missing.\n\n>  * The error message for failed ref updates could include information on the\n>    commit IDs that would have been used. This can help the user fix the\n>    situation by updating the refs manually.\n>  * Modify the --update-refs option from a boolean to an\n>    optionally-string-parameter that specifies refspecs for the 'update-ref'\n>    commands.\n\nrefspecs?  Is that the term you really mean here?\n\n\n> Updates in v5\n> =============\n>\n>  * Rename 'wt_dir' to 'wt_git_dir' for clarity.\n>  * The documented behavior around 'fixup!' and 'squash!' commits was\n>    incorrect, so update the commit message, documentation, and test to\n>    demonstrate the actual behavior.\n>  * Use CALLOC_ARRAY() to be more idiomatic.\n>  * Be much more careful about propagating errors.\n>  * Commit message typo: \"We an\" to \"We can\"\n>  * Remove unnecessary null OID check when writing refs, since those would\n>    already be removed by a previous step.\n\nThanks, I've read over the range-diff and these changes look good to\nme.  One thing I'm curious about (sorry to bring this up so late):\n\"pick\" commands come with the old commit hash.  Perhaps the update-ref\ncommands should too?  (e.g. \"update-ref refs/heads/topic from\n<OLDHASH>\")\n"},{"id":"459616","messageId":"f0f94d74-b49b-e26a-d38c-77687bbbb153@github.com","threadId":"57949","inReplyTo":"CABPp-BGKzcVatwoLhzXiJ3jkKXgWne+=5xNj+VmNO=pL5Kr1Og@mail.gmail.com","subject":"Re: [PATCH v5 00/12] rebase: update branches in multi-part topic","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-21T12:12:57Z","receivedAt":"2022-07-21T12:13:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/21/2022 12:35 AM, Elijah Newren wrote:\n> On Tue, Jul 19, 2022 at 11:33 AM Derrick Stolee via GitGitGadget\n\n>> During review, we have identified some areas that would be good for\n>> #leftoverbits:\n>>\n>>  * Warn the user when they add an 'update-ref ' command but is checked out\n>>    in another worktree.\n>>  * The checks in patch 9 are quadratic. They could be sped up using\n>>    hashtables.\n>>  * Consider whether we should include an 'update-ref ' command for the HEAD\n>>    ref, so that all refs are updated in the same way. This might help\n>>    confused users.\n> \n> Not necessarily so they are updated in the same way; the behind the\n> scenes mechanism could perhaps still be different.  Just so that if\n> the user looks for the \"list of things being updated\" they don't get\n> surprised that HEAD is missing.\n\nNoted.\n \n>>  * The error message for failed ref updates could include information on the\n>>    commit IDs that would have been used. This can help the user fix the\n>>    situation by updating the refs manually.\n>>  * Modify the --update-refs option from a boolean to an\n>>    optionally-string-parameter that specifies refspecs for the 'update-ref'\n>>    commands.\n> \n> refspecs?  Is that the term you really mean here?\n\nProbably \"ref namespaces\" or \"ref prefixes\" would be better.\n\n>> Updates in v5\n>> =============\n>>\n>>  * Rename 'wt_dir' to 'wt_git_dir' for clarity.\n>>  * The documented behavior around 'fixup!' and 'squash!' commits was\n>>    incorrect, so update the commit message, documentation, and test to\n>>    demonstrate the actual behavior.\n>>  * Use CALLOC_ARRAY() to be more idiomatic.\n>>  * Be much more careful about propagating errors.\n>>  * Commit message typo: \"We an\" to \"We can\"\n>>  * Remove unnecessary null OID check when writing refs, since those would\n>>    already be removed by a previous step.\n> \n> Thanks, I've read over the range-diff and these changes look good to\n> me.  One thing I'm curious about (sorry to bring this up so late):\n> \"pick\" commands come with the old commit hash.  Perhaps the update-ref\n> commands should too?  (e.g. \"update-ref refs/heads/topic from\n> <OLDHASH>\")\n\nI don't personally see the value here other than to make it harder\nfor someone to add new commands (and confusing when wanting to\ncreate a brand new ref). We could consider adding a comment in the\nfuture without any backwards compatibility issues:\n\n\tupdate-ref refs/heads/my-ref # was 0123dead\n\nThanks,\n-Stolee\n"},{"id":"459623","messageId":"3ae0c41e-2d18-d496-9b99-a672bf274faa@gmail.com","threadId":"57949","inReplyTo":"e7a91bdffbd9a1fe0eda9a5a264f7734aadbf688.1658255624.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 08/12] rebase: update refs from 'update-ref' commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-21T14:03:57Z","receivedAt":"2022-07-21T14:04:05Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 19/07/2022 19:33, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> +static int do_update_ref(struct repository *r, const char *refname)\n>   {\n> +\tstruct string_list_item *item;\n> +\tstruct string_list list = STRING_LIST_INIT_DUP;\n> +\n> +\tif (sequencer_get_update_refs_state(r->gitdir, &list))\n> +\t\treturn -1;\n\nThis is good, we now return an error if we cannot read the update-refs file.\n\n> +\tfor_each_string_list_item(item, &list) {\n> +\t\tif (!strcmp(item->string, refname)) {\n> +\t\t\tstruct update_ref_record *rec = item->util;\n> +\t\t\tif (read_ref(\"HEAD\", &rec->after))\n> +\t\t\t\treturn -1;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\n> +\twrite_update_refs_state(&list);\n\nThe return value of write_update_refs_state() should be propagated to \nthe caller.\n\n> +\tstring_list_clear(&list, 1);\n>   \treturn 0;\n>   }\n\nBest Wishes\n\nPhillip\n"},{"id":"459624","messageId":"bb96c7a0-8da3-1be7-f1a6-a46ad673002e@gmail.com","threadId":"57949","inReplyTo":"95e2bbcedb1740f240a0a470822549d63075cc46.1658255624.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 09/12] sequencer: rewrite update-refs as user edits todo list","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-21T14:04:08Z","receivedAt":"2022-07-21T14:04:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 19/07/2022 19:33, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <derrickstolee@github.com>\n> \n> +void todo_list_filter_update_refs(struct repository *r,\n> +\t\t\t\t  struct todo_list *todo_list)\n> +{\n> +\tint i;\n> +\tint updated = 0;\n> +\tstruct string_list update_refs = STRING_LIST_INIT_DUP;\n> +\n> +\tsequencer_get_update_refs_state(r->gitdir, &update_refs);\n> +\n> +\t/*\n> +\t * For each item in the update_refs list, if it has no updated\n> +\t * value and does not appear in the todo_list, then remove it\n> +\t * from the update_refs list.\n> +\t */\n> +\tfor (i = 0; i < update_refs.nr; i++) {\n> +\t\tint j;\n> +\t\tint found = 0;\n> +\t\tconst char *ref = update_refs.items[i].string;\n> +\t\tsize_t reflen = strlen(ref);\n> +\t\tstruct update_ref_record *rec = update_refs.items[i].util;\n> +\n> +\t\t/* OID already stored as updated. */\n> +\t\tif (!is_null_oid(&rec->after))\n> +\t\t\tcontinue;\n> +\n> +\t\tfor (j = 0; !found && j < todo_list->total_nr; j++) {\n> +\t\t\tstruct todo_item *item = &todo_list->items[j];\n> +\t\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n> +\n> +\t\t\tif (item->command != TODO_UPDATE_REF)\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tif (item->arg_len != reflen ||\n> +\t\t\t    strncmp(arg, ref, reflen))\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tfound = 1;\n> +\t\t}\n> +\n> +\t\tif (!found) {\n> +\t\t\tfree(update_refs.items[i].string);\n> +\t\t\tfree(update_refs.items[i].util);\n> +\n> +\t\t\tupdate_refs.nr--;\n> +\t\t\tMOVE_ARRAY(update_refs.items + i, update_refs.items + i + 1, update_refs.nr - i);\n> +\n> +\t\t\tupdated = 1;\n> +\t\t\ti--;\n> +\t\t}\n> +\t}\n> +\n> +\t/*\n> +\t * For each todo_item, check if its ref is in the update_refs list.\n> +\t * If not, then add it as an un-updated ref.\n> +\t */\n> +\tfor (i = 0; i < todo_list->total_nr; i++) {\n> +\t\tstruct todo_item *item = &todo_list->items[i];\n> +\t\tconst char *arg = todo_list->buf.buf + item->arg_offset;\n> +\t\tint j, found = 0;\n> +\n> +\t\tif (item->command != TODO_UPDATE_REF)\n> +\t\t\tcontinue;\n> +\n> +\t\tfor (j = 0; !found && j < update_refs.nr; j++) {\n> +\t\t\tconst char *ref = update_refs.items[j].string;\n> +\n> +\t\t\tfound = strlen(ref) == item->arg_len &&\n> +\t\t\t\t!strncmp(ref, arg, item->arg_len);\n> +\t\t}\n> +\n> +\t\tif (!found) {\n> +\t\t\tstruct string_list_item *inserted;\n> +\t\t\tstruct strbuf argref = STRBUF_INIT;\n> +\n> +\t\t\tstrbuf_add(&argref, arg, item->arg_len);\n> +\t\t\tinserted = string_list_insert(&update_refs, argref.buf);\n> +\t\t\tinserted->util = init_update_ref_record(argref.buf);\n> +\t\t\tstrbuf_release(&argref);\n> +\t\t\tupdated = 1;\n> +\t\t}\n> +\t}\n> +\n> +\tif (updated)\n> +\t\twrite_update_refs_state(&update_refs);\n\nWe should return an error if write_update_refs_state() fails\n\nBest Wishes\n\nPhillip\n"},{"id":"459625","messageId":"96f742e9-3274-6b8c-b6b6-9d12624f42c5@gmail.com","threadId":"57949","inReplyTo":"pull.1247.v5.git.1658255624.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/12] rebase: update branches in multi-part topic","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-07-21T14:04:17Z","receivedAt":"2022-07-21T14:04:24Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nI'm afraid I've only had time to have a quick look at patches 8 & 9. The \nerror propagation is looking better but I did notice a couple of calls \nto write_update_refs_state() where we don't propagate the return value.\n\nBest Wishes\n\nPhillip\n\nOn 19/07/2022 19:33, Derrick Stolee via GitGitGadget wrote:\n> This series is based on ds/branch-checked-out.\n> \n> This is a feature I've wanted for quite a while. When working on the sparse\n> index topic, I created a long RFC that actually broke into three topics for\n> full review upstream. These topics were sequential, so any feedback on an\n> earlier one required updates to the later ones. I would work on the full\n> feature and use interactive rebase to update the full list of commits.\n> However, I would need to update the branches pointing to those sub-topics.\n> \n> This series adds a new --update-refs option to 'git rebase' (along with a\n> rebase.updateRefs config option) that adds 'update-ref' commands into the\n> TODO list. This is powered by the commit decoration machinery.\n> \n> As an example, here is my in-progress bundle URI RFC split into subtopics as\n> they appear during the TODO list of a git rebase -i --update-refs:\n> \n> pick 2d966282ff3 docs: document bundle URI standard\n> pick 31396e9171a remote-curl: add 'get' capability\n> pick 54c6ab70f67 bundle-uri: create basic file-copy logic\n> pick 96cb2e35af1 bundle-uri: add support for http(s):// and file://\n> pick 6adaf842684 fetch: add --bundle-uri option\n> pick 6c5840ed77e fetch: add 'refs/bundle/' to log.excludeDecoration\n> update-ref refs/heads/bundle-redo/fetch\n> \n> pick 1e3f6546632 clone: add --bundle-uri option\n> pick 9e4a6fe9b68 clone: --bundle-uri cannot be combined with --depth\n> update-ref refs/heads/bundle-redo/clone\n> \n> pick 5451cb6599c bundle-uri: create bundle_list struct and helpers\n> pick 3029c3aca15 bundle-uri: create base key-value pair parsing\n> pick a8b2de79ce8 bundle-uri: create \"key=value\" line parsing\n> pick 92625a47673 bundle-uri: unit test \"key=value\" parsing\n> pick a8616af4dc2 bundle-uri: limit recursion depth for bundle lists\n> pick 9d6809a8d53 bundle-uri: parse bundle list in config format\n> pick 287a732b54c bundle-uri: fetch a list of bundles\n> update-ref refs/heads/bundle-redo/list\n> \n> pick b09f8226185 protocol v2: add server-side \"bundle-uri\" skeleton\n> pick 520204dcd1c bundle-uri client: add minimal NOOP client\n> pick 62e8b457b48 bundle-uri client: add \"git ls-remote-bundle-uri\"\n> pick 00eae925043 bundle-uri: serve URI advertisement from bundle.* config\n> pick 4277440a250 bundle-uri client: add boolean transfer.bundleURI setting\n> pick caf4599a81d bundle-uri: allow relative URLs in bundle lists\n> pick df255000b7e bundle-uri: download bundles from an advertised list\n> pick d71beabf199 clone: unbundle the advertised bundles\n> pick c9578391976 t5601: basic bundle URI tests\n> # Ref refs/heads/bundle-redo/rfc-3 checked out at '/home/stolee/_git/git-bundles'\n> \n> update-ref refs/heads/bundle-redo/advertise\n> \n> \n> Here is an outline of the series:\n> \n>   * Patch 1 updates some tests for branch_checked_out() to use 'git bisect'\n>     and 'git rebase' as black-boxes instead of manually editing files inside\n>     $GIT_DIR. (Thanks, Junio!)\n>   * Patch 2 updates some tests for branch_checked_out() for the 'apply'\n>     backend.\n>   * Patch 3 updates branch_checked_out() to parse the\n>     rebase-merge/update-refs file to block concurrent ref updates and\n>     checkouts on branches selected by --update-refs.\n>   * Patch 4 updates the todo list documentation to remove some unnecessary\n>     dots in the 'merge' command. This makes it consistent with the 'fixup'\n>     command before we document the 'update-ref' command.\n>   * Patch 5 updates the definition of todo_command_info to use enum values as\n>     array indices.\n>   * Patches 6-8 implement the --update-refs logic itself.\n>   * Patch 9 specifically updates the update-refs file every time the user\n>     edits the todo-list (Thanks Phillip!)\n>   * Patch 10 adds the rebase.updateRefs config option similar to\n>     rebase.autoSquash.\n>   * Patch 11 ignores the HEAD ref when creating the todo list instead of\n>     making a comment (Thanks Elijah!)\n>   * Patch 12 adds messaging to the end of the rebase stating which refs were\n>     updated (Thanks Elijah!)\n> \n> During review, we have identified some areas that would be good for\n> #leftoverbits:\n> \n>   * Warn the user when they add an 'update-ref ' command but is checked out\n>     in another worktree.\n>   * The checks in patch 9 are quadratic. They could be sped up using\n>     hashtables.\n>   * Consider whether we should include an 'update-ref ' command for the HEAD\n>     ref, so that all refs are updated in the same way. This might help\n>     confused users.\n>   * The error message for failed ref updates could include information on the\n>     commit IDs that would have been used. This can help the user fix the\n>     situation by updating the refs manually.\n>   * Modify the --update-refs option from a boolean to an\n>     optionally-string-parameter that specifies refspecs for the 'update-ref'\n>     commands.\n> \n> \n> Updates in v5\n> =============\n> \n>   * Rename 'wt_dir' to 'wt_git_dir' for clarity.\n>   * The documented behavior around 'fixup!' and 'squash!' commits was\n>     incorrect, so update the commit message, documentation, and test to\n>     demonstrate the actual behavior.\n>   * Use CALLOC_ARRAY() to be more idiomatic.\n>   * Be much more careful about propagating errors.\n>   * Commit message typo: \"We an\" to \"We can\"\n>   * Remove unnecessary null OID check when writing refs, since those would\n>     already be removed by a previous step.\n> \n> \n> Updates in v4\n> =============\n> \n> This version took longer than I'd hoped (I had less time to work on it than\n> anticipated) but it also has some major updates. These major updates are\n> direct responses to the significant review this series has received. Thank\n> you!\n> \n>   * The update-refs file now stores \"ref/before/after\" triples (still\n>     separated by lines). This allows us to store the \"before\" OID of a ref in\n>     addition to the \"after\" that we will write to that ref at the end of the\n>     rebase. This allows us to do a \"force-with-lease\" update. The\n>     branch_checked_out() updates should prevent Git from updating those refs\n>     while under the rebase, but older versions and third-party tools don't\n>     have that protection.\n>   * The update-refs file is updated with every update to the todo-list file.\n>     This allows for some advanced changes to the file, including removing,\n>     adding, and duplicating 'update-ref' commands.\n>   * The message at the end of the rebase process now lists which refs were\n>     updated with the update-ref steps. This includes any ref updates that\n>     fail.\n>   * The branch_checked_out() tests now use 'git bisect' and 'git rebase' as\n>     black-boxes instead of testing their internals directly.\n> \n> Here are the more minor updates:\n> \n>   * Dropped an unnecessary stat() call.\n>   * Updated commit messages to include extra details, based on confusion in\n>     last round.\n>   * The HEAD branch no longer appears as a comment line in the initial todo\n>     list.\n>   * The update-refs file is now written using a lockfile.\n>   * Tests now use test_cmp_rev.\n>   * A memory leak ('path' variable) is resolved.\n> \n> \n> Updates in v3\n> =============\n> \n>   * The branch_checked_out() API was extracted to its own topic and is now\n>     the ds/branch-checked-out branch. This series is now based on that one.\n>   * The for_each_decoration() API was removed, since it became trivial once\n>     it did not take a commit directly.\n>   * The branch_checked_out() tests did not verify the rebase-apply data (for\n>     the apply backend), so that is fixed.\n>   * Instead of using the 'label' command and a final 'update-refs' command in\n>     the todo list, use a new 'update-ref ' command. This command updates the\n>     rebase-merge/update-refs file with the OID of HEAD at these steps. At the\n>     very end of the rebase sequence, those refs are updated to the stored OID\n>     values (assuming that they were not removed by the user, in which case we\n>     notice that the OID is the null OID and we do nothing).\n>   * New tests are added.\n>   * The todo-list comment documentation has some new formatting updates, but\n>     also includes a description of 'update-refs' in this version.\n> \n> \n> Updates in v2\n> =============\n> \n> As recommended by the excellent feedback, I have removed the 'exec' commands\n> in favor of the 'label' commands and a new 'update-refs' command at the very\n> end. This way, there is only one step that updates all of the refs at the\n> end instead of updating refs during the rebase. If a user runs 'git rebase\n> --abort' in the middle, then their refs are still where they need to be.\n> \n> Based on some of the discussion, it seemed like one way to do this would be\n> to have an 'update-ref ' command that would take the place of these 'label'\n> commands. However, this would require two things that make it a bit awkward:\n> \n>   1. We would need to replicate the storage of those positions during the\n>      rebase. 'label' already does this pretty well. I've added the\n>      \"for-update-refs/\" label to help here.\n>   2. If we want to close out all of the refs as the rebase is finishing, then\n>      that \"step\" becomes invisible to the user (and a bit more complicated to\n>      insert). Thus, the 'update-refs' step performs this action. If the user\n>      wants to do things after that step, then they can do so by editing the\n>      TODO list.\n> \n> Other updates:\n> \n>   * The 'keep_decorations' parameter was renamed to 'update_refs'.\n>   * I added tests for --rebase-merges=rebase-cousins to show how these labels\n>     interact with other labels and merge commands.\n>   * I changed the order of the insertion of these update-refs labels to be\n>     before the fixups are rearranged. This fixes a bug where the tip commit\n>     is a fixup! so its decorations are never inspected (and they would be in\n>     the wrong place even if they were). The fixup! commands are properly\n>     inserted between a pick and its following label command. Tests\n>     demonstrate this is correct.\n>   * Numerous style choices are updated based on feedback.\n> \n> Thank you for all of the detailed review and ideas in this space. I\n> appreciate any more ideas that can make this feature as effective as it can\n> be.\n> \n> Thanks, -Stolee\n> \n> Derrick Stolee (12):\n>    t2407: test bisect and rebase as black-boxes\n>    t2407: test branches currently using apply backend\n>    branch: consider refs under 'update-refs'\n>    rebase-interactive: update 'merge' description\n>    sequencer: define array with enum values\n>    sequencer: add update-ref command\n>    rebase: add --update-refs option\n>    rebase: update refs from 'update-ref' commands\n>    sequencer: rewrite update-refs as user edits todo list\n>    rebase: add rebase.updateRefs config option\n>    sequencer: ignore HEAD ref under --update-refs\n>    sequencer: notify user of --update-refs activity\n> \n>   Documentation/config/rebase.txt |   3 +\n>   Documentation/git-rebase.txt    |  10 +\n>   branch.c                        |  13 +\n>   builtin/rebase.c                |  10 +\n>   rebase-interactive.c            |  15 +-\n>   sequencer.c                     | 474 +++++++++++++++++++++++++++++++-\n>   sequencer.h                     |  23 ++\n>   t/lib-rebase.sh                 |  15 +\n>   t/t2407-worktree-heads.sh       | 103 +++++--\n>   t/t3404-rebase-interactive.sh   | 273 ++++++++++++++++++\n>   10 files changed, 895 insertions(+), 44 deletions(-)\n> \n> \n> base-commit: 9bef0b1e6ec371e786c2fba3edcc06ad040a536c\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1247%2Fderrickstolee%2Frebase-keep-decorations-v5\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1247/derrickstolee/rebase-keep-decorations-v5\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1247\n> \n> Range-diff vs v4:\n> \n>    1:  9e53a27017a =  1:  9e53a27017a t2407: test bisect and rebase as black-boxes\n>    2:  540a3be256f =  2:  540a3be256f t2407: test branches currently using apply backend\n>    3:  bf301a054e3 !  3:  1089a0edb73 branch: consider refs under 'update-refs'\n>       @@ sequencer.c: static GIT_PATH_FUNC(rebase_path_squash_onto, \"rebase-merge/squash-\n>        + * rebase_path_update_refs() returns the path to this file for a given\n>        + * worktree directory. For the current worktree, pass the_repository->gitdir.\n>        + */\n>       -+static char *rebase_path_update_refs(const char *wt_dir)\n>       ++static char *rebase_path_update_refs(const char *wt_git_dir)\n>        +{\n>       -+\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_dir);\n>       ++\treturn xstrfmt(\"%s/rebase-merge/update-refs\", wt_git_dir);\n>        +}\n>        +\n>         /*\n>    4:  dec95681d2b =  4:  d1cce4f06aa rebase-interactive: update 'merge' description\n>    5:  b2c09600918 =  5:  4c086d477f0 sequencer: define array with enum values\n>    6:  fa7ecb718cf =  6:  7b3d6601960 sequencer: add update-ref command\n>    7:  3ec2cc922f9 !  7:  7efb55e4f14 rebase: add --update-refs option\n>       @@ Commit message\n>            --fixup commit at the tip of the feature to apply correctly to the sub\n>            branch, even if it is fixing up the most-recent commit in that part.\n>        \n>       -    One potential problem here is that refs decorating commits that are\n>       -    already marked as \"fixup!\" or \"squash!\" will not be included in this\n>       -    list. Generally, the reordering of the \"fixup!\" and \"squash!\" is likely\n>       -    to change the relative order of these refs, so it is not recommended.\n>       -    The workflow here is intended to allow these kinds of commits at the tip\n>       -    of the rebased branch while the other sub branches come along for the\n>       -    ride without intervention.\n>       -\n>            This change update the documentation and builtin to accept the\n>            --update-refs option as well as updating the todo file with the\n>            'update-ref' commands. Tests are added to ensure that these todo\n>       @@ Documentation/git-rebase.txt: provided. Otherwise an explicit `--no-reschedule-f\n>        +--no-update-refs::\n>        +\tAutomatically force-update any branches that point to commits that\n>        +\tare being rebased. Any branches that are checked out in a worktree\n>       -+\tor point to a `squash! ...` or `fixup! ...` commit are not updated\n>       -+\tin this way.\n>       ++\tare not updated in this way.\n>        +\n>         INCOMPATIBLE OPTIONS\n>         --------------------\n>       @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n>         \t\t\t N_(\"move commits that begin with \"\n>         \t\t\t    \"squash!/fixup! under -i\")),\n>        +\t\tOPT_BOOL(0, \"update-refs\", &options.update_refs,\n>       -+\t\t\t N_(\"update local refs that point to commits \"\n>       ++\t\t\t N_(\"update branches that point to commits \"\n>        +\t\t\t    \"that are being rebased\")),\n>         \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &gpg_sign, N_(\"key-id\"),\n>         \t\t\tN_(\"GPG-sign commits\"),\n>       @@ t/t3404-rebase-interactive.sh: test_expect_success 'ORIG_HEAD is updated correct\n>        +\tgit branch -f second HEAD~3 &&\n>        +\tgit branch -f third HEAD~1 &&\n>        +\tgit commit --allow-empty --fixup=third &&\n>       ++\tgit branch -f is-not-reordered &&\n>       ++\tgit commit --allow-empty --fixup=HEAD~4 &&\n>        +\tgit branch -f shared-tip &&\n>        +\t(\n>        +\t\tset_cat_todo_editor &&\n>        +\n>        +\t\tcat >expect <<-EOF &&\n>        +\t\tpick $(git log -1 --format=%h J) J\n>       ++\t\tfixup $(git log -1 --format=%h update-refs) fixup! J # empty\n>        +\t\tupdate-ref refs/heads/second\n>        +\t\tupdate-ref refs/heads/first\n>        +\t\tpick $(git log -1 --format=%h K) K\n>        +\t\tpick $(git log -1 --format=%h L) L\n>       -+\t\tfixup $(git log -1 --format=%h update-refs) fixup! L # empty\n>       ++\t\tfixup $(git log -1 --format=%h is-not-reordered) fixup! L # empty\n>        +\t\tupdate-ref refs/heads/third\n>        +\t\tpick $(git log -1 --format=%h M) M\n>        +\t\tupdate-ref refs/heads/no-conflict-branch\n>       ++\t\tupdate-ref refs/heads/is-not-reordered\n>        +\t\tupdate-ref refs/heads/shared-tip\n>        +\t\tEOF\n>        +\n>    8:  fb5f64c5201 !  8:  e7a91bdffbd rebase: update refs from 'update-ref' commands\n>       @@ sequencer.c: struct update_ref_record {\n>         \n>        +static struct update_ref_record *init_update_ref_record(const char *ref)\n>        +{\n>       -+\tstruct update_ref_record *rec = xmalloc(sizeof(*rec));\n>       ++\tstruct update_ref_record *rec;\n>       ++\n>       ++\tCALLOC_ARRAY(rec, 1);\n>        +\n>        +\toidcpy(&rec->before, null_oid());\n>        +\toidcpy(&rec->after, null_oid());\n>       @@ sequencer.c: leave_merge:\n>         \n>        -static int do_update_ref(struct repository *r, const char *ref_name)\n>        +static int write_update_refs_state(struct string_list *refs_to_oids)\n>       - {\n>       ++{\n>        +\tint result = 0;\n>        +\tstruct lock_file lock = LOCK_INIT;\n>        +\tFILE *fp = NULL;\n>       @@ sequencer.c: leave_merge:\n>        +}\n>        +\n>        +static int do_update_ref(struct repository *r, const char *refname)\n>       -+{\n>       + {\n>        +\tstruct string_list_item *item;\n>        +\tstruct string_list list = STRING_LIST_INIT_DUP;\n>        +\n>       -+\tsequencer_get_update_refs_state(r->gitdir, &list);\n>       ++\tif (sequencer_get_update_refs_state(r->gitdir, &list))\n>       ++\t\treturn -1;\n>        +\n>        +\tfor_each_string_list_item(item, &list) {\n>        +\t\tif (!strcmp(item->string, refname)) {\n>        +\t\t\tstruct update_ref_record *rec = item->util;\n>       -+\t\t\tread_ref(\"HEAD\", &rec->after);\n>       ++\t\t\tif (read_ref(\"HEAD\", &rec->after))\n>       ++\t\t\t\treturn -1;\n>        +\t\t\tbreak;\n>        +\t\t}\n>        +\t}\n>       @@ sequencer.c: leave_merge:\n>        +\tstruct string_list refs_to_oids = STRING_LIST_INIT_DUP;\n>        +\tstruct ref_store *refs = get_main_ref_store(r);\n>        +\n>       -+\tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n>       ++\tif ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))\n>       ++\t\treturn res;\n>        +\n>        +\tfor_each_string_list_item(item, &refs_to_oids) {\n>        +\t\tstruct update_ref_record *rec = item->util;\n>        +\n>       -+\t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n>       -+\t\t\t/*\n>       -+\t\t\t * Ref was not updated. User may have deleted the\n>       -+\t\t\t * 'update-ref' step.\n>       -+\t\t\t */\n>       -+\t\t\tcontinue;\n>       -+\t\t}\n>       -+\n>        +\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n>        +\t\t\t\t       item->string,\n>        +\t\t\t\t       &rec->after, &rec->before,\n>       @@ sequencer.c: leave_merge:\n>         {\n>         \tint i = todo_list->current;\n>        @@ sequencer.c: cleanup_head_ref:\n>       +\n>       + \t\tstrbuf_release(&buf);\n>         \t\tstrbuf_release(&head_ref);\n>       ++\n>       ++\t\tif (do_update_refs(r))\n>       ++\t\t\treturn -1;\n>         \t}\n>         \n>       -+\tdo_update_refs(r);\n>       -+\n>         \t/*\n>       - \t * Sequence of picks finished successfully; cleanup by\n>       - \t * removing the .git/sequencer directory\n>        @@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n>         \n>         \t\t\tsti = string_list_insert(&ctx->refs_to_oids,\n>       @@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n>         \t\t}\n>         \n>         \t\titem->offset_in_buf = base_offset;\n>       +@@ sequencer.c: static int add_decorations_to_list(const struct commit *commit,\n>       +  */\n>       + static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n>       + {\n>       +-\tint i;\n>       ++\tint i, res;\n>       + \tstatic struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;\n>       + \tstatic struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;\n>       + \tstatic struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;\n>        @@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo_list)\n>         \t\t}\n>         \t}\n>         \n>       -+\twrite_update_refs_state(&ctx.refs_to_oids);\n>       ++\tres = write_update_refs_state(&ctx.refs_to_oids);\n>        +\n>         \tstring_list_clear(&ctx.refs_to_oids, 1);\n>       ++\n>       ++\tif (res) {\n>       ++\t\t/* we failed, so clean up the new list. */\n>       ++\t\tfree(ctx.items);\n>       ++\t\treturn res;\n>       ++\t}\n>       ++\n>         \tfree(todo_list->items);\n>         \ttodo_list->items = ctx.items;\n>       + \ttodo_list->nr = ctx.items_nr;\n>        \n>         ## t/t2407-worktree-heads.sh ##\n>        @@ t/t2407-worktree-heads.sh: test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer\n>    9:  29c7c76805a !  9:  95e2bbcedb1 sequencer: rewrite update-refs as user edits todo list\n>       @@ Commit message\n>        \n>            We can test that this works by rewriting the todo-list several times in\n>            the course of a rebase. Check that each ref is locked or unlocked for\n>       -    updates after each todo-list update. We an also verify that the ref\n>       +    updates after each todo-list update. We can also verify that the ref\n>            update fails if a concurrent process updates one of the refs after the\n>            rebase process records the \"locked\" ref location.\n>        \n>   10:  c0022d07579 ! 10:  a73b02568f3 rebase: add rebase.updateRefs config option\n>       @@ Documentation/config/rebase.txt: rebase.autoStash::\n>        \n>         ## Documentation/git-rebase.txt ##\n>        @@ Documentation/git-rebase.txt: start would be overridden by the presence of\n>       + \tAutomatically force-update any branches that point to commits that\n>         \tare being rebased. Any branches that are checked out in a worktree\n>       - \tor point to a `squash! ...` or `fixup! ...` commit are not updated\n>       - \tin this way.\n>       + \tare not updated in this way.\n>        ++\n>        +If the configuration variable `rebase.updateRefs` is set, then this option\n>        +can be used to override and disable this setting.\n>   11:  d53b4ff2cee = 11:  2a6577974c7 sequencer: ignore HEAD ref under --update-refs\n>   12:  d5cd4b49e46 ! 12:  ec080ce1e90 sequencer: notify user of --update-refs activity\n>       @@ sequencer.c: static int do_update_ref(struct repository *r, const char *refname)\n>        +\tstruct strbuf update_msg = STRBUF_INIT;\n>        +\tstruct strbuf error_msg = STRBUF_INIT;\n>         \n>       - \tsequencer_get_update_refs_state(r->gitdir, &refs_to_oids);\n>       + \tif ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))\n>       + \t\treturn res;\n>         \n>         \tfor_each_string_list_item(item, &refs_to_oids) {\n>         \t\tstruct update_ref_record *rec = item->util;\n>        +\t\tint loop_res;\n>         \n>       - \t\tif (oideq(&rec->after, the_hash_algo->null_oid)) {\n>       - \t\t\t/*\n>       -@@ sequencer.c: static int do_update_refs(struct repository *r)\n>       - \t\t\tcontinue;\n>       - \t\t}\n>       -\n>        -\t\tres |= refs_update_ref(refs, \"rewritten during rebase\",\n>        -\t\t\t\t       item->string,\n>        -\t\t\t\t       &rec->after, &rec->before,\n>       @@ sequencer.c: static int do_update_refs(struct repository *r)\n>         }\n>         \n>        @@ sequencer.c: cleanup_head_ref:\n>       + \t\tstrbuf_release(&buf);\n>         \t\tstrbuf_release(&head_ref);\n>       - \t}\n>         \n>       --\tdo_update_refs(r);\n>       -+\tdo_update_refs(r, opts->quiet);\n>       +-\t\tif (do_update_refs(r))\n>       ++\t\tif (do_update_refs(r, opts->quiet))\n>       + \t\t\treturn -1;\n>       + \t}\n>         \n>       - \t/*\n>       - \t * Sequence of picks finished successfully; cleanup by\n>        \n>         ## t/t3404-rebase-interactive.sh ##\n>        @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs updates refs correctly' '\n>       @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs updates refs c\n>         \n>         test_expect_success 'respect user edits to update-ref steps' '\n>        @@ t/t3404-rebase-interactive.sh: test_expect_success '--update-refs: check failed ref update' '\n>       + \t# the lock in the update-refs file.\n>         \tgit rev-parse third >.git/refs/heads/second &&\n>         \n>       - \tgit rebase --continue 2>err &&\n>       +-\tgit rebase --continue 2>err &&\n>        -\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err\n>       ++\ttest_must_fail git rebase --continue 2>err &&\n>        +\tgrep \"update_ref failed for ref '\\''refs/heads/second'\\''\" err &&\n>        +\n>        +\tcat >expect <<-\\EOF &&\n> \n"},{"id":"459715","messageId":"CABPp-BEaX06dGC8YqjRPwnzpRcMorZsk=TCqCsWV4=CFgtvGng@mail.gmail.com","threadId":"57949","inReplyTo":"f0f94d74-b49b-e26a-d38c-77687bbbb153@github.com","subject":"Re: [PATCH v5 00/12] rebase: update branches in multi-part topic","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-21T19:43:02Z","receivedAt":"2022-07-21T19:43:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 21, 2022 at 5:13 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 7/21/2022 12:35 AM, Elijah Newren wrote:\n>\n> > One thing I'm curious about (sorry to bring this up so late):\n> > \"pick\" commands come with the old commit hash.  Perhaps the update-ref\n> > commands should too?  (e.g. \"update-ref refs/heads/topic from\n> > <OLDHASH>\")\n>\n> I don't personally see the value here other than to make it harder\n> for someone to add new commands (and confusing when wanting to\n> create a brand new ref).\n\nI apologize if my late query caused frustration.  I was thinking it\nmade more sense to ask now than later and was genuinely curious for\nthoughts on the idea, but perhaps it'd have been better if I just\ndidn't bring it up.  Sorry.\n\nBut, if you're curious why I was thinking about it...\n\n\nThere are four ways I can think of to handle the <oldvalue>: (A)\ninclude it in the \"update-ref\" instruction, (B) manually get the\n<oldvalue> at program invocation, (C) accept an increased race-window\nby not grabbing <oldvalue> until the line is parsed, or (D) ignore\nraces and just don't provide <oldvalue> when updating refs.  If one\nchooses (B), they can either (B1) pre-parse the entire todo list and\nlook up the current values of any refs mentioned in an update-ref\ninstruction, or (B2) record the value of all existing refs at\ninvocation.\n\nRebase is already paying the cost for pre-parsing the entire todo list\n(i.e. B1).  In fact, you _also_ preemptively locking the refs\nmentioned.  So, in a sense, rebase is already covered. However:\n  * adding such a thing could potentially remove a small race window\n(perhaps the user takes a while to edit the todo list, and the ref\nsomehow gets updated before they even finish that step; being able to\nspecify the oldref might help in such a case).\n  * existing commands (pick, merge, etc.) give the oldrefs (or labels\nto them), so having update-ref also do so might provide some \"feel\ngood\" consistency.\n\nPerhaps those reasons are pretty weak for the rebase case, given you\nalready do B1.  However, `git replay` isn't going to do B1, and nor\nwill it preemptively lock refs.  I see `git replay` as a patch-based\nanalogue to the snapshot-based tools of fast-export and fast-import.\n(fast-import doesn't pre-parse its entire input before starting to\nprocess the first commit, nor does it preemptively lock refs.)  B2 is\nnot appealing because some repositories have gargantuan numbers of\nrefs.  Also, the update-ref-during-edit-of-todo rationale is more\nimportant for `git replay`, because I want `git replay` to accept a\nfilename containing a todo list in lieu of a range of revisions to\nreplay -- perhaps users spent quite some time or programming effort\ngenerating their todo list and it took some time to generate/review.\nAnd that further undercuts both B1 and B2.  So, between all those\nreasons, (A) is very appealing to me.\n\nAll that said, `git replay` already has other planned differences in\nits todo list (e.g. a \"reset\" directive must be the first non-comment\ndirective, there's no separate \"merge\" directive, etc.), so if I have\nto add another one, it's no big deal.\n\nYour point about specifying <oldref> being seen as friction for some\nusers is well taken, and I'm thinking of making the \"from <oldref>\"\npart of the instruction optional (though documenting that users are\naccepting some race risk by switching from (A) to (C) when they omit\nit).\n\n> We could consider adding a comment in the\n> future without any backwards compatibility issues:\n>\n>         update-ref refs/heads/my-ref # was 0123dead\n\nYes, this could certainly be added later (though none of my reasons\nfor wanting <oldref> were geared around pointers for the end user, so\nthis form wouldn't be helpful to me).\n\nAlso, if \"from <oldref>\" is optional, it could also be added later.\n\n\nIf you've read this far, let me just take a minute to again thank you\nfor your work on this --update-refs option to rebase.  It's some cool\nwork.  Also, thanks for listening to my random ideas and musings along\nthe way.  :-)\n"},{"id":"459720","messageId":"e18ff601-78e4-5dcf-e149-247ca2c7a05b@github.com","threadId":"57949","inReplyTo":"CABPp-BEaX06dGC8YqjRPwnzpRcMorZsk=TCqCsWV4=CFgtvGng@mail.gmail.com","subject":"Re: [PATCH v5 00/12] rebase: update branches in multi-part topic","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-07-21T20:05:00Z","receivedAt":"2022-07-21T20:05:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/21/2022 3:43 PM, Elijah Newren wrote:\n> On Thu, Jul 21, 2022 at 5:13 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>>\n>> On 7/21/2022 12:35 AM, Elijah Newren wrote:\n>>\n>>> One thing I'm curious about (sorry to bring this up so late):\n>>> \"pick\" commands come with the old commit hash.  Perhaps the update-ref\n>>> commands should too?  (e.g. \"update-ref refs/heads/topic from\n>>> <OLDHASH>\")\n>>\n>> I don't personally see the value here other than to make it harder\n>> for someone to add new commands (and confusing when wanting to\n>> create a brand new ref).\n> \n> I apologize if my late query caused frustration. \n\nI intended this as \"I don't understand, please enlighten me\", but\nlooking back now it definitely sounds more frustrated. Sorry about\nthat.\n\n> I was thinking it\n> made more sense to ask now than later and was genuinely curious for\n> thoughts on the idea, but perhaps it'd have been better if I just\n> didn't bring it up.  Sorry.\n \nIt's definitely the time to bring it up, because it will be harder\nto change it once it's shipped.\n\n> But, if you're curious why I was thinking about it...\n> \n> \n> There are four ways I can think of to handle the <oldvalue>: (A)\n> include it in the \"update-ref\" instruction, (B) manually get the\n> <oldvalue> at program invocation, (C) accept an increased race-window\n> by not grabbing <oldvalue> until the line is parsed, or (D) ignore\n> races and just don't provide <oldvalue> when updating refs.  If one\n> chooses (B), they can either (B1) pre-parse the entire todo list and\n> look up the current values of any refs mentioned in an update-ref\n> instruction, or (B2) record the value of all existing refs at\n> invocation.\n> \n> Rebase is already paying the cost for pre-parsing the entire todo list\n> (i.e. B1).  In fact, you _also_ preemptively locking the refs\n> mentioned.  So, in a sense, rebase is already covered. However:\n>   * adding such a thing could potentially remove a small race window\n> (perhaps the user takes a while to edit the todo list, and the ref\n> somehow gets updated before they even finish that step; being able to\n> specify the oldref might help in such a case).\n>   * existing commands (pick, merge, etc.) give the oldrefs (or labels\n> to them), so having update-ref also do so might provide some \"feel\n> good\" consistency.\n> \n> Perhaps those reasons are pretty weak for the rebase case, given you\n> already do B1.  However, `git replay` isn't going to do B1, and nor\n> will it preemptively lock refs.  I see `git replay` as a patch-based\n> analogue to the snapshot-based tools of fast-export and fast-import.\n> (fast-import doesn't pre-parse its entire input before starting to\n> process the first commit, nor does it preemptively lock refs.)  B2 is\n> not appealing because some repositories have gargantuan numbers of\n> refs.  Also, the update-ref-during-edit-of-todo rationale is more\n> important for `git replay`, because I want `git replay` to accept a\n> filename containing a todo list in lieu of a range of revisions to\n> replay -- perhaps users spent quite some time or programming effort\n> generating their todo list and it took some time to generate/review.\n> And that further undercuts both B1 and B2.  So, between all those\n> reasons, (A) is very appealing to me.\n> \n> All that said, `git replay` already has other planned differences in\n> its todo list (e.g. a \"reset\" directive must be the first non-comment\n> directive, there's no separate \"merge\" directive, etc.), so if I have\n> to add another one, it's no big deal.\n\nI'm happy that you're working on 'git replay'. I'm excited to see how\nit works. Also, it would be helpful to keep these things in line as\nmuch as possible.\n\n> Your point about specifying <oldref> being seen as friction for some\n> users is well taken, and I'm thinking of making the \"from <oldref>\"\n> part of the instruction optional (though documenting that users are\n> accepting some race risk by switching from (A) to (C) when they omit\n> it).\n>\n>> We could consider adding a comment in the\n>> future without any backwards compatibility issues:\n>>\n>>         update-ref refs/heads/my-ref # was 0123dead\n> \n> Yes, this could certainly be added later (though none of my reasons\n> for wanting <oldref> were geared around pointers for the end user, so\n> this form wouldn't be helpful to me).\n> \n> Also, if \"from <oldref>\" is optional, it could also be added later.\n\nThe optional value makes this something that we can add later, which\nI think is good flexibility. I was thinking about doing the comment\nthing as a follow-up (in addition to Phillip's error mode comments)\nbut maybe I'll explore this direction instead.\n \n> If you've read this far, let me just take a minute to again thank you\n> for your work on this --update-refs option to rebase.  It's some cool\n> work.  Also, thanks for listening to my random ideas and musings along\n> the way.  :-)\n\nI really appreciate the attention you've given it. It's already\nimproved a lot due to your help.\n\nThanks,\n-Stolee\n"}]}