{"thread":{"id":"57437","subject":"[PATCH 0/3] libify reflog","startedAt":"2022-02-18T18:40:53Z","lastAt":"2022-03-08T18:10:03Z","messageCount":63,"participants":["John Cai via GitGitGadget","Ævar Arnfjörð Bjarmason","Taylor Blau","Junio C Hamano","John Cai","Phillip Wood"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"448783","messageId":"pull.1218.git.git.1645209647.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":null,"subject":"[PATCH 0/3] libify reflog","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-18T18:40:44Z","receivedAt":"2022-02-18T18:40:53Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"In [1], there was a discussion around a bug report of stash not recovering\nin the middle of the process. It turned out to not be a bug we need to fix.\nHowever, out of that discussion came the idea of libifying reflog. This can\nstand alone as a code improvement.\n\nstash.c currently shells out to call reflog to delete reflogs. Libify reflog\ndelete and call it from both builtin/reflog.c and builtin/stash.c.\n\nThis patch has three parts:\n\n * libify reflog's delete functionality and move some of the helpers into a\n   reflog.c library and export them\n * call reflog_delete from builtin/reflog.c\n * call reflog_delete from builtin/stash.c\n\n 1. https://lore.kernel.org/git/220126.86h79qe692.gmgdl@evledraar.gmail.com/\n\nJohn Cai (3):\n  reflog: libify delete reflog function and helpers\n  reflog: call reflog_delete from reflog.c\n  stash: call reflog_delete from reflog.c\n\n Makefile         |   1 +\n builtin/reflog.c | 451 +----------------------------------------------\n builtin/stash.c  |  16 +-\n reflog.c         | 432 +++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  49 +++++\n 5 files changed, 490 insertions(+), 459 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\n\nbase-commit: b80121027d1247a0754b3cc46897fee75c050b44\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1218%2Fjohn-cai%2Fjc-libify-reflog-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1218/john-cai/jc-libify-reflog-v1\nPull-Request: https://github.com/git/git/pull/1218\n-- \ngitgitgadget\n"},{"id":"448784","messageId":"e4c0047a17ca1b5f824acfb209884a59a93ea523.1645209647.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.git.git.1645209647.gitgitgadget@gmail.com","subject":"[PATCH 2/3] reflog: call reflog_delete from reflog.c","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-18T18:40:46Z","receivedAt":"2022-02-18T18:40:56Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nNow that reflog is libified into reflog.c, we can call reflog_delete\nfrom the reflog.c library.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n builtin/reflog.c | 42 ++----------------------------------------\n 1 file changed, 2 insertions(+), 40 deletions(-)\n\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 65198320cd2..03d347e5832 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -316,12 +316,10 @@ static const char * reflog_delete_usage[] = {\n \n static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n {\n-\tstruct cmd_reflog_expire_cb cmd = { 0 };\n \tint i, status = 0;\n \tunsigned int flags = 0;\n \tint verbose = 0;\n \n-\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n \tconst struct option options[] = {\n \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\n \t\t\tEXPIRE_REFLOGS_DRY_RUN),\n@@ -337,48 +335,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);\n \n-\tif (verbose)\n-\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n-\n \tif (argc < 1)\n \t\treturn error(_(\"no reflog specified to delete\"));\n \n-\tfor (i = 0; i < argc; i++) {\n-\t\tconst char *spec = strstr(argv[i], \"@{\");\n-\t\tchar *ep, *ref;\n-\t\tint recno;\n-\t\tstruct expire_reflog_policy_cb cb = {\n-\t\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n-\t\t};\n-\n-\t\tif (!spec) {\n-\t\t\tstatus |= error(_(\"not a reflog: %s\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {\n-\t\t\tstatus |= error(_(\"no reflog for '%s'\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\trecno = strtoul(spec + 2, &ep, 10);\n-\t\tif (*ep == '}') {\n-\t\t\tcmd.recno = -recno;\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t} else {\n-\t\t\tcmd.expire_total = approxidate(spec + 2);\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t\tcmd.expire_total = 0;\n-\t\t}\n+\tfor (i = 0; i < argc; i++)\n+\t\tstatus |= reflog_delete(argv[i], flags, verbose);\n \n-\t\tcb.cmd = cmd;\n-\t\tstatus |= reflog_expire(ref, flags,\n-\t\t\t\t\treflog_expiry_prepare,\n-\t\t\t\t\tshould_prune_fn,\n-\t\t\t\t\treflog_expiry_cleanup,\n-\t\t\t\t\t&cb);\n-\t\tfree(ref);\n-\t}\n \treturn status;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"448785","messageId":"bcc1eae053124f6e68f60a7a02cbee4744a022f0.1645209647.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.git.git.1645209647.gitgitgadget@gmail.com","subject":"[PATCH 3/3] stash: call reflog_delete from reflog.c","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-18T18:40:47Z","receivedAt":"2022-02-18T18:40:58Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nNow that cmd_reflog_delete has been libified an exported it into a new\nreflog.c library so we can call it directly from builtin/stash.c. This\nnot only gives us a performance gain since we don't need to create a\nsubprocess, but it also allows us to use the ref transactions api in the\nfuture.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n builtin/stash.c | 16 ++++------------\n 1 file changed, 4 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 9638c56303e..d0967b3d3c3 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -17,6 +17,7 @@\n #include \"diffcore.h\"\n #include \"exec-cmd.h\"\n #include \"entry.h\"\n+#include \"reflog.h\"\n \n #define INCLUDE_ALL_FILES 2\n \n@@ -635,18 +636,9 @@ static int reflog_is_empty(const char *refname)\n static int do_drop_stash(struct stash_info *info, int quiet)\n {\n \tint ret;\n-\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n-\n-\t/*\n-\t * reflog does not provide a simple function for deleting refs. One will\n-\t * need to be added to avoid implementing too much reflog code here\n-\t */\n-\n-\tcp_reflog.git_cmd = 1;\n-\tstrvec_pushl(&cp_reflog.args, \"reflog\", \"delete\", \"--updateref\",\n-\t\t     \"--rewrite\", NULL);\n-\tstrvec_push(&cp_reflog.args, info->revision.buf);\n-\tret = run_command(&cp_reflog);\n+\tret = reflog_delete(info->revision.buf,\n+\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n+\t\t\t    0);\n \tif (!ret) {\n \t\tif (!quiet)\n \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n-- \ngitgitgadget\n"},{"id":"448786","messageId":"9e17ece8d8956c7fd41b7be2f5c0475b1f9af6ec.1645209647.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.git.git.1645209647.gitgitgadget@gmail.com","subject":"[PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-18T18:40:45Z","receivedAt":"2022-02-18T18:41:00Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nCurrently stash shells out to reflog in order to delete refs. In an\neffort to reduce how much we shell out to a subprocess, libify the\nfunctionality that stash needs into reflog.c.\n\nAdd a reflog_delete function that is pretty much the logic in the while\nloop in builtin/reflog.c cmd_reflog_delete(). This is a function that\nbuiltin/reflog.c and builtin/stash.c can both call.\n\nAlso move functions needed by reflog_delete and export them.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n Makefile         |   1 +\n builtin/reflog.c | 409 +-------------------------------------------\n reflog.c         | 432 +++++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  49 ++++++\n 4 files changed, 484 insertions(+), 407 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\ndiff --git a/Makefile b/Makefile\nindex 186f9ab6190..f1e295539ab 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -990,6 +990,7 @@ LIB_OBJS += rebase-interactive.o\n LIB_OBJS += rebase.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += reflog-walk.o\n+LIB_OBJS += reflog.o\n LIB_OBJS += refs.o\n LIB_OBJS += refs/debug.o\n LIB_OBJS += refs/files-backend.o\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 85b838720c3..65198320cd2 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -1,16 +1,13 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"lockfile.h\"\n-#include \"object-store.h\"\n #include \"repository.h\"\n-#include \"commit.h\"\n-#include \"refs.h\"\n #include \"dir.h\"\n-#include \"tree-walk.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"reachable.h\"\n #include \"worktree.h\"\n+#include \"reflog.h\"\n \n static const char reflog_exists_usage[] =\n N_(\"git reflog exists <ref>\");\n@@ -18,404 +15,11 @@ N_(\"git reflog exists <ref>\");\n static timestamp_t default_reflog_expire;\n static timestamp_t default_reflog_expire_unreachable;\n \n-struct cmd_reflog_expire_cb {\n-\tint stalefix;\n-\tint explicit_expiry;\n-\ttimestamp_t expire_total;\n-\ttimestamp_t expire_unreachable;\n-\tint recno;\n-};\n-\n-struct expire_reflog_policy_cb {\n-\tenum {\n-\t\tUE_NORMAL,\n-\t\tUE_ALWAYS,\n-\t\tUE_HEAD\n-\t} unreachable_expire_kind;\n-\tstruct commit_list *mark_list;\n-\tunsigned long mark_limit;\n-\tstruct cmd_reflog_expire_cb cmd;\n-\tstruct commit *tip_commit;\n-\tstruct commit_list *tips;\n-\tunsigned int dry_run:1;\n-};\n-\n struct worktree_reflogs {\n \tstruct worktree *worktree;\n \tstruct string_list reflogs;\n };\n \n-/* Remember to update object flag allocation in object.h */\n-#define INCOMPLETE\t(1u<<10)\n-#define STUDYING\t(1u<<11)\n-#define REACHABLE\t(1u<<12)\n-\n-static int tree_is_complete(const struct object_id *oid)\n-{\n-\tstruct tree_desc desc;\n-\tstruct name_entry entry;\n-\tint complete;\n-\tstruct tree *tree;\n-\n-\ttree = lookup_tree(the_repository, oid);\n-\tif (!tree)\n-\t\treturn 0;\n-\tif (tree->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (tree->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\n-\tif (!tree->buffer) {\n-\t\tenum object_type type;\n-\t\tunsigned long size;\n-\t\tvoid *data = read_object_file(oid, &type, &size);\n-\t\tif (!data) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\treturn 0;\n-\t\t}\n-\t\ttree->buffer = data;\n-\t\ttree->size = size;\n-\t}\n-\tinit_tree_desc(&desc, tree->buffer, tree->size);\n-\tcomplete = 1;\n-\twhile (tree_entry(&desc, &entry)) {\n-\t\tif (!has_object_file(&entry.oid) ||\n-\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\tcomplete = 0;\n-\t\t}\n-\t}\n-\tfree_tree_buffer(tree);\n-\n-\tif (complete)\n-\t\ttree->object.flags |= SEEN;\n-\treturn complete;\n-}\n-\n-static int commit_is_complete(struct commit *commit)\n-{\n-\tstruct object_array study;\n-\tstruct object_array found;\n-\tint is_incomplete = 0;\n-\tint i;\n-\n-\t/* early return */\n-\tif (commit->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (commit->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\t/*\n-\t * Find all commits that are reachable and are not marked as\n-\t * SEEN.  Then make sure the trees and blobs contained are\n-\t * complete.  After that, mark these commits also as SEEN.\n-\t * If some of the objects that are needed to complete this\n-\t * commit are missing, mark this commit as INCOMPLETE.\n-\t */\n-\tmemset(&study, 0, sizeof(study));\n-\tmemset(&found, 0, sizeof(found));\n-\tadd_object_array(&commit->object, NULL, &study);\n-\tadd_object_array(&commit->object, NULL, &found);\n-\tcommit->object.flags |= STUDYING;\n-\twhile (study.nr) {\n-\t\tstruct commit *c;\n-\t\tstruct commit_list *parent;\n-\n-\t\tc = (struct commit *)object_array_pop(&study);\n-\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n-\t\t\tc->object.flags |= INCOMPLETE;\n-\n-\t\tif (c->object.flags & INCOMPLETE) {\n-\t\t\tis_incomplete = 1;\n-\t\t\tbreak;\n-\t\t}\n-\t\telse if (c->object.flags & SEEN)\n-\t\t\tcontinue;\n-\t\tfor (parent = c->parents; parent; parent = parent->next) {\n-\t\t\tstruct commit *p = parent->item;\n-\t\t\tif (p->object.flags & STUDYING)\n-\t\t\t\tcontinue;\n-\t\t\tp->object.flags |= STUDYING;\n-\t\t\tadd_object_array(&p->object, NULL, &study);\n-\t\t\tadd_object_array(&p->object, NULL, &found);\n-\t\t}\n-\t}\n-\tif (!is_incomplete) {\n-\t\t/*\n-\t\t * make sure all commits in \"found\" array have all the\n-\t\t * necessary objects.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++) {\n-\t\t\tstruct commit *c =\n-\t\t\t\t(struct commit *)found.objects[i].item;\n-\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n-\t\t\t\tis_incomplete = 1;\n-\t\t\t\tc->object.flags |= INCOMPLETE;\n-\t\t\t}\n-\t\t}\n-\t\tif (!is_incomplete) {\n-\t\t\t/* mark all found commits as complete, iow SEEN */\n-\t\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t\t}\n-\t}\n-\t/* clear flags from the objects we traversed */\n-\tfor (i = 0; i < found.nr; i++)\n-\t\tfound.objects[i].item->flags &= ~STUDYING;\n-\tif (is_incomplete)\n-\t\tcommit->object.flags |= INCOMPLETE;\n-\telse {\n-\t\t/*\n-\t\t * If we come here, we have (1) traversed the ancestry chain\n-\t\t * from the \"commit\" until we reach SEEN commits (which are\n-\t\t * known to be complete), and (2) made sure that the commits\n-\t\t * encountered during the above traversal refer to trees that\n-\t\t * are complete.  Which means that we know *all* the commits\n-\t\t * we have seen during this process are complete.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t}\n-\t/* free object arrays */\n-\tobject_array_clear(&study);\n-\tobject_array_clear(&found);\n-\treturn !is_incomplete;\n-}\n-\n-static int keep_entry(struct commit **it, struct object_id *oid)\n-{\n-\tstruct commit *commit;\n-\n-\tif (is_null_oid(oid))\n-\t\treturn 1;\n-\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!commit)\n-\t\treturn 0;\n-\n-\t/*\n-\t * Make sure everything in this commit exists.\n-\t *\n-\t * We have walked all the objects reachable from the refs\n-\t * and cache earlier.  The commits reachable by this commit\n-\t * must meet SEEN commits -- and then we should mark them as\n-\t * SEEN as well.\n-\t */\n-\tif (!commit_is_complete(commit))\n-\t\treturn 0;\n-\t*it = commit;\n-\treturn 1;\n-}\n-\n-/*\n- * Starting from commits in the cb->mark_list, mark commits that are\n- * reachable from them.  Stop the traversal at commits older than\n- * the expire_limit and queue them back, so that the caller can call\n- * us again to restart the traversal with longer expire_limit.\n- */\n-static void mark_reachable(struct expire_reflog_policy_cb *cb)\n-{\n-\tstruct commit_list *pending;\n-\ttimestamp_t expire_limit = cb->mark_limit;\n-\tstruct commit_list *leftover = NULL;\n-\n-\tfor (pending = cb->mark_list; pending; pending = pending->next)\n-\t\tpending->item->object.flags &= ~REACHABLE;\n-\n-\tpending = cb->mark_list;\n-\twhile (pending) {\n-\t\tstruct commit_list *parent;\n-\t\tstruct commit *commit = pop_commit(&pending);\n-\t\tif (commit->object.flags & REACHABLE)\n-\t\t\tcontinue;\n-\t\tif (parse_commit(commit))\n-\t\t\tcontinue;\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tif (commit->date < expire_limit) {\n-\t\t\tcommit_list_insert(commit, &leftover);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tparent = commit->parents;\n-\t\twhile (parent) {\n-\t\t\tcommit = parent->item;\n-\t\t\tparent = parent->next;\n-\t\t\tif (commit->object.flags & REACHABLE)\n-\t\t\t\tcontinue;\n-\t\t\tcommit_list_insert(commit, &pending);\n-\t\t}\n-\t}\n-\tcb->mark_list = leftover;\n-}\n-\n-static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n-{\n-\t/*\n-\t * We may or may not have the commit yet - if not, look it\n-\t * up using the supplied sha1.\n-\t */\n-\tif (!commit) {\n-\t\tif (is_null_oid(oid))\n-\t\t\treturn 0;\n-\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n-\t\t\t\t\t\t\t1);\n-\n-\t\t/* Not a commit -- keep it */\n-\t\tif (!commit)\n-\t\t\treturn 0;\n-\t}\n-\n-\t/* Reachable from the current ref?  Don't prune. */\n-\tif (commit->object.flags & REACHABLE)\n-\t\treturn 0;\n-\n-\tif (cb->mark_list && cb->mark_limit) {\n-\t\tcb->mark_limit = 0; /* dig down to the root */\n-\t\tmark_reachable(cb);\n-\t}\n-\n-\treturn !(commit->object.flags & REACHABLE);\n-}\n-\n-/*\n- * Return true iff the specified reflog entry should be expired.\n- */\n-static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n-\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit *old_commit, *new_commit;\n-\n-\tif (timestamp < cb->cmd.expire_total)\n-\t\treturn 1;\n-\n-\told_commit = new_commit = NULL;\n-\tif (cb->cmd.stalefix &&\n-\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n-\t\treturn 1;\n-\n-\tif (timestamp < cb->cmd.expire_unreachable) {\n-\t\tswitch (cb->unreachable_expire_kind) {\n-\t\tcase UE_ALWAYS:\n-\t\t\treturn 1;\n-\t\tcase UE_NORMAL:\n-\t\tcase UE_HEAD:\n-\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n-\t\t\t\treturn 1;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\n-\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n-\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n-static int should_expire_reflog_ent_verbose(struct object_id *ooid,\n-\t\t\t\t\t    struct object_id *noid,\n-\t\t\t\t\t    const char *email,\n-\t\t\t\t\t    timestamp_t timestamp, int tz,\n-\t\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tint expire;\n-\n-\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n-\t\t\t\t\t  message, cb);\n-\n-\tif (!expire)\n-\t\tprintf(\"keep %s\", message);\n-\telse if (cb->dry_run)\n-\t\tprintf(\"would prune %s\", message);\n-\telse\n-\t\tprintf(\"prune %s\", message);\n-\n-\treturn expire;\n-}\n-\n-static int push_tip_to_list(const char *refname, const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n-{\n-\tstruct commit_list **list = cb_data;\n-\tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n-\t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!tip_commit)\n-\t\treturn 0;\n-\tcommit_list_insert(tip_commit, list);\n-\treturn 0;\n-}\n-\n-static int is_head(const char *refname)\n-{\n-\tswitch (ref_type(refname)) {\n-\tcase REF_TYPE_OTHER_PSEUDOREF:\n-\tcase REF_TYPE_MAIN_PSEUDOREF:\n-\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n-\t\t\tBUG(\"not a worktree ref: %s\", refname);\n-\t\tbreak;\n-\tdefault:\n-\t\tbreak;\n-\t}\n-\treturn !strcmp(refname, \"HEAD\");\n-}\n-\n-static void reflog_expiry_prepare(const char *refname,\n-\t\t\t\t  const struct object_id *oid,\n-\t\t\t\t  void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\tstruct commit *commit = NULL;\n-\n-\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n-\t\tcb->unreachable_expire_kind = UE_HEAD;\n-\t} else {\n-\t\tcommit = lookup_commit(the_repository, oid);\n-\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n-\t}\n-\n-\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n-\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tcommit_list_insert(commit, &cb->mark_list);\n-\t\t/* For reflog_expiry_cleanup() below */\n-\t\tcb->tip_commit = commit;\n-\t}\n-\tcb->mark_limit = cb->cmd.expire_total;\n-\tmark_reachable(cb);\n-}\n-\n-static void reflog_expiry_cleanup(void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tclear_commit_marks(elem->item, REACHABLE);\n-\t\tfree_commit_list(cb->tips);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n-\t\tbreak;\n-\t}\n-}\n-\n static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)\n {\n \tstruct worktree_reflogs *cb = cb_data;\n@@ -704,16 +308,6 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n-static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\tconst char *email, timestamp_t timestamp, int tz,\n-\t\tconst char *message, void *cb_data)\n-{\n-\tstruct cmd_reflog_expire_cb *cb = cb_data;\n-\tif (!cb->expire_total || timestamp < cb->expire_total)\n-\t\tcb->recno++;\n-\treturn 0;\n-}\n-\n static const char * reflog_delete_usage[] = {\n \tN_(\"git reflog delete [--rewrite] [--updateref] \"\n \t   \"[--dry-run | -n] [--verbose] <refs>...\"),\n@@ -726,6 +320,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n \tint i, status = 0;\n \tunsigned int flags = 0;\n \tint verbose = 0;\n+\n \treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n \tconst struct option options[] = {\n \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\ndiff --git a/reflog.c b/reflog.c\nnew file mode 100644\nindex 00000000000..227ed83b3da\n--- /dev/null\n+++ b/reflog.c\n@@ -0,0 +1,432 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"object-store.h\"\n+#include \"reachable.h\"\n+#include \"reflog.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"tree-walk.h\"\n+#include \"worktree.h\"\n+\n+static int tree_is_complete(const struct object_id *oid)\n+{\n+\tstruct tree_desc desc;\n+\tstruct name_entry entry;\n+\tint complete;\n+\tstruct tree *tree;\n+\n+\ttree = lookup_tree(the_repository, oid);\n+\tif (!tree)\n+\t\treturn 0;\n+\tif (tree->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (tree->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\n+\tif (!tree->buffer) {\n+\t\tenum object_type type;\n+\t\tunsigned long size;\n+\t\tvoid *data = read_object_file(oid, &type, &size);\n+\t\tif (!data) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\treturn 0;\n+\t\t}\n+\t\ttree->buffer = data;\n+\t\ttree->size = size;\n+\t}\n+\tinit_tree_desc(&desc, tree->buffer, tree->size);\n+\tcomplete = 1;\n+\twhile (tree_entry(&desc, &entry)) {\n+\t\tif (!has_object_file(&entry.oid) ||\n+\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\tcomplete = 0;\n+\t\t}\n+\t}\n+\tfree_tree_buffer(tree);\n+\n+\tif (complete)\n+\t\ttree->object.flags |= SEEN;\n+\treturn complete;\n+}\n+\n+static int commit_is_complete(struct commit *commit)\n+{\n+\tstruct object_array study;\n+\tstruct object_array found;\n+\tint is_incomplete = 0;\n+\tint i;\n+\n+\t/* early return */\n+\tif (commit->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (commit->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\t/*\n+\t * Find all commits that are reachable and are not marked as\n+\t * SEEN.  Then make sure the trees and blobs contained are\n+\t * complete.  After that, mark these commits also as SEEN.\n+\t * If some of the objects that are needed to complete this\n+\t * commit are missing, mark this commit as INCOMPLETE.\n+\t */\n+\tmemset(&study, 0, sizeof(study));\n+\tmemset(&found, 0, sizeof(found));\n+\tadd_object_array(&commit->object, NULL, &study);\n+\tadd_object_array(&commit->object, NULL, &found);\n+\tcommit->object.flags |= STUDYING;\n+\twhile (study.nr) {\n+\t\tstruct commit *c;\n+\t\tstruct commit_list *parent;\n+\n+\t\tc = (struct commit *)object_array_pop(&study);\n+\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n+\t\t\tc->object.flags |= INCOMPLETE;\n+\n+\t\tif (c->object.flags & INCOMPLETE) {\n+\t\t\tis_incomplete = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t\telse if (c->object.flags & SEEN)\n+\t\t\tcontinue;\n+\t\tfor (parent = c->parents; parent; parent = parent->next) {\n+\t\t\tstruct commit *p = parent->item;\n+\t\t\tif (p->object.flags & STUDYING)\n+\t\t\t\tcontinue;\n+\t\t\tp->object.flags |= STUDYING;\n+\t\t\tadd_object_array(&p->object, NULL, &study);\n+\t\t\tadd_object_array(&p->object, NULL, &found);\n+\t\t}\n+\t}\n+\tif (!is_incomplete) {\n+\t\t/*\n+\t\t * make sure all commits in \"found\" array have all the\n+\t\t * necessary objects.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++) {\n+\t\t\tstruct commit *c =\n+\t\t\t\t(struct commit *)found.objects[i].item;\n+\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n+\t\t\t\tis_incomplete = 1;\n+\t\t\t\tc->object.flags |= INCOMPLETE;\n+\t\t\t}\n+\t\t}\n+\t\tif (!is_incomplete) {\n+\t\t\t/* mark all found commits as complete, iow SEEN */\n+\t\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t\t}\n+\t}\n+\t/* clear flags from the objects we traversed */\n+\tfor (i = 0; i < found.nr; i++)\n+\t\tfound.objects[i].item->flags &= ~STUDYING;\n+\tif (is_incomplete)\n+\t\tcommit->object.flags |= INCOMPLETE;\n+\telse {\n+\t\t/*\n+\t\t * If we come here, we have (1) traversed the ancestry chain\n+\t\t * from the \"commit\" until we reach SEEN commits (which are\n+\t\t * known to be complete), and (2) made sure that the commits\n+\t\t * encountered during the above traversal refer to trees that\n+\t\t * are complete.  Which means that we know *all* the commits\n+\t\t * we have seen during this process are complete.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t}\n+\t/* free object arrays */\n+\tobject_array_clear(&study);\n+\tobject_array_clear(&found);\n+\treturn !is_incomplete;\n+}\n+\n+static int keep_entry(struct commit **it, struct object_id *oid)\n+{\n+\tstruct commit *commit;\n+\n+\tif (is_null_oid(oid))\n+\t\treturn 1;\n+\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!commit)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Make sure everything in this commit exists.\n+\t *\n+\t * We have walked all the objects reachable from the refs\n+\t * and cache earlier.  The commits reachable by this commit\n+\t * must meet SEEN commits -- and then we should mark them as\n+\t * SEEN as well.\n+\t */\n+\tif (!commit_is_complete(commit))\n+\t\treturn 0;\n+\t*it = commit;\n+\treturn 1;\n+}\n+\n+/*\n+ * Starting from commits in the cb->mark_list, mark commits that are\n+ * reachable from them.  Stop the traversal at commits older than\n+ * the expire_limit and queue them back, so that the caller can call\n+ * us again to restart the traversal with longer expire_limit.\n+ */\n+static void mark_reachable(struct expire_reflog_policy_cb *cb)\n+{\n+\tstruct commit_list *pending;\n+\ttimestamp_t expire_limit = cb->mark_limit;\n+\tstruct commit_list *leftover = NULL;\n+\n+\tfor (pending = cb->mark_list; pending; pending = pending->next)\n+\t\tpending->item->object.flags &= ~REACHABLE;\n+\n+\tpending = cb->mark_list;\n+\twhile (pending) {\n+\t\tstruct commit_list *parent;\n+\t\tstruct commit *commit = pop_commit(&pending);\n+\t\tif (commit->object.flags & REACHABLE)\n+\t\t\tcontinue;\n+\t\tif (parse_commit(commit))\n+\t\t\tcontinue;\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tif (commit->date < expire_limit) {\n+\t\t\tcommit_list_insert(commit, &leftover);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tparent = commit->parents;\n+\t\twhile (parent) {\n+\t\t\tcommit = parent->item;\n+\t\t\tparent = parent->next;\n+\t\t\tif (commit->object.flags & REACHABLE)\n+\t\t\t\tcontinue;\n+\t\t\tcommit_list_insert(commit, &pending);\n+\t\t}\n+\t}\n+\tcb->mark_list = leftover;\n+}\n+\n+static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n+{\n+\t/*\n+\t * We may or may not have the commit yet - if not, look it\n+\t * up using the supplied sha1.\n+\t */\n+\tif (!commit) {\n+\t\tif (is_null_oid(oid))\n+\t\t\treturn 0;\n+\n+\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n+\t\t\t\t\t\t\t1);\n+\n+\t\t/* Not a commit -- keep it */\n+\t\tif (!commit)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Reachable from the current ref?  Don't prune. */\n+\tif (commit->object.flags & REACHABLE)\n+\t\treturn 0;\n+\n+\tif (cb->mark_list && cb->mark_limit) {\n+\t\tcb->mark_limit = 0; /* dig down to the root */\n+\t\tmark_reachable(cb);\n+\t}\n+\n+\treturn !(commit->object.flags & REACHABLE);\n+}\n+\n+/*\n+ * Return true iff the specified reflog entry should be expired.\n+ */\n+int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n+\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit *old_commit, *new_commit;\n+\n+\tif (timestamp < cb->cmd.expire_total)\n+\t\treturn 1;\n+\n+\told_commit = new_commit = NULL;\n+\tif (cb->cmd.stalefix &&\n+\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n+\t\treturn 1;\n+\n+\tif (timestamp < cb->cmd.expire_unreachable) {\n+\t\tswitch (cb->unreachable_expire_kind) {\n+\t\tcase UE_ALWAYS:\n+\t\t\treturn 1;\n+\t\tcase UE_NORMAL:\n+\t\tcase UE_HEAD:\n+\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n+\t\t\t\treturn 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+int should_expire_reflog_ent_verbose(struct object_id *ooid,\n+\t\t\t\t\t    struct object_id *noid,\n+\t\t\t\t\t    const char *email,\n+\t\t\t\t\t    timestamp_t timestamp, int tz,\n+\t\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tint expire;\n+\n+\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n+\t\t\t\t\t  message, cb);\n+\n+\tif (!expire)\n+\t\tprintf(\"keep %s\", message);\n+\telse if (cb->dry_run)\n+\t\tprintf(\"would prune %s\", message);\n+\telse\n+\t\tprintf(\"prune %s\", message);\n+\n+\treturn expire;\n+}\n+\n+static int push_tip_to_list(const char *refname, const struct object_id *oid,\n+\t\t\t    int flags, void *cb_data)\n+{\n+\tstruct commit_list **list = cb_data;\n+\tstruct commit *tip_commit;\n+\tif (flags & REF_ISSYMREF)\n+\t\treturn 0;\n+\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!tip_commit)\n+\t\treturn 0;\n+\tcommit_list_insert(tip_commit, list);\n+\treturn 0;\n+}\n+\n+static int is_head(const char *refname)\n+{\n+\tswitch (ref_type(refname)) {\n+\tcase REF_TYPE_OTHER_PSEUDOREF:\n+\tcase REF_TYPE_MAIN_PSEUDOREF:\n+\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n+\t\t\tBUG(\"not a worktree ref: %s\", refname);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\treturn !strcmp(refname, \"HEAD\");\n+}\n+\n+void reflog_expiry_prepare(const char *refname,\n+\t\t\t\t  const struct object_id *oid,\n+\t\t\t\t  void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\tstruct commit *commit = NULL;\n+\n+\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n+\t\tcb->unreachable_expire_kind = UE_HEAD;\n+\t} else {\n+\t\tcommit = lookup_commit(the_repository, oid);\n+\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n+\t}\n+\n+\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n+\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tcommit_list_insert(commit, &cb->mark_list);\n+\t\t/* For reflog_expiry_cleanup() below */\n+\t\tcb->tip_commit = commit;\n+\t}\n+\tcb->mark_limit = cb->cmd.expire_total;\n+\tmark_reachable(cb);\n+}\n+\n+void reflog_expiry_cleanup(void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tclear_commit_marks(elem->item, REACHABLE);\n+\t\tfree_commit_list(cb->tips);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n+\t\tbreak;\n+\t}\n+}\n+\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\tconst char *email, timestamp_t timestamp, int tz,\n+\t\tconst char *message, void *cb_data)\n+{\n+\tstruct cmd_reflog_expire_cb *cb = cb_data;\n+\tif (!cb->expire_total || timestamp < cb->expire_total)\n+\t\tcb->recno++;\n+\treturn 0;\n+}\n+\n+int reflog_delete(const char *rev, int flags, int verbose)\n+{\n+\tstruct cmd_reflog_expire_cb cmd = { 0 };\n+\tint status = 0;\n+\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n+\tconst char *spec = strstr(rev, \"@{\");\n+\tchar *ep, *ref;\n+\tint recno;\n+\tstruct expire_reflog_policy_cb cb = {\n+\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n+\t};\n+\n+\tif (verbose)\n+\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n+\n+\tif (!spec) {\n+\t\tstatus |= error(_(\"not a reflog: %s\"), rev);\n+\t}\n+\n+\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n+\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n+\t}\n+\n+\tif (status)\n+\t\treturn status;\n+\n+\trecno = strtoul(spec + 2, &ep, 10);\n+\tif (*ep == '}') {\n+\t\tcmd.recno = -recno;\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t} else {\n+\t\tcmd.expire_total = approxidate(spec + 2);\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t\tcmd.expire_total = 0;\n+\t}\n+\n+\tcb.cmd = cmd;\n+\tstatus |= reflog_expire(ref, flags,\n+\t\t\t\treflog_expiry_prepare,\n+\t\t\t\tshould_prune_fn,\n+\t\t\t\treflog_expiry_cleanup,\n+\t\t\t\t&cb);\n+\tfree(ref);\n+\n+\treturn status;\n+}\ndiff --git a/reflog.h b/reflog.h\nnew file mode 100644\nindex 00000000000..e4a8a104f45\n--- /dev/null\n+++ b/reflog.h\n@@ -0,0 +1,49 @@\n+#ifndef REFLOG_H\n+#define REFLOG_H\n+\n+#include \"cache.h\"\n+#include \"commit.h\"\n+\n+/* Remember to update object flag allocation in object.h */\n+#define INCOMPLETE\t(1u<<10)\n+#define STUDYING\t(1u<<11)\n+#define REACHABLE\t(1u<<12)\n+\n+struct cmd_reflog_expire_cb {\n+\tint stalefix;\n+\tint explicit_expiry;\n+\ttimestamp_t expire_total;\n+\ttimestamp_t expire_unreachable;\n+\tint recno;\n+};\n+\n+struct expire_reflog_policy_cb {\n+\tenum {\n+\t\tUE_NORMAL,\n+\t\tUE_ALWAYS,\n+\t\tUE_HEAD\n+\t} unreachable_expire_kind;\n+\tstruct commit_list *mark_list;\n+\tunsigned long mark_limit;\n+\tstruct cmd_reflog_expire_cb cmd;\n+\tstruct commit *tip_commit;\n+\tstruct commit_list *tips;\n+\tunsigned int dry_run:1;\n+};\n+\n+int reflog_delete(const char*, int, int);\n+void reflog_expiry_cleanup(void *);\n+void reflog_expiry_prepare(const char*, const struct object_id*,\n+\t\t\t   void *);\n+int should_expire_reflog_ent(struct object_id *, struct object_id*,\n+\t\t\t\t    const char *, timestamp_t, int,\n+\t\t\t\t    const char *, void *);\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\tconst char *email, timestamp_t timestamp, int tz,\n+\t\tconst char *message, void *cb_data);\n+int should_expire_reflog_ent_verbose(struct object_id *,\n+\t\t\t\t     struct object_id *,\n+\t\t\t\t     const char *,\n+\t\t\t\t     timestamp_t, int,\n+\t\t\t\t     const char *, void *);\n+#endif /* REFLOG_H */\n-- \ngitgitgadget\n\n"},{"id":"448790","messageId":"220218.86wnhsgf5n.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"9e17ece8d8956c7fd41b7be2f5c0475b1f9af6ec.1645209647.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-18T19:10:07Z","receivedAt":"2022-02-18T19:15:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 18 2022, John Cai via GitGitGadget wrote:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> Currently stash shells out to reflog in order to delete refs. In an\n> effort to reduce how much we shell out to a subprocess, libify the\n> functionality that stash needs into reflog.c.\n>\n> Add a reflog_delete function that is pretty much the logic in the while\n> loop in builtin/reflog.c cmd_reflog_delete(). This is a function that\n> builtin/reflog.c and builtin/stash.c can both call.\n>\n> Also move functions needed by reflog_delete and export them.\n>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n\nYay! This looks great. Even though I'll need to deal with some conflicts\nlocally .. :)\n\n> +int reflog_delete(const char *rev, int flags, int verbose)\n> +{\n> +\tstruct cmd_reflog_expire_cb cmd = { 0 };\n> +\tint status = 0;\n> +\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n> +\tconst char *spec = strstr(rev, \"@{\");\n> +\tchar *ep, *ref;\n> +\tint recno;\n> +\tstruct expire_reflog_policy_cb cb = {\n> +\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n> +\t};\n> +\n> +\tif (verbose)\n> +\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n> +\n> +\tif (!spec) {\n> +\t\tstatus |= error(_(\"not a reflog: %s\"), rev);\n> +\t}\n> +\n> +\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n> +\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n> +\t}\n\nFor these let's follow our usual style of not having braces for\nsingle-line if's.\n\nBuuuut in this case doing so will make the diff move detection less\nuseful for 1..2.\n\nSo probably best to leave it, or do some post-cleanup at the end maybe.\n\n> +int reflog_delete(const char*, int, int);\n> +void reflog_expiry_cleanup(void *);\n> +void reflog_expiry_prepare(const char*, const struct object_id*,\n> +\t\t\t   void *);\n> +int should_expire_reflog_ent(struct object_id *, struct object_id*,\n> +\t\t\t\t    const char *, timestamp_t, int,\n> +\t\t\t\t    const char *, void *);\n> +int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n> +\t\tconst char *email, timestamp_t timestamp, int tz,\n> +\t\tconst char *message, void *cb_data);\n> +int should_expire_reflog_ent_verbose(struct object_id *,\n> +\t\t\t\t     struct object_id *,\n> +\t\t\t\t     const char *,\n> +\t\t\t\t     timestamp_t, int,\n> +\t\t\t\t     const char *, void *);\n> +#endif /* REFLOG_H */\n\nJust a style preference, but I for one prefer the style where we retain\nthe parameter names, it helps to read these, especially when we add API\ndocs here.\n\nSome of these are mis-indented. We align with the opening \"(\" with \"\\t\"\n= 8 chars, so e.g. 2x \\t + 5 SP for the count_reflog_ent() arguments\netc.\n"},{"id":"448791","messageId":"220218.86sfsggf0e.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"e4c0047a17ca1b5f824acfb209884a59a93ea523.1645209647.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/3] reflog: call reflog_delete from reflog.c","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-18T19:15:18Z","receivedAt":"2022-02-18T19:18:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 18 2022, John Cai via GitGitGadget wrote:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> Now that reflog is libified into reflog.c, we can call reflog_delete\n> from the reflog.c library.\n>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n> ---\n>  builtin/reflog.c | 42 ++----------------------------------------\n>  1 file changed, 2 insertions(+), 40 deletions(-)\n>\n> diff --git a/builtin/reflog.c b/builtin/reflog.c\n> index 65198320cd2..03d347e5832 100644\n> --- a/builtin/reflog.c\n> +++ b/builtin/reflog.c\n> @@ -316,12 +316,10 @@ static const char * reflog_delete_usage[] = {\n>  \n>  static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n>  {\n> -\tstruct cmd_reflog_expire_cb cmd = { 0 };\n>  \tint i, status = 0;\n>  \tunsigned int flags = 0;\n>  \tint verbose = 0;\n>  \n> -\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n>  \tconst struct option options[] = {\n>  \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\n>  \t\t\tEXPIRE_REFLOGS_DRY_RUN),\n> @@ -337,48 +335,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n>  \n>  \targc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);\n>  \n> -\tif (verbose)\n> -\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n> -\n>  \tif (argc < 1)\n>  \t\treturn error(_(\"no reflog specified to delete\"));\n>  \n> -\tfor (i = 0; i < argc; i++) {\n> -\t\tconst char *spec = strstr(argv[i], \"@{\");\n> -\t\tchar *ep, *ref;\n> -\t\tint recno;\n> -\t\tstruct expire_reflog_policy_cb cb = {\n> -\t\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n> -\t\t};\n> -\n> -\t\tif (!spec) {\n> -\t\t\tstatus |= error(_(\"not a reflog: %s\"), argv[i]);\n> -\t\t\tcontinue;\n> -\t\t}\n> -\n> -\t\tif (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {\n> -\t\t\tstatus |= error(_(\"no reflog for '%s'\"), argv[i]);\n> -\t\t\tcontinue;\n> -\t\t}\n> -\n> -\t\trecno = strtoul(spec + 2, &ep, 10);\n> -\t\tif (*ep == '}') {\n> -\t\t\tcmd.recno = -recno;\n> -\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n> -\t\t} else {\n> -\t\t\tcmd.expire_total = approxidate(spec + 2);\n> -\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n> -\t\t\tcmd.expire_total = 0;\n> -\t\t}\n> +\tfor (i = 0; i < argc; i++)\n> +\t\tstatus |= reflog_delete(argv[i], flags, verbose);\n>  \n> -\t\tcb.cmd = cmd;\n> -\t\tstatus |= reflog_expire(ref, flags,\n> -\t\t\t\t\treflog_expiry_prepare,\n> -\t\t\t\t\tshould_prune_fn,\n> -\t\t\t\t\treflog_expiry_cleanup,\n> -\t\t\t\t\t&cb);\n> -\t\tfree(ref);\n> -\t}\n>  \treturn status;\n>  }\n\n\nMaybe others will disagree, but per my comment on 1/2 I found reviewing\nthis locally much easier with this squashed into 1/2 (without the {}\nchanges I suggested).\n\nI.e. the diff move/rename detection eats up this change & shows that the\ncombinatino of 1/3 and 2/3 is almost entirely just moving around\nexisting code (good!).\n\nBut without this squashed 1/3 has a reflog_delete() \"addition\", that we\nlater can see is mostly just moving things around.\n\nI'll leave it to you to decide what you want to do there, just\nsuggestion on an otherwise very trivial-to-review change :)\n"},{"id":"448792","messageId":"220218.86o834gej9.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"bcc1eae053124f6e68f60a7a02cbee4744a022f0.1645209647.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/3] stash: call reflog_delete from reflog.c","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-18T19:20:13Z","receivedAt":"2022-02-18T19:28:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 18 2022, John Cai via GitGitGadget wrote:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> Now that cmd_reflog_delete has been libified an exported it into a new\n> reflog.c library so we can call it directly from builtin/stash.c. This\n> not only gives us a performance gain since we don't need to create a\n> subprocess, but it also allows us to use the ref transactions api in the\n> future.\n>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n\nVery nicely done, and nice that despite the ~500 lines added/removed in\nthe diffstat that the \"actual\" changes in this series are so small.q\n\n> @@ -635,18 +636,9 @@ static int reflog_is_empty(const char *refname)\n>  static int do_drop_stash(struct stash_info *info, int quiet)\n>  {\n>  \tint ret;\n> -\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n> -\n\nNit: We usually separate variables decls with a \\n\\n, as is done in the\npre-image, but you end up dropping that.\n\n> -\t/*\n> -\t * reflog does not provide a simple function for deleting refs. One will\n> -\t * need to be added to avoid implementing too much reflog code here\n> -\t */\n> -\n> -\tcp_reflog.git_cmd = 1;\n> -\tstrvec_pushl(&cp_reflog.args, \"reflog\", \"delete\", \"--updateref\",\n> -\t\t     \"--rewrite\", NULL);\n> -\tstrvec_push(&cp_reflog.args, info->revision.buf);\n> -\tret = run_command(&cp_reflog);\n> +\tret = reflog_delete(info->revision.buf,\n> +\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n> +\t\t\t    0);\n>  \tif (!ret) {\n>  \t\tif (!quiet)\n>  \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n\nI think per the above squashing this in would be nice, i.e. you get rid\nof the long line & it'sclear that \"ret\" is not used for anything now:\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex d0967b3d3c3..7b939576720 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -635,11 +635,9 @@ static int reflog_is_empty(const char *refname)\n \n static int do_drop_stash(struct stash_info *info, int quiet)\n {\n-\tint ret;\n-\tret = reflog_delete(info->revision.buf,\n-\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n-\t\t\t    0);\n-\tif (!ret) {\n+\tunsigned int flags = EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE;\n+\n+\tif (!reflog_delete(info->revision.buf, flags, 0)) {\n \t\tif (!quiet)\n \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n \t\t\t\t  oid_to_hex(&info->w_commit));\n\nBut, having written that I notice that we have *_REWRITE twice there, so\nI almost just carried forward a new bug in 3/3 when composing this :)\n\nSo one should be EXPIRE_REFLOGS_UPDATE_REF, presumably.\n\nAnd perhaps it's a big pain, but that suggests that the code isn't\neither used at all, or that we're missing a test for it.\n\nSo adding a prep commit to this series where we either drop it, or add\nthe missing test would be a very nice addition.\n\nSee my quite recent 5ac15ad2509 (reflog tests: add --updateref tests,\n2021-10-16) for adding such tests, but in that case I just covered \"git\nreflog\" itself, not \"git stash\". Maybe we can just add something to that\nfor-loop in t1417 (or similar for a new stash test).\n\nAlso, a s/unsigned int flags/enum expire_reflog_flags/ while we're at it\nwould be very nice here, but that could be done as another small prep\ncommit. I.e. it's *not* a new issue since cmd_reflog_delete() had it\nbefore, but when converting this to a documented API it would be very\nnice to have it reflect the actual type we end up using.\n"},{"id":"448793","messageId":"220218.86k0dsgeg6.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"pull.1218.git.git.1645209647.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/3] libify reflog","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-18T19:29:19Z","receivedAt":"2022-02-18T19:30:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 18 2022, John Cai via GitGitGadget wrote:\n\n> In [1], there was a discussion around a bug report of stash not recovering\n> in the middle of the process. It turned out to not be a bug we need to fix.\n> However, out of that discussion came the idea of libifying reflog. This can\n> stand alone as a code improvement.\n>\n> stash.c currently shells out to call reflog to delete reflogs. Libify reflog\n> delete and call it from both builtin/reflog.c and builtin/stash.c.\n>\n> This patch has three parts:\n>\n>  * libify reflog's delete functionality and move some of the helpers into a\n>    reflog.c library and export them\n>  * call reflog_delete from builtin/reflog.c\n>  * call reflog_delete from builtin/stash.c\n>\n>  1. https://lore.kernel.org/git/220126.86h79qe692.gmgdl@evledraar.gmail.com/\n\nI just reviewed this in detail, it's very nice to see this done &\nanother shell-out in our code API-ified.\n\nThere's at least one bad bug here (but easily fixed), and some nits\netc. that I suggested mainly to make the diff easier to look at locally,\nbut aside from those minor adjustments this looks very good to me.\n\n\n"},{"id":"448794","messageId":"Yg/09XYTruPJQVV/@nand.local","threadId":"57437","inReplyTo":"9e17ece8d8956c7fd41b7be2f5c0475b1f9af6ec.1645209647.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-02-18T19:35:17Z","receivedAt":"2022-02-18T19:35:23Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Feb 18, 2022 at 06:40:45PM +0000, John Cai via GitGitGadget wrote:\n> From: John Cai <johncai86@gmail.com>\n>\n> Currently stash shells out to reflog in order to delete refs. In an\n> effort to reduce how much we shell out to a subprocess, libify the\n> functionality that stash needs into reflog.c.\n\nSounds great. For other reviewers, looking at this with `--color-moved`\nif you have the patches applied locally makes it much easier to see what\nis going on here, which is basically:\n\n  - All of the implementation that used to be in builtin/reflog.c moved\n    to reflog.c.\n\n  - The function signatures and structure declarations moved to\n    reflog.h.\n\n> Add a reflog_delete function that is pretty much the logic in the while\n> loop in builtin/reflog.c cmd_reflog_delete(). This is a function that\n> builtin/reflog.c and builtin/stash.c can both call.\n\nAs you mentioned, the `reflog_delete()` implementation is indeed new. It\nlooks like Ævar reviewed most of it already, so I'll take a look at his\nmessage before I end up repeating everything he already said ;).\n\nIt's worth noting that the subsequent clean-up to rewrite\ncmd_reflog_delete() in terms of your new `reflog_delete()` happens in\nthe subsequent commit. If you end up rerolling this series, mentioning\nthat here may be worthwhile.\n\nOne question that I had which I don't see answered already is what the\nplan is for existing reflog-related functions that live in refs.h.\nShould or will those functions be moved to the new reflog-specific\nheader, too?\n\nThanks,\nTaylor\n"},{"id":"448796","messageId":"Yg/1205sTdqE2mC5@nand.local","threadId":"57437","inReplyTo":"220218.86wnhsgf5n.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-02-18T19:39:07Z","receivedAt":"2022-02-18T19:39:12Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Feb 18, 2022 at 08:10:07PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> > +int reflog_delete(const char *rev, int flags, int verbose)\n> > +{\n> > +\tstruct cmd_reflog_expire_cb cmd = { 0 };\n> > +\tint status = 0;\n> > +\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n> > +\tconst char *spec = strstr(rev, \"@{\");\n> > +\tchar *ep, *ref;\n> > +\tint recno;\n> > +\tstruct expire_reflog_policy_cb cb = {\n> > +\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n> > +\t};\n> > +\n> > +\tif (verbose)\n> > +\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n> > +\n> > +\tif (!spec) {\n> > +\t\tstatus |= error(_(\"not a reflog: %s\"), rev);\n> > +\t}\n> > +\n> > +\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n> > +\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n> > +\t}\n>\n> For these let's follow our usual style of not having braces for\n> single-line if's.\n>\n> Buuuut in this case doing so will make the diff move detection less\n> useful for 1..2.\n>\n> So probably best to leave it, or do some post-cleanup at the end maybe.\n\nHmm. I don't think the diff detection mechanism would have an\nopportunity to kick in here, since the code is added in one patch and\nthen removed in another. I think I may be missing what you're trying to\nsay here ;).\n\nIn any case, I don't think it's a huge deal if we can't accurately\ncolorize this with `--color-moved`, so I'd probably just as soon clean\nup the style nits in this patch.\n\n> > +int reflog_delete(const char*, int, int);\n> > +void reflog_expiry_cleanup(void *);\n> > +void reflog_expiry_prepare(const char*, const struct object_id*,\n> > +\t\t\t   void *);\n> > +int should_expire_reflog_ent(struct object_id *, struct object_id*,\n> > +\t\t\t\t    const char *, timestamp_t, int,\n> > +\t\t\t\t    const char *, void *);\n> > +int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n> > +\t\tconst char *email, timestamp_t timestamp, int tz,\n> > +\t\tconst char *message, void *cb_data);\n> > +int should_expire_reflog_ent_verbose(struct object_id *,\n> > +\t\t\t\t     struct object_id *,\n> > +\t\t\t\t     const char *,\n> > +\t\t\t\t     timestamp_t, int,\n> > +\t\t\t\t     const char *, void *);\n> > +#endif /* REFLOG_H */\n>\n> Just a style preference, but I for one prefer the style where we retain\n> the parameter names, it helps to read these, especially when we add API\n> docs here.\n>\n> Some of these are mis-indented. We align with the opening \"(\" with \"\\t\"\n> = 8 chars, so e.g. 2x \\t + 5 SP for the count_reflog_ent() arguments\n> etc.\n\nThanks for noting on both.\n\nThanks,\nTaylor\n"},{"id":"448798","messageId":"220218.86bkz4gdlt.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"Yg/1205sTdqE2mC5@nand.local","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-18T19:48:08Z","receivedAt":"2022-02-18T19:48:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 18 2022, Taylor Blau wrote:\n\n> On Fri, Feb 18, 2022 at 08:10:07PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>> > +int reflog_delete(const char *rev, int flags, int verbose)\n>> > +{\n>> > +\tstruct cmd_reflog_expire_cb cmd = { 0 };\n>> > +\tint status = 0;\n>> > +\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n>> > +\tconst char *spec = strstr(rev, \"@{\");\n>> > +\tchar *ep, *ref;\n>> > +\tint recno;\n>> > +\tstruct expire_reflog_policy_cb cb = {\n>> > +\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n>> > +\t};\n>> > +\n>> > +\tif (verbose)\n>> > +\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n>> > +\n>> > +\tif (!spec) {\n>> > +\t\tstatus |= error(_(\"not a reflog: %s\"), rev);\n>> > +\t}\n>> > +\n>> > +\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n>> > +\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n>> > +\t}\n>>\n>> For these let's follow our usual style of not having braces for\n>> single-line if's.\n>>\n>> Buuuut in this case doing so will make the diff move detection less\n>> useful for 1..2.\n>>\n>> So probably best to leave it, or do some post-cleanup at the end maybe.\n>\n> Hmm. I don't think the diff detection mechanism would have an\n> opportunity to kick in here, since the code is added in one patch and\n> then removed in another. I think I may be missing what you're trying to\n> say here ;).\n>\n> In any case, I don't think it's a huge deal if we can't accurately\n> colorize this with `--color-moved`, so I'd probably just as soon clean\n> up the style nits in this patch.\n\nYes, this should really be read in combination with my comment on 2/3 to\nsquash that commit into 1/3 to take full advantage of that, sorry.\n"},{"id":"448799","messageId":"xmqqpmnkymfn.fsf@gitster.g","threadId":"57437","inReplyTo":"9e17ece8d8956c7fd41b7be2f5c0475b1f9af6ec.1645209647.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-18T20:00:44Z","receivedAt":"2022-02-18T20:00:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/reflog.h b/reflog.h\n> new file mode 100644\n> index 00000000000..e4a8a104f45\n> --- /dev/null\n> +++ b/reflog.h\n> @@ -0,0 +1,49 @@\n> +#ifndef REFLOG_H\n> +#define REFLOG_H\n> +\n> +#include \"cache.h\"\n> +#include \"commit.h\"\n> +\n> +/* Remember to update object flag allocation in object.h */\n> +#define INCOMPLETE\t(1u<<10)\n> +#define STUDYING\t(1u<<11)\n> +#define REACHABLE\t(1u<<12)\n\nThese names were unique enough back when they were part of internal\nimplementation detail inside builtin/reflog.c but it no longer is in\nthe scope it is visible.  builtin/stash.c (and whatever other things\nthat may want to deal with reflogs in the future) is about stash\nentries and wants to have a way to differentiate these symbols from\nothers and hint that these are about reflog operation.\n\nPerhaps prefix them with REFLOG_ or something?\n\nOr, do we even NEED to expose these bits outside the implementation\nof reflog.c?  If these three constants are used ONLY by reflog.c and\nnot by builtin/reflog.c (or builtin/stash.c), then moving them to\nwhere they are used, i.e. in reflog.c, without exposing them to\nothers in reflog.h, would be a far better thing to do.  That way,\nthey can stay with their original name, without having to add a\ndifferentiating prefix.\n\nI also checked if any of the following struct's can be left as a\nprivate implementation detail, but they are shared between reflog.c\nand builtin/reflog.c and need to be in reflog.h\n\n\n\n> +struct cmd_reflog_expire_cb {\n> +\tint stalefix;\n> +\tint explicit_expiry;\n> +\ttimestamp_t expire_total;\n> +\ttimestamp_t expire_unreachable;\n> +\tint recno;\n> +};\n> +\n> +struct expire_reflog_policy_cb {\n> +\tenum {\n> +\t\tUE_NORMAL,\n> +\t\tUE_ALWAYS,\n> +\t\tUE_HEAD\n> +\t} unreachable_expire_kind;\n> +\tstruct commit_list *mark_list;\n> +\tunsigned long mark_limit;\n> +\tstruct cmd_reflog_expire_cb cmd;\n> +\tstruct commit *tip_commit;\n> +\tstruct commit_list *tips;\n> +\tunsigned int dry_run:1;\n> +};\n> +\n> +int reflog_delete(const char*, int, int);\n> +void reflog_expiry_cleanup(void *);\n> +void reflog_expiry_prepare(const char*, const struct object_id*,\n> +\t\t\t   void *);\n> +int should_expire_reflog_ent(struct object_id *, struct object_id*,\n> +\t\t\t\t    const char *, timestamp_t, int,\n> +\t\t\t\t    const char *, void *);\n> +int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n> +\t\tconst char *email, timestamp_t timestamp, int tz,\n> +\t\tconst char *message, void *cb_data);\n> +int should_expire_reflog_ent_verbose(struct object_id *,\n> +\t\t\t\t     struct object_id *,\n> +\t\t\t\t     const char *,\n> +\t\t\t\t     timestamp_t, int,\n> +\t\t\t\t     const char *, void *);\n> +#endif /* REFLOG_H */\n"},{"id":"448800","messageId":"xmqqk0dsylgj.fsf@gitster.g","threadId":"57437","inReplyTo":"9e17ece8d8956c7fd41b7be2f5c0475b1f9af6ec.1645209647.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-18T20:21:48Z","receivedAt":"2022-02-18T20:21:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> Currently stash shells out to reflog in order to delete refs. In an\n> effort to reduce how much we shell out to a subprocess, libify the\n> functionality that stash needs into reflog.c.\n>\n> Add a reflog_delete function that is pretty much the logic in the while\n> loop in builtin/reflog.c cmd_reflog_delete(). This is a function that\n> builtin/reflog.c and builtin/stash.c can both call.\n\nI do not quite get this step.  Yes, eventually cmd_reflog_delete()\nand cmd_stash() can both call the new reflog_delete() function, but\nat this step, cmd_reflog_delete() should ALREADY be able to call it,\nbut I see duplicated code.\n\nWhy?\n\n> @@ -726,6 +320,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n>  \tint i, status = 0;\n>  \tunsigned int flags = 0;\n>  \tint verbose = 0;\n> +\n>  \treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n>  \tconst struct option options[] = {\n>  \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\n\nIn other words, why the only change this step makes to\ncmd_reflog_delete() is a style change here, instead of replacing the\nlogic inside its \"while loop\" with a call to the new helper\nfunction, which is introduced ...\n\n> diff --git a/reflog.c b/reflog.c\n> new file mode 100644\n> index 00000000000..227ed83b3da\n> --- /dev/null\n> +++ b/reflog.c\n> ...\n> +int reflog_delete(const char *rev, int flags, int verbose)\n> +{\n\n... here?\n\n> +\tstruct cmd_reflog_expire_cb cmd = { 0 };\n> +\tint status = 0;\n> +\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n> +\tconst char *spec = strstr(rev, \"@{\");\n> +\tchar *ep, *ref;\n> +\tint recno;\n> +\tstruct expire_reflog_policy_cb cb = {\n> +\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n> +\t};\n> +\n> +\tif (verbose)\n> +\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n> +\n> +\tif (!spec) {\n> +\t\tstatus |= error(_(\"not a reflog: %s\"), rev);\n> +\t}\n\nIf this were \"we moved one iteration of an existing loop to a\nseparate function, while trying to keep what the existing code\nlooked like as pristine as possible\", then it is fine, but it does\nnot look like that is what is going on (and I do not think it is\nfeasible to do so, as the original being an interation in a loop,\nit has \"continue\" here, instead you'd need to arrange to return an\nerror from here to allow the caller to work in a similar way).\n\nSo, if the patch is adding an adjusted implementation by removing\n\"continue;\" that is not suitable in the context of this helper, it\nshould remove the now-unnecessary {braces} around the single-statement\nblock.\n\nBut I think you should keep {braces} around, because the \"if spec is\nNULL\" case should not be a single-statement block.  You'd need to\nreturn or this code is buggy.\n\n> +\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n> +\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n> +\t}\n\nI already hinted that the previous \"if spec is NULL\" code is buggy\nbecause it does not return to allow the original caller to keep\nworking as before.  Because you lack an early return there, you'll\nend up calling dwim_log with spec==NULL here, which does not end\nwell.\n\n"},{"id":"448801","messageId":"xmqqczjjzztp.fsf@gitster.g","threadId":"57437","inReplyTo":"220218.86sfsggf0e.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 2/3] reflog: call reflog_delete from reflog.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-18T20:26:10Z","receivedAt":"2022-02-18T20:26:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> Maybe others will disagree, but per my comment on 1/2 I found reviewing\n> this locally much easier with this squashed into 1/2 (without the {}\n> changes I suggested).\n\nOh, I am pretty much on the same page.\n\nThe if() block has to retain {} after all but not for the reason you\ncite (i.e. help \"diff --color-moved\"), but for correctness reasons\nit has to have some \"early return to avoid using NULL\" to replace\n\"continue\", which means the body of the if() statement needs to stay\na two statement block.\n\n"},{"id":"448819","messageId":"YhA4F5qb7/TIxMcB@nand.local","threadId":"57437","inReplyTo":"220218.86o834gej9.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 3/3] stash: call reflog_delete from reflog.c","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-02-19T00:21:43Z","receivedAt":"2022-02-19T00:21:53Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Feb 18, 2022 at 08:20:13PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index d0967b3d3c3..7b939576720 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -635,11 +635,9 @@ static int reflog_is_empty(const char *refname)\n>\n>  static int do_drop_stash(struct stash_info *info, int quiet)\n>  {\n> -\tint ret;\n> -\tret = reflog_delete(info->revision.buf,\n> -\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n> -\t\t\t    0);\n> -\tif (!ret) {\n> +\tunsigned int flags = EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE;\n> +\n> +\tif (!reflog_delete(info->revision.buf, flags, 0)) {\n>  \t\tif (!quiet)\n>  \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n>  \t\t\t\t  oid_to_hex(&info->w_commit));\n>\n> But, having written that I notice that we have *_REWRITE twice there, so\n> I almost just carried forward a new bug in 3/3 when composing this :)\n>\n> So one should be EXPIRE_REFLOGS_UPDATE_REF, presumably.\n\nThanks for pointing it out. Just thinking aloud here, the old code ran:\n\n    git reflog delete --updateref --rewrite\n\nwhere `--updateref` sets the `EXPIRE_REFLOGS_UPDATE_REF` bit and\n`-rewrite` sets the `EXPIRE_REFLOGS_REWRITE` bit.\n\nSo yep, you and I have the same conclusion there, and one of these flags\nshould be the UPDATE_REF variant.\n\n> And perhaps it's a big pain, but that suggests that the code isn't\n> either used at all, or that we're missing a test for it.\n\nI think this is much more in the \"lacking test coverage\" category than\nthe \"unused features we should remove\" one.\n\nThanks,\nTaylor\n"},{"id":"448826","messageId":"220219.86h78vftsa.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"xmqqpmnkymfn.fsf@gitster.g","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-19T02:53:14Z","receivedAt":"2022-02-19T02:56:59Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 18 2022, Junio C Hamano wrote:\n\n> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> diff --git a/reflog.h b/reflog.h\n>> new file mode 100644\n>> index 00000000000..e4a8a104f45\n>> --- /dev/null\n>> +++ b/reflog.h\n>> @@ -0,0 +1,49 @@\n>> +#ifndef REFLOG_H\n>> +#define REFLOG_H\n>> +\n>> +#include \"cache.h\"\n>> +#include \"commit.h\"\n>> +\n>> +/* Remember to update object flag allocation in object.h */\n>> +#define INCOMPLETE\t(1u<<10)\n>> +#define STUDYING\t(1u<<11)\n>> +#define REACHABLE\t(1u<<12)\n>\n> These names were unique enough back when they were part of internal\n> implementation detail inside builtin/reflog.c but it no longer is in\n> the scope it is visible.  builtin/stash.c (and whatever other things\n> that may want to deal with reflogs in the future) is about stash\n> entries and wants to have a way to differentiate these symbols from\n> others and hint that these are about reflog operation.\n>\n> Perhaps prefix them with REFLOG_ or something?\n>\n> Or, do we even NEED to expose these bits outside the implementation\n> of reflog.c?  If these three constants are used ONLY by reflog.c and\n> not by builtin/reflog.c (or builtin/stash.c), then moving them to\n> where they are used, i.e. in reflog.c, without exposing them to\n> others in reflog.h, would be a far better thing to do.  That way,\n> they can stay with their original name, without having to add a\n> differentiating prefix.\n\nNo objection to this, but FWIW the general pattern for these object.h\nflags is to use these un-prefixed names:\n\n     git grep -A20 'Remember to update object flag allocation' | grep define\n\nTo be fair the only one that's really comparable is the revision.h ones,\nbut those are very non-namespace-y, with names like SEEN, ADDED, SHOWN\netc.\n\nI'd be fine with just leaving these as-is, especially with compilers\nwarning about macro re-definitions.\n"},{"id":"448828","messageId":"YhBdyEQX56IKYlCM@nand.local","threadId":"57437","inReplyTo":"220219.86h78vftsa.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-02-19T03:02:32Z","receivedAt":"2022-02-19T03:02:37Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Sat, Feb 19, 2022 at 03:53:14AM +0100, Ævar Arnfjörð Bjarmason wrote:\n>\n> On Fri, Feb 18 2022, Junio C Hamano wrote:\n>\n> > \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> >> diff --git a/reflog.h b/reflog.h\n> >> new file mode 100644\n> >> index 00000000000..e4a8a104f45\n> >> --- /dev/null\n> >> +++ b/reflog.h\n> >> @@ -0,0 +1,49 @@\n> >> +#ifndef REFLOG_H\n> >> +#define REFLOG_H\n> >> +\n> >> +#include \"cache.h\"\n> >> +#include \"commit.h\"\n> >> +\n> >> +/* Remember to update object flag allocation in object.h */\n> >> +#define INCOMPLETE\t(1u<<10)\n> >> +#define STUDYING\t(1u<<11)\n> >> +#define REACHABLE\t(1u<<12)\n> >\n> > These names were unique enough back when they were part of internal\n> > implementation detail inside builtin/reflog.c but it no longer is in\n> > the scope it is visible.  builtin/stash.c (and whatever other things\n> > that may want to deal with reflogs in the future) is about stash\n> > entries and wants to have a way to differentiate these symbols from\n> > others and hint that these are about reflog operation.\n> >\n> > Perhaps prefix them with REFLOG_ or something?\n> >\n> > Or, do we even NEED to expose these bits outside the implementation\n> > of reflog.c?  If these three constants are used ONLY by reflog.c and\n> > not by builtin/reflog.c (or builtin/stash.c), then moving them to\n> > where they are used, i.e. in reflog.c, without exposing them to\n> > others in reflog.h, would be a far better thing to do.  That way,\n> > they can stay with their original name, without having to add a\n> > differentiating prefix.\n>\n> No objection to this, but FWIW the general pattern for these object.h\n> flags is to use these un-prefixed names:\n>\n>      git grep -A20 'Remember to update object flag allocation' | grep define\n>\n> To be fair the only one that's really comparable is the revision.h ones,\n> but those are very non-namespace-y, with names like SEEN, ADDED, SHOWN\n> etc.\n>\n> I'd be fine with just leaving these as-is, especially with compilers\n> warning about macro re-definitions.\n\nI think I have a mild preference to avoid polluting the set of macros\nwith something like INCOMPLETE outside of reflog.c's compilation unit\nwhen possible. Though I don't really feel strongly either way.\n\nIn any case, let's make sure to update object.h's flag allocation table,\nwhich as of this patch still indicates that these bits belong to\n\"builtin/reflog.c\" (instead of \"reflog.h\" or \"reflog.c\", depending on\nwhat the outcome of this discussion is).\n\nThanks,\nTaylor\n"},{"id":"448893","messageId":"xmqqo832vuyr.fsf@gitster.g","threadId":"57437","inReplyTo":"220219.86h78vftsa.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-20T07:49:16Z","receivedAt":"2022-02-20T07:49:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n>> Perhaps prefix them with REFLOG_ or something?\n>>\n>> Or, do we even NEED to expose these bits outside the implementation\n>> of reflog.c?  If these three constants are used ONLY by reflog.c and\n>> not by builtin/reflog.c (or builtin/stash.c), then moving them to\n>> where they are used, i.e. in reflog.c, without exposing them to\n>> others in reflog.h, would be a far better thing to do.  That way,\n>> they can stay with their original name, without having to add a\n>> differentiating prefix.\n>\n> No objection to this, but FWIW the general pattern for these object.h\n> flags is to use these un-prefixed names:\n>\n>      git grep -A20 'Remember to update object flag allocation' | grep define\n>\n> To be fair the only one that's really comparable is the revision.h ones,\n> but those are very non-namespace-y, with names like SEEN, ADDED, SHOWN\n> etc.\n\nYes, but I consider that revision.h owns the flag bits, and all\nothers that are local to a single *.c files borrow leftover bits\n(and that is why the allocations can be overlapping).\n\nAnd that is why my preference is to keep these within reflog.c if we\nare separating it out of builtin/reflog.c to make sure they do not\npollute global namespace.\n"},{"id":"448936","messageId":"F49696AE-3A00-4870-A355-A9510468F35D@gmail.com","threadId":"57437","inReplyTo":"Yg/09XYTruPJQVV/@nand.local","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-21T01:43:14Z","receivedAt":"2022-02-21T01:43:19Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hey Taylor,\n\nOn 18 Feb 2022, at 14:35, Taylor Blau wrote:\n\n> On Fri, Feb 18, 2022 at 06:40:45PM +0000, John Cai via GitGitGadget wrote:\n>> From: John Cai <johncai86@gmail.com>\n>>\n>> Currently stash shells out to reflog in order to delete refs. In an\n>> effort to reduce how much we shell out to a subprocess, libify the\n>> functionality that stash needs into reflog.c.\n>\n> Sounds great. For other reviewers, looking at this with `--color-moved`\n> if you have the patches applied locally makes it much easier to see what\n> is going on here, which is basically:\n>\n>   - All of the implementation that used to be in builtin/reflog.c moved\n>     to reflog.c.\n>\n>   - The function signatures and structure declarations moved to\n>     reflog.h.\n>\n>> Add a reflog_delete function that is pretty much the logic in the while\n>> loop in builtin/reflog.c cmd_reflog_delete(). This is a function that\n>> builtin/reflog.c and builtin/stash.c can both call.\n>\n> As you mentioned, the `reflog_delete()` implementation is indeed new. It\n> looks like Ævar reviewed most of it already, so I'll take a look at his\n> message before I end up repeating everything he already said ;).\n>\n> It's worth noting that the subsequent clean-up to rewrite\n> cmd_reflog_delete() in terms of your new `reflog_delete()` happens in\n> the subsequent commit. If you end up rerolling this series, mentioning\n> that here may be worthwhile.\n>\n> One question that I had which I don't see answered already is what the\n> plan is for existing reflog-related functions that live in refs.h.\n> Should or will those functions be moved to the new reflog-specific\n> header, too?\n\nThanks for bringing ths up! Maybe this cleanup can be included in a separate\npatch series since this one is mainly about getting rid of the shell-out in\nstash.c. What do you think?\n\n>\n> Thanks,\n> Taylor\n"},{"id":"448939","messageId":"YhLv9P9VFQ6Iwv41@nand.local","threadId":"57437","inReplyTo":"F49696AE-3A00-4870-A355-A9510468F35D@gmail.com","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-02-21T01:50:44Z","receivedAt":"2022-02-21T01:50:48Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Sun, Feb 20, 2022 at 08:43:14PM -0500, John Cai wrote:\n> > One question that I had which I don't see answered already is what the\n> > plan is for existing reflog-related functions that live in refs.h.\n> > Should or will those functions be moved to the new reflog-specific\n> > header, too?\n>\n> Thanks for bringing ths up! Maybe this cleanup can be included in a separate\n> patch series since this one is mainly about getting rid of the shell-out in\n> stash.c. What do you think?\n\nYeah; I think that's fine. We don't need to get all reflog-related\nfunctions moved into reflog.h in the first series, so I think it's fine\nto move things as you discover them.\n\nI was just wondering whether it was something you planned to do at some\npoint (as part of this series, or a future one).\n\nThanks,\nTaylor\n"},{"id":"449087","messageId":"DAF4762E-6376-44BC-9E8C-B9C32B65D92B@gmail.com","threadId":"57437","inReplyTo":"220218.86o834gej9.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 3/3] stash: call reflog_delete from reflog.c","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-22T02:36:13Z","receivedAt":"2022-02-22T02:36:17Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Ævar,\n\nOn 18 Feb 2022, at 14:20, Ævar Arnfjörð Bjarmason wrote:\n\n> On Fri, Feb 18 2022, John Cai via GitGitGadget wrote:\n>\n>> From: John Cai <johncai86@gmail.com>\n>>\n>> Now that cmd_reflog_delete has been libified an exported it into a new\n>> reflog.c library so we can call it directly from builtin/stash.c. This\n>> not only gives us a performance gain since we don't need to create a\n>> subprocess, but it also allows us to use the ref transactions api in the\n>> future.\n>>\n>> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>> Signed-off-by: John Cai <johncai86@gmail.com>\n>\n> Very nicely done, and nice that despite the ~500 lines added/removed in\n> the diffstat that the \"actual\" changes in this series are so small.q\n>\n>> @@ -635,18 +636,9 @@ static int reflog_is_empty(const char *refname)\n>>  static int do_drop_stash(struct stash_info *info, int quiet)\n>>  {\n>>  \tint ret;\n>> -\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n>> -\n>\n> Nit: We usually separate variables decls with a \\n\\n, as is done in the\n> pre-image, but you end up dropping that.\n>\n>> -\t/*\n>> -\t * reflog does not provide a simple function for deleting refs. One will\n>> -\t * need to be added to avoid implementing too much reflog code here\n>> -\t */\n>> -\n>> -\tcp_reflog.git_cmd = 1;\n>> -\tstrvec_pushl(&cp_reflog.args, \"reflog\", \"delete\", \"--updateref\",\n>> -\t\t     \"--rewrite\", NULL);\n>> -\tstrvec_push(&cp_reflog.args, info->revision.buf);\n>> -\tret = run_command(&cp_reflog);\n>> +\tret = reflog_delete(info->revision.buf,\n>> +\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n>> +\t\t\t    0);\n>>  \tif (!ret) {\n>>  \t\tif (!quiet)\n>>  \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n>\n> I think per the above squashing this in would be nice, i.e. you get rid\n> of the long line & it'sclear that \"ret\" is not used for anything now:\n>\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index d0967b3d3c3..7b939576720 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -635,11 +635,9 @@ static int reflog_is_empty(const char *refname)\n>\n>  static int do_drop_stash(struct stash_info *info, int quiet)\n>  {\n> -\tint ret;\n> -\tret = reflog_delete(info->revision.buf,\n> -\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n> -\t\t\t    0);\n> -\tif (!ret) {\n> +\tunsigned int flags = EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE;\n> +\n> +\tif (!reflog_delete(info->revision.buf, flags, 0)) {\n>  \t\tif (!quiet)\n>  \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n>  \t\t\t\t  oid_to_hex(&info->w_commit));\n>\n> But, having written that I notice that we have *_REWRITE twice there, so\n> I almost just carried forward a new bug in 3/3 when composing this :)\n>\n> So one should be EXPIRE_REFLOGS_UPDATE_REF, presumably.\n>\n> And perhaps it's a big pain, but that suggests that the code isn't\n> either used at all, or that we're missing a test for it.\n>\n> So adding a prep commit to this series where we either drop it, or add\n> the missing test would be a very nice addition.\n>\n> See my quite recent 5ac15ad2509 (reflog tests: add --updateref tests,\n> 2021-10-16) for adding such tests, but in that case I just covered \"git\n> reflog\" itself, not \"git stash\". Maybe we can just add something to that\n> for-loop in t1417 (or similar for a new stash test).\n\nSo I was trying to write a test to exercise the reflog --updateref and --rewrite\ncases. --updateref is pretty straightforward, but with --rewrite I noticed that\nit rewrites the old sha in the .git/log/refs/stash file. But, I was having\ntrouble finding somewhere that read this value. The test could reach into this file\nand check the literal contents, but wondering if there is a better way. Any help appreciated!\n\n>\n> Also, a s/unsigned int flags/enum expire_reflog_flags/ while we're at it\n> would be very nice here, but that could be done as another small prep\n> commit. I.e. it's *not* a new issue since cmd_reflog_delete() had it\n> before, but when converting this to a documented API it would be very\n> nice to have it reflect the actual type we end up using.\n"},{"id":"449109","messageId":"220222.86fsob88h7.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"DAF4762E-6376-44BC-9E8C-B9C32B65D92B@gmail.com","subject":"Re: [PATCH 3/3] stash: call reflog_delete from reflog.c","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-22T10:51:19Z","receivedAt":"2022-02-22T11:08:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Feb 21 2022, John Cai wrote:\n\n> Hi Ævar,\n>\n> On 18 Feb 2022, at 14:20, Ævar Arnfjörð Bjarmason wrote:\n>\n>> On Fri, Feb 18 2022, John Cai via GitGitGadget wrote:\n>>\n>>> From: John Cai <johncai86@gmail.com>\n>>>\n>>> Now that cmd_reflog_delete has been libified an exported it into a new\n>>> reflog.c library so we can call it directly from builtin/stash.c. This\n>>> not only gives us a performance gain since we don't need to create a\n>>> subprocess, but it also allows us to use the ref transactions api in the\n>>> future.\n>>>\n>>> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>>> Signed-off-by: John Cai <johncai86@gmail.com>\n>>\n>> Very nicely done, and nice that despite the ~500 lines added/removed in\n>> the diffstat that the \"actual\" changes in this series are so small.q\n>>\n>>> @@ -635,18 +636,9 @@ static int reflog_is_empty(const char *refname)\n>>>  static int do_drop_stash(struct stash_info *info, int quiet)\n>>>  {\n>>>  \tint ret;\n>>> -\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n>>> -\n>>\n>> Nit: We usually separate variables decls with a \\n\\n, as is done in the\n>> pre-image, but you end up dropping that.\n>>\n>>> -\t/*\n>>> -\t * reflog does not provide a simple function for deleting refs. One will\n>>> -\t * need to be added to avoid implementing too much reflog code here\n>>> -\t */\n>>> -\n>>> -\tcp_reflog.git_cmd = 1;\n>>> -\tstrvec_pushl(&cp_reflog.args, \"reflog\", \"delete\", \"--updateref\",\n>>> -\t\t     \"--rewrite\", NULL);\n>>> -\tstrvec_push(&cp_reflog.args, info->revision.buf);\n>>> -\tret = run_command(&cp_reflog);\n>>> +\tret = reflog_delete(info->revision.buf,\n>>> +\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n>>> +\t\t\t    0);\n>>>  \tif (!ret) {\n>>>  \t\tif (!quiet)\n>>>  \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n>>\n>> I think per the above squashing this in would be nice, i.e. you get rid\n>> of the long line & it'sclear that \"ret\" is not used for anything now:\n>>\n>> diff --git a/builtin/stash.c b/builtin/stash.c\n>> index d0967b3d3c3..7b939576720 100644\n>> --- a/builtin/stash.c\n>> +++ b/builtin/stash.c\n>> @@ -635,11 +635,9 @@ static int reflog_is_empty(const char *refname)\n>>\n>>  static int do_drop_stash(struct stash_info *info, int quiet)\n>>  {\n>> -\tint ret;\n>> -\tret = reflog_delete(info->revision.buf,\n>> -\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n>> -\t\t\t    0);\n>> -\tif (!ret) {\n>> +\tunsigned int flags = EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE;\n>> +\n>> +\tif (!reflog_delete(info->revision.buf, flags, 0)) {\n>>  \t\tif (!quiet)\n>>  \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n>>  \t\t\t\t  oid_to_hex(&info->w_commit));\n>>\n>> But, having written that I notice that we have *_REWRITE twice there, so\n>> I almost just carried forward a new bug in 3/3 when composing this :)\n>>\n>> So one should be EXPIRE_REFLOGS_UPDATE_REF, presumably.\n>>\n>> And perhaps it's a big pain, but that suggests that the code isn't\n>> either used at all, or that we're missing a test for it.\n>>\n>> So adding a prep commit to this series where we either drop it, or add\n>> the missing test would be a very nice addition.\n>>\n>> See my quite recent 5ac15ad2509 (reflog tests: add --updateref tests,\n>> 2021-10-16) for adding such tests, but in that case I just covered \"git\n>> reflog\" itself, not \"git stash\". Maybe we can just add something to that\n>> for-loop in t1417 (or similar for a new stash test).\n>\n> So I was trying to write a test to exercise the reflog --updateref and --rewrite\n> cases. --updateref is pretty straightforward, but with --rewrite I noticed that\n> it rewrites the old sha in the .git/log/refs/stash file. But, I was having\n> trouble finding somewhere that read this value. The test could reach into this file\n> and check the literal contents, but wondering if there is a better way. Any help appreciated!\n\nI can check it out, but to make that easier can you share the WIP diff\nyou have for getting the test setup that far?\n\nI think you mean that it munges the SHA-1 on the LHS of\n.git/refs/logs/stash, I tried to do that now locally and I didn't come\nup with some way where you could observably make \"git stash show\", \"git\nstash list\" etc. show anything different.\n\nI.e. it'll just promiscuously show whatever OID is on the RHS, even in\ncases where some of them are turned into \"deadbeef...\" (i.e. \"list\", but\n\"show\" will die).\n\nSo maybe it's just observable with a subsequent \"git fsck\", but I just\ntried composing a reflog with these entries:\n\n    0000000000000000000000000000000000000000 A\n    A B\n    B C\n\nAnd then manually changing it to:\n\n    0000000000000000000000000000000000000000 A\n    A B\n    A C\n\nWhich \"git fsck\" will pass, and only complain if that \"A\" isn't a valid\nOID at all.\n\nAnyway, if there isn't a way to get fsck/reflog to spew it out, but we\nwant to assert that it's correct wouldn't this catch it (will need to\ndepend on REFFILES) (untested):\n\n\tcut -d' ' -f1-2 .git/logs/refs/stash >actual &&\n\tcat >expect <<-EOF &&\n\t$(test_oid zero) $(git rev-parse ...)\n\t$(git rev-parse ...) $(git rev-parse ...)\n\tEOF\n\ttest_cmp expect actual\n\nI.e. to simply run whatever operation we do now, check that the OIDs\nmatch what we expect, and which would be different if one of these flags\nwasn't given?\n\n>>\n>> Also, a s/unsigned int flags/enum expire_reflog_flags/ while we're at it\n>> would be very nice here, but that could be done as another small prep\n>> commit. I.e. it's *not* a new issue since cmd_reflog_delete() had it\n>> before, but when converting this to a documented API it would be very\n>> nice to have it reflect the actual type we end up using.\n\n"},{"id":"449176","messageId":"pull.1218.v2.git.git.1645554651.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.git.git.1645209647.gitgitgadget@gmail.com","subject":"[PATCH v2 0/3] libify reflog","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-22T18:30:48Z","receivedAt":"2022-02-22T18:31:00Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"In [1], there was a discussion around a bug report of stash not recovering\nin the middle of the process when killed with ctl-c. It turned out to not be\na bug we need to fix. However, out of that discussion came the idea of\nlibifying reflog. This can stand alone as a code improvement.\n\nstash.c currently shells out to call reflog to delete reflogs. Libify reflog\ndelete and call it from both builtin/reflog.c and builtin/stash.c.\n\nThis patch has three parts:\n\n * add missing test coverage for git stash delete\n * libify reflog's delete functionality and move some of the helpers into a\n   reflog.c library and call reflog_delete from builtin/reflog.c\n * call reflog_delete from builtin/stash.c\n\nUpdates since v1:\n\n * added missing test coverage\n * squashed 1/3 and 2/3 together\n * moved enum into reflog.c\n * updated object.h's flag allocation mapping\n\n 1. https://lore.kernel.org/git/220126.86h79qe692.gmgdl@evledraar.gmail.com/\n\nJohn Cai (3):\n  stash: add test to ensure reflog --rewrite --updatref behavior\n  reflog: libify delete reflog function and helpers\n  stash: call reflog_delete() in reflog.c\n\n Makefile         |   1 +\n builtin/reflog.c | 451 +----------------------------------------------\n builtin/stash.c  |  18 +-\n object.h         |   2 +-\n reflog.c         | 435 +++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  49 +++++\n t/t3903-stash.sh |  25 ++-\n 7 files changed, 518 insertions(+), 463 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\n\nbase-commit: e6ebfd0e8cbbd10878070c8a356b5ad1b3ca464e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1218%2Fjohn-cai%2Fjc-libify-reflog-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1218/john-cai/jc-libify-reflog-v2\nPull-Request: https://github.com/git/git/pull/1218\n\nRange-diff vs v1:\n\n -:  ----------- > 1:  6e136b62ca4 stash: add test to ensure reflog --rewrite --updatref behavior\n 1:  9e17ece8d89 ! 2:  e7c950218b1 reflog: libify delete reflog function and helpers\n     @@ builtin/reflog.c: static int cmd_reflog_expire(int argc, const char **argv, cons\n       static const char * reflog_delete_usage[] = {\n       \tN_(\"git reflog delete [--rewrite] [--updateref] \"\n       \t   \"[--dry-run | -n] [--verbose] <refs>...\"),\n     -@@ builtin/reflog.c: static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n     +@@ builtin/reflog.c: static const char * reflog_delete_usage[] = {\n     + \n     + static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n     + {\n     +-\tstruct cmd_reflog_expire_cb cmd = { 0 };\n       \tint i, status = 0;\n       \tunsigned int flags = 0;\n       \tint verbose = 0;\n     +-\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n      +\n     - \treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n       \tconst struct option options[] = {\n       \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\n     + \t\t\tEXPIRE_REFLOGS_DRY_RUN),\n     +@@ builtin/reflog.c: static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n     + \n     + \targc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);\n     + \n     +-\tif (verbose)\n     +-\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n     +-\n     + \tif (argc < 1)\n     + \t\treturn error(_(\"no reflog specified to delete\"));\n     + \n     +-\tfor (i = 0; i < argc; i++) {\n     +-\t\tconst char *spec = strstr(argv[i], \"@{\");\n     +-\t\tchar *ep, *ref;\n     +-\t\tint recno;\n     +-\t\tstruct expire_reflog_policy_cb cb = {\n     +-\t\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n     +-\t\t};\n     +-\n     +-\t\tif (!spec) {\n     +-\t\t\tstatus |= error(_(\"not a reflog: %s\"), argv[i]);\n     +-\t\t\tcontinue;\n     +-\t\t}\n     ++\tfor (i = 0; i < argc; i++)\n     ++\t\tstatus |= reflog_delete(argv[i], flags, verbose);\n     + \n     +-\t\tif (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {\n     +-\t\t\tstatus |= error(_(\"no reflog for '%s'\"), argv[i]);\n     +-\t\t\tcontinue;\n     +-\t\t}\n     +-\n     +-\t\trecno = strtoul(spec + 2, &ep, 10);\n     +-\t\tif (*ep == '}') {\n     +-\t\t\tcmd.recno = -recno;\n     +-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n     +-\t\t} else {\n     +-\t\t\tcmd.expire_total = approxidate(spec + 2);\n     +-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n     +-\t\t\tcmd.expire_total = 0;\n     +-\t\t}\n     +-\n     +-\t\tcb.cmd = cmd;\n     +-\t\tstatus |= reflog_expire(ref, flags,\n     +-\t\t\t\t\treflog_expiry_prepare,\n     +-\t\t\t\t\tshould_prune_fn,\n     +-\t\t\t\t\treflog_expiry_cleanup,\n     +-\t\t\t\t\t&cb);\n     +-\t\tfree(ref);\n     +-\t}\n     + \treturn status;\n     + }\n     + \n     +\n     + ## object.h ##\n     +@@ object.h: struct object_array {\n     +  * builtin/fsck.c:           0--3\n     +  * builtin/gc.c:             0\n     +  * builtin/index-pack.c:                                     2021\n     +- * builtin/reflog.c:                   10--12\n     ++ * reflog.c:                           10--12\n     +  * builtin/show-branch.c:    0-------------------------------------------26\n     +  * builtin/unpack-objects.c:                                 2021\n     +  */\n      \n       ## reflog.c (new) ##\n      @@\n     @@ reflog.c (new)\n      +#include \"tree-walk.h\"\n      +#include \"worktree.h\"\n      +\n     ++/* Remember to update object flag allocation in object.h */\n     ++#define INCOMPLETE\t(1u<<10)\n     ++#define STUDYING\t(1u<<11)\n     ++#define REACHABLE\t(1u<<12)\n     ++\n      +static int tree_is_complete(const struct object_id *oid)\n      +{\n      +\tstruct tree_desc desc;\n     @@ reflog.c (new)\n      +\treturn 0;\n      +}\n      +\n     -+int reflog_delete(const char *rev, int flags, int verbose)\n     ++int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose)\n      +{\n      +\tstruct cmd_reflog_expire_cb cmd = { 0 };\n      +\tint status = 0;\n     @@ reflog.c (new)\n      +\tif (verbose)\n      +\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n      +\n     -+\tif (!spec) {\n     -+\t\tstatus |= error(_(\"not a reflog: %s\"), rev);\n     -+\t}\n     ++\tif (!spec)\n     ++\t\treturn error(_(\"not a reflog: %s\"), rev);\n      +\n      +\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n      +\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n     ++\t\tgoto cleanup;\n      +\t}\n      +\n     -+\tif (status)\n     -+\t\treturn status;\n     -+\n      +\trecno = strtoul(spec + 2, &ep, 10);\n      +\tif (*ep == '}') {\n      +\t\tcmd.recno = -recno;\n     @@ reflog.c (new)\n      +\t\t\t\tshould_prune_fn,\n      +\t\t\t\treflog_expiry_cleanup,\n      +\t\t\t\t&cb);\n     -+\tfree(ref);\n      +\n     ++ cleanup:\n     ++\tfree(ref);\n      +\treturn status;\n      +}\n      \n     @@ reflog.h (new)\n      +#ifndef REFLOG_H\n      +#define REFLOG_H\n      +\n     -+#include \"cache.h\"\n     -+#include \"commit.h\"\n     -+\n     -+/* Remember to update object flag allocation in object.h */\n     -+#define INCOMPLETE\t(1u<<10)\n     -+#define STUDYING\t(1u<<11)\n     -+#define REACHABLE\t(1u<<12)\n     ++#include \"refs.h\"\n      +\n      +struct cmd_reflog_expire_cb {\n      +\tint stalefix;\n     @@ reflog.h (new)\n      +\tunsigned int dry_run:1;\n      +};\n      +\n     -+int reflog_delete(const char*, int, int);\n     -+void reflog_expiry_cleanup(void *);\n     -+void reflog_expiry_prepare(const char*, const struct object_id*,\n     -+\t\t\t   void *);\n     -+int should_expire_reflog_ent(struct object_id *, struct object_id*,\n     -+\t\t\t\t    const char *, timestamp_t, int,\n     -+\t\t\t\t    const char *, void *);\n     ++int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose);\n     ++\n     ++void reflog_expiry_cleanup(void *cb_data);\n     ++\n     ++void reflog_expiry_prepare(const char *refname, const struct object_id *oid,\n     ++\t\t\t   void *cb_data);\n     ++\n     ++int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n     ++\t\t\t     const char *email, timestamp_t timestamp, int tz,\n     ++\t\t\t     const char *message, void *cb_data);\n     ++\n      +int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n     -+\t\tconst char *email, timestamp_t timestamp, int tz,\n     -+\t\tconst char *message, void *cb_data);\n     -+int should_expire_reflog_ent_verbose(struct object_id *,\n     -+\t\t\t\t     struct object_id *,\n     -+\t\t\t\t     const char *,\n     -+\t\t\t\t     timestamp_t, int,\n     -+\t\t\t\t     const char *, void *);\n     ++\t\t     const char *email, timestamp_t timestamp, int tz,\n     ++\t\t     const char *message, void *cb_data);\n     ++\n     ++int should_expire_reflog_ent_verbose(struct object_id *ooid,\n     ++\t\t\t\t     struct object_id *noid,\n     ++\t\t\t\t     const char *email,\n     ++\t\t\t\t     timestamp_t timestamp, int tz,\n     ++\t\t\t\t     const char *message, void *cb_data);\n     ++\n      +#endif /* REFLOG_H */\n 2:  e4c0047a17c < -:  ----------- reflog: call reflog_delete from reflog.c\n 3:  bcc1eae0531 ! 3:  a023a70092b stash: call reflog_delete from reflog.c\n     @@ Metadata\n      Author: John Cai <johncai86@gmail.com>\n      \n       ## Commit message ##\n     -    stash: call reflog_delete from reflog.c\n     +    stash: call reflog_delete() in reflog.c\n      \n          Now that cmd_reflog_delete has been libified an exported it into a new\n          reflog.c library so we can call it directly from builtin/stash.c. This\n     @@ builtin/stash.c\n       #define INCLUDE_ALL_FILES 2\n       \n      @@ builtin/stash.c: static int reflog_is_empty(const char *refname)\n     + \n       static int do_drop_stash(struct stash_info *info, int quiet)\n       {\n     - \tint ret;\n     +-\tint ret;\n      -\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n      -\n      -\t/*\n     @@ builtin/stash.c: static int reflog_is_empty(const char *refname)\n      -\t\t     \"--rewrite\", NULL);\n      -\tstrvec_push(&cp_reflog.args, info->revision.buf);\n      -\tret = run_command(&cp_reflog);\n     -+\tret = reflog_delete(info->revision.buf,\n     -+\t\t\t    EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_REWRITE,\n     -+\t\t\t    0);\n     - \tif (!ret) {\n     +-\tif (!ret) {\n     ++\tif (!reflog_delete(info->revision.buf,\n     ++\t\t\t   EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF,\n     ++\t\t\t   0)) {\n       \t\tif (!quiet)\n       \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n     + \t\t\t\t  oid_to_hex(&info->w_commit));\n\n-- \ngitgitgadget\n"},{"id":"449177","messageId":"6e136b62ca4588cc58f2cb59b635eeaf14e6e20d.1645554652.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v2.git.git.1645554651.gitgitgadget@gmail.com","subject":"[PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-22T18:30:49Z","receivedAt":"2022-02-22T18:31:01Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nThere is missing test coverage to ensure that the resulting reflogs\nafter a git stash drop has had its old oid rewritten if applicable, and\nif the refs/stash has been updated if applicable.\n\nAdd two tests that verify both of these happen.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n t/t3903-stash.sh | 25 ++++++++++++++++++++++++-\n 1 file changed, 24 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex b149e2af441..ec9cc5646d6 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -185,10 +185,33 @@ test_expect_success 'drop middle stash by index' '\n \ttest 1 = $(git show HEAD:file)\n '\n \n+test_expect_success 'drop stash reflog updates refs/stash' '\n+\tgit reset --hard &&\n+\tgit rev-parse refs/stash >expect &&\n+\techo 9 >file &&\n+\tgit stash &&\n+\tgit stash drop stash@{0} &&\n+\tgit rev-parse refs/stash >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n+\tgit reset --hard &&\n+\techo 9 >file &&\n+\tgit stash &&\n+\toid=\"$(git rev-parse stash@{0})\" &&\n+\tgit stash drop stash@{1} &&\n+\tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n+\tcat >expect <<-EOF &&\n+\t$(test_oid zero) $oid\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'stash pop' '\n \tgit reset --hard &&\n \tgit stash pop &&\n-\ttest 3 = $(cat file) &&\n+\ttest 9 = $(cat file) &&\n \ttest 1 = $(git show :file) &&\n \ttest 1 = $(git show HEAD:file) &&\n \ttest 0 = $(git stash list | wc -l)\n-- \ngitgitgadget\n\n"},{"id":"449179","messageId":"e7c950218b1b6b91a3cecedf3d2339230522e2e0.1645554652.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v2.git.git.1645554651.gitgitgadget@gmail.com","subject":"[PATCH v2 2/3] reflog: libify delete reflog function and helpers","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-22T18:30:50Z","receivedAt":"2022-02-22T18:31:04Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nCurrently stash shells out to reflog in order to delete refs. In an\neffort to reduce how much we shell out to a subprocess, libify the\nfunctionality that stash needs into reflog.c.\n\nAdd a reflog_delete function that is pretty much the logic in the while\nloop in builtin/reflog.c cmd_reflog_delete(). This is a function that\nbuiltin/reflog.c and builtin/stash.c can both call.\n\nAlso move functions needed by reflog_delete and export them.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n Makefile         |   1 +\n builtin/reflog.c | 451 +----------------------------------------------\n object.h         |   2 +-\n reflog.c         | 435 +++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  49 +++++\n 5 files changed, 490 insertions(+), 448 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\ndiff --git a/Makefile b/Makefile\nindex 6f0b4b775fe..876d4dfd6cb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -989,6 +989,7 @@ LIB_OBJS += rebase-interactive.o\n LIB_OBJS += rebase.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += reflog-walk.o\n+LIB_OBJS += reflog.o\n LIB_OBJS += refs.o\n LIB_OBJS += refs/debug.o\n LIB_OBJS += refs/files-backend.o\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 85b838720c3..03d347e5832 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -1,16 +1,13 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"lockfile.h\"\n-#include \"object-store.h\"\n #include \"repository.h\"\n-#include \"commit.h\"\n-#include \"refs.h\"\n #include \"dir.h\"\n-#include \"tree-walk.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"reachable.h\"\n #include \"worktree.h\"\n+#include \"reflog.h\"\n \n static const char reflog_exists_usage[] =\n N_(\"git reflog exists <ref>\");\n@@ -18,404 +15,11 @@ N_(\"git reflog exists <ref>\");\n static timestamp_t default_reflog_expire;\n static timestamp_t default_reflog_expire_unreachable;\n \n-struct cmd_reflog_expire_cb {\n-\tint stalefix;\n-\tint explicit_expiry;\n-\ttimestamp_t expire_total;\n-\ttimestamp_t expire_unreachable;\n-\tint recno;\n-};\n-\n-struct expire_reflog_policy_cb {\n-\tenum {\n-\t\tUE_NORMAL,\n-\t\tUE_ALWAYS,\n-\t\tUE_HEAD\n-\t} unreachable_expire_kind;\n-\tstruct commit_list *mark_list;\n-\tunsigned long mark_limit;\n-\tstruct cmd_reflog_expire_cb cmd;\n-\tstruct commit *tip_commit;\n-\tstruct commit_list *tips;\n-\tunsigned int dry_run:1;\n-};\n-\n struct worktree_reflogs {\n \tstruct worktree *worktree;\n \tstruct string_list reflogs;\n };\n \n-/* Remember to update object flag allocation in object.h */\n-#define INCOMPLETE\t(1u<<10)\n-#define STUDYING\t(1u<<11)\n-#define REACHABLE\t(1u<<12)\n-\n-static int tree_is_complete(const struct object_id *oid)\n-{\n-\tstruct tree_desc desc;\n-\tstruct name_entry entry;\n-\tint complete;\n-\tstruct tree *tree;\n-\n-\ttree = lookup_tree(the_repository, oid);\n-\tif (!tree)\n-\t\treturn 0;\n-\tif (tree->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (tree->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\n-\tif (!tree->buffer) {\n-\t\tenum object_type type;\n-\t\tunsigned long size;\n-\t\tvoid *data = read_object_file(oid, &type, &size);\n-\t\tif (!data) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\treturn 0;\n-\t\t}\n-\t\ttree->buffer = data;\n-\t\ttree->size = size;\n-\t}\n-\tinit_tree_desc(&desc, tree->buffer, tree->size);\n-\tcomplete = 1;\n-\twhile (tree_entry(&desc, &entry)) {\n-\t\tif (!has_object_file(&entry.oid) ||\n-\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\tcomplete = 0;\n-\t\t}\n-\t}\n-\tfree_tree_buffer(tree);\n-\n-\tif (complete)\n-\t\ttree->object.flags |= SEEN;\n-\treturn complete;\n-}\n-\n-static int commit_is_complete(struct commit *commit)\n-{\n-\tstruct object_array study;\n-\tstruct object_array found;\n-\tint is_incomplete = 0;\n-\tint i;\n-\n-\t/* early return */\n-\tif (commit->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (commit->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\t/*\n-\t * Find all commits that are reachable and are not marked as\n-\t * SEEN.  Then make sure the trees and blobs contained are\n-\t * complete.  After that, mark these commits also as SEEN.\n-\t * If some of the objects that are needed to complete this\n-\t * commit are missing, mark this commit as INCOMPLETE.\n-\t */\n-\tmemset(&study, 0, sizeof(study));\n-\tmemset(&found, 0, sizeof(found));\n-\tadd_object_array(&commit->object, NULL, &study);\n-\tadd_object_array(&commit->object, NULL, &found);\n-\tcommit->object.flags |= STUDYING;\n-\twhile (study.nr) {\n-\t\tstruct commit *c;\n-\t\tstruct commit_list *parent;\n-\n-\t\tc = (struct commit *)object_array_pop(&study);\n-\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n-\t\t\tc->object.flags |= INCOMPLETE;\n-\n-\t\tif (c->object.flags & INCOMPLETE) {\n-\t\t\tis_incomplete = 1;\n-\t\t\tbreak;\n-\t\t}\n-\t\telse if (c->object.flags & SEEN)\n-\t\t\tcontinue;\n-\t\tfor (parent = c->parents; parent; parent = parent->next) {\n-\t\t\tstruct commit *p = parent->item;\n-\t\t\tif (p->object.flags & STUDYING)\n-\t\t\t\tcontinue;\n-\t\t\tp->object.flags |= STUDYING;\n-\t\t\tadd_object_array(&p->object, NULL, &study);\n-\t\t\tadd_object_array(&p->object, NULL, &found);\n-\t\t}\n-\t}\n-\tif (!is_incomplete) {\n-\t\t/*\n-\t\t * make sure all commits in \"found\" array have all the\n-\t\t * necessary objects.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++) {\n-\t\t\tstruct commit *c =\n-\t\t\t\t(struct commit *)found.objects[i].item;\n-\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n-\t\t\t\tis_incomplete = 1;\n-\t\t\t\tc->object.flags |= INCOMPLETE;\n-\t\t\t}\n-\t\t}\n-\t\tif (!is_incomplete) {\n-\t\t\t/* mark all found commits as complete, iow SEEN */\n-\t\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t\t}\n-\t}\n-\t/* clear flags from the objects we traversed */\n-\tfor (i = 0; i < found.nr; i++)\n-\t\tfound.objects[i].item->flags &= ~STUDYING;\n-\tif (is_incomplete)\n-\t\tcommit->object.flags |= INCOMPLETE;\n-\telse {\n-\t\t/*\n-\t\t * If we come here, we have (1) traversed the ancestry chain\n-\t\t * from the \"commit\" until we reach SEEN commits (which are\n-\t\t * known to be complete), and (2) made sure that the commits\n-\t\t * encountered during the above traversal refer to trees that\n-\t\t * are complete.  Which means that we know *all* the commits\n-\t\t * we have seen during this process are complete.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t}\n-\t/* free object arrays */\n-\tobject_array_clear(&study);\n-\tobject_array_clear(&found);\n-\treturn !is_incomplete;\n-}\n-\n-static int keep_entry(struct commit **it, struct object_id *oid)\n-{\n-\tstruct commit *commit;\n-\n-\tif (is_null_oid(oid))\n-\t\treturn 1;\n-\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!commit)\n-\t\treturn 0;\n-\n-\t/*\n-\t * Make sure everything in this commit exists.\n-\t *\n-\t * We have walked all the objects reachable from the refs\n-\t * and cache earlier.  The commits reachable by this commit\n-\t * must meet SEEN commits -- and then we should mark them as\n-\t * SEEN as well.\n-\t */\n-\tif (!commit_is_complete(commit))\n-\t\treturn 0;\n-\t*it = commit;\n-\treturn 1;\n-}\n-\n-/*\n- * Starting from commits in the cb->mark_list, mark commits that are\n- * reachable from them.  Stop the traversal at commits older than\n- * the expire_limit and queue them back, so that the caller can call\n- * us again to restart the traversal with longer expire_limit.\n- */\n-static void mark_reachable(struct expire_reflog_policy_cb *cb)\n-{\n-\tstruct commit_list *pending;\n-\ttimestamp_t expire_limit = cb->mark_limit;\n-\tstruct commit_list *leftover = NULL;\n-\n-\tfor (pending = cb->mark_list; pending; pending = pending->next)\n-\t\tpending->item->object.flags &= ~REACHABLE;\n-\n-\tpending = cb->mark_list;\n-\twhile (pending) {\n-\t\tstruct commit_list *parent;\n-\t\tstruct commit *commit = pop_commit(&pending);\n-\t\tif (commit->object.flags & REACHABLE)\n-\t\t\tcontinue;\n-\t\tif (parse_commit(commit))\n-\t\t\tcontinue;\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tif (commit->date < expire_limit) {\n-\t\t\tcommit_list_insert(commit, &leftover);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tparent = commit->parents;\n-\t\twhile (parent) {\n-\t\t\tcommit = parent->item;\n-\t\t\tparent = parent->next;\n-\t\t\tif (commit->object.flags & REACHABLE)\n-\t\t\t\tcontinue;\n-\t\t\tcommit_list_insert(commit, &pending);\n-\t\t}\n-\t}\n-\tcb->mark_list = leftover;\n-}\n-\n-static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n-{\n-\t/*\n-\t * We may or may not have the commit yet - if not, look it\n-\t * up using the supplied sha1.\n-\t */\n-\tif (!commit) {\n-\t\tif (is_null_oid(oid))\n-\t\t\treturn 0;\n-\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n-\t\t\t\t\t\t\t1);\n-\n-\t\t/* Not a commit -- keep it */\n-\t\tif (!commit)\n-\t\t\treturn 0;\n-\t}\n-\n-\t/* Reachable from the current ref?  Don't prune. */\n-\tif (commit->object.flags & REACHABLE)\n-\t\treturn 0;\n-\n-\tif (cb->mark_list && cb->mark_limit) {\n-\t\tcb->mark_limit = 0; /* dig down to the root */\n-\t\tmark_reachable(cb);\n-\t}\n-\n-\treturn !(commit->object.flags & REACHABLE);\n-}\n-\n-/*\n- * Return true iff the specified reflog entry should be expired.\n- */\n-static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n-\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit *old_commit, *new_commit;\n-\n-\tif (timestamp < cb->cmd.expire_total)\n-\t\treturn 1;\n-\n-\told_commit = new_commit = NULL;\n-\tif (cb->cmd.stalefix &&\n-\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n-\t\treturn 1;\n-\n-\tif (timestamp < cb->cmd.expire_unreachable) {\n-\t\tswitch (cb->unreachable_expire_kind) {\n-\t\tcase UE_ALWAYS:\n-\t\t\treturn 1;\n-\t\tcase UE_NORMAL:\n-\t\tcase UE_HEAD:\n-\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n-\t\t\t\treturn 1;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\n-\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n-\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n-static int should_expire_reflog_ent_verbose(struct object_id *ooid,\n-\t\t\t\t\t    struct object_id *noid,\n-\t\t\t\t\t    const char *email,\n-\t\t\t\t\t    timestamp_t timestamp, int tz,\n-\t\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tint expire;\n-\n-\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n-\t\t\t\t\t  message, cb);\n-\n-\tif (!expire)\n-\t\tprintf(\"keep %s\", message);\n-\telse if (cb->dry_run)\n-\t\tprintf(\"would prune %s\", message);\n-\telse\n-\t\tprintf(\"prune %s\", message);\n-\n-\treturn expire;\n-}\n-\n-static int push_tip_to_list(const char *refname, const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n-{\n-\tstruct commit_list **list = cb_data;\n-\tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n-\t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!tip_commit)\n-\t\treturn 0;\n-\tcommit_list_insert(tip_commit, list);\n-\treturn 0;\n-}\n-\n-static int is_head(const char *refname)\n-{\n-\tswitch (ref_type(refname)) {\n-\tcase REF_TYPE_OTHER_PSEUDOREF:\n-\tcase REF_TYPE_MAIN_PSEUDOREF:\n-\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n-\t\t\tBUG(\"not a worktree ref: %s\", refname);\n-\t\tbreak;\n-\tdefault:\n-\t\tbreak;\n-\t}\n-\treturn !strcmp(refname, \"HEAD\");\n-}\n-\n-static void reflog_expiry_prepare(const char *refname,\n-\t\t\t\t  const struct object_id *oid,\n-\t\t\t\t  void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\tstruct commit *commit = NULL;\n-\n-\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n-\t\tcb->unreachable_expire_kind = UE_HEAD;\n-\t} else {\n-\t\tcommit = lookup_commit(the_repository, oid);\n-\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n-\t}\n-\n-\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n-\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tcommit_list_insert(commit, &cb->mark_list);\n-\t\t/* For reflog_expiry_cleanup() below */\n-\t\tcb->tip_commit = commit;\n-\t}\n-\tcb->mark_limit = cb->cmd.expire_total;\n-\tmark_reachable(cb);\n-}\n-\n-static void reflog_expiry_cleanup(void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tclear_commit_marks(elem->item, REACHABLE);\n-\t\tfree_commit_list(cb->tips);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n-\t\tbreak;\n-\t}\n-}\n-\n static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)\n {\n \tstruct worktree_reflogs *cb = cb_data;\n@@ -704,16 +308,6 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n-static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\tconst char *email, timestamp_t timestamp, int tz,\n-\t\tconst char *message, void *cb_data)\n-{\n-\tstruct cmd_reflog_expire_cb *cb = cb_data;\n-\tif (!cb->expire_total || timestamp < cb->expire_total)\n-\t\tcb->recno++;\n-\treturn 0;\n-}\n-\n static const char * reflog_delete_usage[] = {\n \tN_(\"git reflog delete [--rewrite] [--updateref] \"\n \t   \"[--dry-run | -n] [--verbose] <refs>...\"),\n@@ -722,11 +316,10 @@ static const char * reflog_delete_usage[] = {\n \n static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n {\n-\tstruct cmd_reflog_expire_cb cmd = { 0 };\n \tint i, status = 0;\n \tunsigned int flags = 0;\n \tint verbose = 0;\n-\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n+\n \tconst struct option options[] = {\n \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\n \t\t\tEXPIRE_REFLOGS_DRY_RUN),\n@@ -742,48 +335,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);\n \n-\tif (verbose)\n-\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n-\n \tif (argc < 1)\n \t\treturn error(_(\"no reflog specified to delete\"));\n \n-\tfor (i = 0; i < argc; i++) {\n-\t\tconst char *spec = strstr(argv[i], \"@{\");\n-\t\tchar *ep, *ref;\n-\t\tint recno;\n-\t\tstruct expire_reflog_policy_cb cb = {\n-\t\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n-\t\t};\n-\n-\t\tif (!spec) {\n-\t\t\tstatus |= error(_(\"not a reflog: %s\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n+\tfor (i = 0; i < argc; i++)\n+\t\tstatus |= reflog_delete(argv[i], flags, verbose);\n \n-\t\tif (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {\n-\t\t\tstatus |= error(_(\"no reflog for '%s'\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\trecno = strtoul(spec + 2, &ep, 10);\n-\t\tif (*ep == '}') {\n-\t\t\tcmd.recno = -recno;\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t} else {\n-\t\t\tcmd.expire_total = approxidate(spec + 2);\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t\tcmd.expire_total = 0;\n-\t\t}\n-\n-\t\tcb.cmd = cmd;\n-\t\tstatus |= reflog_expire(ref, flags,\n-\t\t\t\t\treflog_expiry_prepare,\n-\t\t\t\t\tshould_prune_fn,\n-\t\t\t\t\treflog_expiry_cleanup,\n-\t\t\t\t\t&cb);\n-\t\tfree(ref);\n-\t}\n \treturn status;\n }\n \ndiff --git a/object.h b/object.h\nindex cb556ab7753..a2219464c2b 100644\n--- a/object.h\n+++ b/object.h\n@@ -75,7 +75,7 @@ struct object_array {\n  * builtin/fsck.c:           0--3\n  * builtin/gc.c:             0\n  * builtin/index-pack.c:                                     2021\n- * builtin/reflog.c:                   10--12\n+ * reflog.c:                           10--12\n  * builtin/show-branch.c:    0-------------------------------------------26\n  * builtin/unpack-objects.c:                                 2021\n  */\ndiff --git a/reflog.c b/reflog.c\nnew file mode 100644\nindex 00000000000..8d57dc43503\n--- /dev/null\n+++ b/reflog.c\n@@ -0,0 +1,435 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"object-store.h\"\n+#include \"reachable.h\"\n+#include \"reflog.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"tree-walk.h\"\n+#include \"worktree.h\"\n+\n+/* Remember to update object flag allocation in object.h */\n+#define INCOMPLETE\t(1u<<10)\n+#define STUDYING\t(1u<<11)\n+#define REACHABLE\t(1u<<12)\n+\n+static int tree_is_complete(const struct object_id *oid)\n+{\n+\tstruct tree_desc desc;\n+\tstruct name_entry entry;\n+\tint complete;\n+\tstruct tree *tree;\n+\n+\ttree = lookup_tree(the_repository, oid);\n+\tif (!tree)\n+\t\treturn 0;\n+\tif (tree->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (tree->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\n+\tif (!tree->buffer) {\n+\t\tenum object_type type;\n+\t\tunsigned long size;\n+\t\tvoid *data = read_object_file(oid, &type, &size);\n+\t\tif (!data) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\treturn 0;\n+\t\t}\n+\t\ttree->buffer = data;\n+\t\ttree->size = size;\n+\t}\n+\tinit_tree_desc(&desc, tree->buffer, tree->size);\n+\tcomplete = 1;\n+\twhile (tree_entry(&desc, &entry)) {\n+\t\tif (!has_object_file(&entry.oid) ||\n+\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\tcomplete = 0;\n+\t\t}\n+\t}\n+\tfree_tree_buffer(tree);\n+\n+\tif (complete)\n+\t\ttree->object.flags |= SEEN;\n+\treturn complete;\n+}\n+\n+static int commit_is_complete(struct commit *commit)\n+{\n+\tstruct object_array study;\n+\tstruct object_array found;\n+\tint is_incomplete = 0;\n+\tint i;\n+\n+\t/* early return */\n+\tif (commit->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (commit->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\t/*\n+\t * Find all commits that are reachable and are not marked as\n+\t * SEEN.  Then make sure the trees and blobs contained are\n+\t * complete.  After that, mark these commits also as SEEN.\n+\t * If some of the objects that are needed to complete this\n+\t * commit are missing, mark this commit as INCOMPLETE.\n+\t */\n+\tmemset(&study, 0, sizeof(study));\n+\tmemset(&found, 0, sizeof(found));\n+\tadd_object_array(&commit->object, NULL, &study);\n+\tadd_object_array(&commit->object, NULL, &found);\n+\tcommit->object.flags |= STUDYING;\n+\twhile (study.nr) {\n+\t\tstruct commit *c;\n+\t\tstruct commit_list *parent;\n+\n+\t\tc = (struct commit *)object_array_pop(&study);\n+\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n+\t\t\tc->object.flags |= INCOMPLETE;\n+\n+\t\tif (c->object.flags & INCOMPLETE) {\n+\t\t\tis_incomplete = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t\telse if (c->object.flags & SEEN)\n+\t\t\tcontinue;\n+\t\tfor (parent = c->parents; parent; parent = parent->next) {\n+\t\t\tstruct commit *p = parent->item;\n+\t\t\tif (p->object.flags & STUDYING)\n+\t\t\t\tcontinue;\n+\t\t\tp->object.flags |= STUDYING;\n+\t\t\tadd_object_array(&p->object, NULL, &study);\n+\t\t\tadd_object_array(&p->object, NULL, &found);\n+\t\t}\n+\t}\n+\tif (!is_incomplete) {\n+\t\t/*\n+\t\t * make sure all commits in \"found\" array have all the\n+\t\t * necessary objects.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++) {\n+\t\t\tstruct commit *c =\n+\t\t\t\t(struct commit *)found.objects[i].item;\n+\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n+\t\t\t\tis_incomplete = 1;\n+\t\t\t\tc->object.flags |= INCOMPLETE;\n+\t\t\t}\n+\t\t}\n+\t\tif (!is_incomplete) {\n+\t\t\t/* mark all found commits as complete, iow SEEN */\n+\t\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t\t}\n+\t}\n+\t/* clear flags from the objects we traversed */\n+\tfor (i = 0; i < found.nr; i++)\n+\t\tfound.objects[i].item->flags &= ~STUDYING;\n+\tif (is_incomplete)\n+\t\tcommit->object.flags |= INCOMPLETE;\n+\telse {\n+\t\t/*\n+\t\t * If we come here, we have (1) traversed the ancestry chain\n+\t\t * from the \"commit\" until we reach SEEN commits (which are\n+\t\t * known to be complete), and (2) made sure that the commits\n+\t\t * encountered during the above traversal refer to trees that\n+\t\t * are complete.  Which means that we know *all* the commits\n+\t\t * we have seen during this process are complete.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t}\n+\t/* free object arrays */\n+\tobject_array_clear(&study);\n+\tobject_array_clear(&found);\n+\treturn !is_incomplete;\n+}\n+\n+static int keep_entry(struct commit **it, struct object_id *oid)\n+{\n+\tstruct commit *commit;\n+\n+\tif (is_null_oid(oid))\n+\t\treturn 1;\n+\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!commit)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Make sure everything in this commit exists.\n+\t *\n+\t * We have walked all the objects reachable from the refs\n+\t * and cache earlier.  The commits reachable by this commit\n+\t * must meet SEEN commits -- and then we should mark them as\n+\t * SEEN as well.\n+\t */\n+\tif (!commit_is_complete(commit))\n+\t\treturn 0;\n+\t*it = commit;\n+\treturn 1;\n+}\n+\n+/*\n+ * Starting from commits in the cb->mark_list, mark commits that are\n+ * reachable from them.  Stop the traversal at commits older than\n+ * the expire_limit and queue them back, so that the caller can call\n+ * us again to restart the traversal with longer expire_limit.\n+ */\n+static void mark_reachable(struct expire_reflog_policy_cb *cb)\n+{\n+\tstruct commit_list *pending;\n+\ttimestamp_t expire_limit = cb->mark_limit;\n+\tstruct commit_list *leftover = NULL;\n+\n+\tfor (pending = cb->mark_list; pending; pending = pending->next)\n+\t\tpending->item->object.flags &= ~REACHABLE;\n+\n+\tpending = cb->mark_list;\n+\twhile (pending) {\n+\t\tstruct commit_list *parent;\n+\t\tstruct commit *commit = pop_commit(&pending);\n+\t\tif (commit->object.flags & REACHABLE)\n+\t\t\tcontinue;\n+\t\tif (parse_commit(commit))\n+\t\t\tcontinue;\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tif (commit->date < expire_limit) {\n+\t\t\tcommit_list_insert(commit, &leftover);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tparent = commit->parents;\n+\t\twhile (parent) {\n+\t\t\tcommit = parent->item;\n+\t\t\tparent = parent->next;\n+\t\t\tif (commit->object.flags & REACHABLE)\n+\t\t\t\tcontinue;\n+\t\t\tcommit_list_insert(commit, &pending);\n+\t\t}\n+\t}\n+\tcb->mark_list = leftover;\n+}\n+\n+static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n+{\n+\t/*\n+\t * We may or may not have the commit yet - if not, look it\n+\t * up using the supplied sha1.\n+\t */\n+\tif (!commit) {\n+\t\tif (is_null_oid(oid))\n+\t\t\treturn 0;\n+\n+\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n+\t\t\t\t\t\t\t1);\n+\n+\t\t/* Not a commit -- keep it */\n+\t\tif (!commit)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Reachable from the current ref?  Don't prune. */\n+\tif (commit->object.flags & REACHABLE)\n+\t\treturn 0;\n+\n+\tif (cb->mark_list && cb->mark_limit) {\n+\t\tcb->mark_limit = 0; /* dig down to the root */\n+\t\tmark_reachable(cb);\n+\t}\n+\n+\treturn !(commit->object.flags & REACHABLE);\n+}\n+\n+/*\n+ * Return true iff the specified reflog entry should be expired.\n+ */\n+int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n+\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit *old_commit, *new_commit;\n+\n+\tif (timestamp < cb->cmd.expire_total)\n+\t\treturn 1;\n+\n+\told_commit = new_commit = NULL;\n+\tif (cb->cmd.stalefix &&\n+\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n+\t\treturn 1;\n+\n+\tif (timestamp < cb->cmd.expire_unreachable) {\n+\t\tswitch (cb->unreachable_expire_kind) {\n+\t\tcase UE_ALWAYS:\n+\t\t\treturn 1;\n+\t\tcase UE_NORMAL:\n+\t\tcase UE_HEAD:\n+\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n+\t\t\t\treturn 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+int should_expire_reflog_ent_verbose(struct object_id *ooid,\n+\t\t\t\t\t    struct object_id *noid,\n+\t\t\t\t\t    const char *email,\n+\t\t\t\t\t    timestamp_t timestamp, int tz,\n+\t\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tint expire;\n+\n+\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n+\t\t\t\t\t  message, cb);\n+\n+\tif (!expire)\n+\t\tprintf(\"keep %s\", message);\n+\telse if (cb->dry_run)\n+\t\tprintf(\"would prune %s\", message);\n+\telse\n+\t\tprintf(\"prune %s\", message);\n+\n+\treturn expire;\n+}\n+\n+static int push_tip_to_list(const char *refname, const struct object_id *oid,\n+\t\t\t    int flags, void *cb_data)\n+{\n+\tstruct commit_list **list = cb_data;\n+\tstruct commit *tip_commit;\n+\tif (flags & REF_ISSYMREF)\n+\t\treturn 0;\n+\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!tip_commit)\n+\t\treturn 0;\n+\tcommit_list_insert(tip_commit, list);\n+\treturn 0;\n+}\n+\n+static int is_head(const char *refname)\n+{\n+\tswitch (ref_type(refname)) {\n+\tcase REF_TYPE_OTHER_PSEUDOREF:\n+\tcase REF_TYPE_MAIN_PSEUDOREF:\n+\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n+\t\t\tBUG(\"not a worktree ref: %s\", refname);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\treturn !strcmp(refname, \"HEAD\");\n+}\n+\n+void reflog_expiry_prepare(const char *refname,\n+\t\t\t\t  const struct object_id *oid,\n+\t\t\t\t  void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\tstruct commit *commit = NULL;\n+\n+\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n+\t\tcb->unreachable_expire_kind = UE_HEAD;\n+\t} else {\n+\t\tcommit = lookup_commit(the_repository, oid);\n+\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n+\t}\n+\n+\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n+\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tcommit_list_insert(commit, &cb->mark_list);\n+\t\t/* For reflog_expiry_cleanup() below */\n+\t\tcb->tip_commit = commit;\n+\t}\n+\tcb->mark_limit = cb->cmd.expire_total;\n+\tmark_reachable(cb);\n+}\n+\n+void reflog_expiry_cleanup(void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tclear_commit_marks(elem->item, REACHABLE);\n+\t\tfree_commit_list(cb->tips);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n+\t\tbreak;\n+\t}\n+}\n+\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\tconst char *email, timestamp_t timestamp, int tz,\n+\t\tconst char *message, void *cb_data)\n+{\n+\tstruct cmd_reflog_expire_cb *cb = cb_data;\n+\tif (!cb->expire_total || timestamp < cb->expire_total)\n+\t\tcb->recno++;\n+\treturn 0;\n+}\n+\n+int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose)\n+{\n+\tstruct cmd_reflog_expire_cb cmd = { 0 };\n+\tint status = 0;\n+\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n+\tconst char *spec = strstr(rev, \"@{\");\n+\tchar *ep, *ref;\n+\tint recno;\n+\tstruct expire_reflog_policy_cb cb = {\n+\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n+\t};\n+\n+\tif (verbose)\n+\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n+\n+\tif (!spec)\n+\t\treturn error(_(\"not a reflog: %s\"), rev);\n+\n+\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n+\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n+\t\tgoto cleanup;\n+\t}\n+\n+\trecno = strtoul(spec + 2, &ep, 10);\n+\tif (*ep == '}') {\n+\t\tcmd.recno = -recno;\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t} else {\n+\t\tcmd.expire_total = approxidate(spec + 2);\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t\tcmd.expire_total = 0;\n+\t}\n+\n+\tcb.cmd = cmd;\n+\tstatus |= reflog_expire(ref, flags,\n+\t\t\t\treflog_expiry_prepare,\n+\t\t\t\tshould_prune_fn,\n+\t\t\t\treflog_expiry_cleanup,\n+\t\t\t\t&cb);\n+\n+ cleanup:\n+\tfree(ref);\n+\treturn status;\n+}\ndiff --git a/reflog.h b/reflog.h\nnew file mode 100644\nindex 00000000000..3427021cdc2\n--- /dev/null\n+++ b/reflog.h\n@@ -0,0 +1,49 @@\n+#ifndef REFLOG_H\n+#define REFLOG_H\n+\n+#include \"refs.h\"\n+\n+struct cmd_reflog_expire_cb {\n+\tint stalefix;\n+\tint explicit_expiry;\n+\ttimestamp_t expire_total;\n+\ttimestamp_t expire_unreachable;\n+\tint recno;\n+};\n+\n+struct expire_reflog_policy_cb {\n+\tenum {\n+\t\tUE_NORMAL,\n+\t\tUE_ALWAYS,\n+\t\tUE_HEAD\n+\t} unreachable_expire_kind;\n+\tstruct commit_list *mark_list;\n+\tunsigned long mark_limit;\n+\tstruct cmd_reflog_expire_cb cmd;\n+\tstruct commit *tip_commit;\n+\tstruct commit_list *tips;\n+\tunsigned int dry_run:1;\n+};\n+\n+int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose);\n+\n+void reflog_expiry_cleanup(void *cb_data);\n+\n+void reflog_expiry_prepare(const char *refname, const struct object_id *oid,\n+\t\t\t   void *cb_data);\n+\n+int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t\t     const char *email, timestamp_t timestamp, int tz,\n+\t\t\t     const char *message, void *cb_data);\n+\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t     const char *email, timestamp_t timestamp, int tz,\n+\t\t     const char *message, void *cb_data);\n+\n+int should_expire_reflog_ent_verbose(struct object_id *ooid,\n+\t\t\t\t     struct object_id *noid,\n+\t\t\t\t     const char *email,\n+\t\t\t\t     timestamp_t timestamp, int tz,\n+\t\t\t\t     const char *message, void *cb_data);\n+\n+#endif /* REFLOG_H */\n-- \ngitgitgadget\n\n"},{"id":"449178","messageId":"a023a70092b805be0ca96a8ce3f9afd12d098b52.1645554652.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v2.git.git.1645554651.gitgitgadget@gmail.com","subject":"[PATCH v2 3/3] stash: call reflog_delete() in reflog.c","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-22T18:30:51Z","receivedAt":"2022-02-22T18:31:05Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nNow that cmd_reflog_delete has been libified an exported it into a new\nreflog.c library so we can call it directly from builtin/stash.c. This\nnot only gives us a performance gain since we don't need to create a\nsubprocess, but it also allows us to use the ref transactions api in the\nfuture.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n builtin/stash.c | 18 ++++--------------\n 1 file changed, 4 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 5897febfbec..3e2f478b761 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -17,6 +17,7 @@\n #include \"diffcore.h\"\n #include \"exec-cmd.h\"\n #include \"entry.h\"\n+#include \"reflog.h\"\n \n #define INCLUDE_ALL_FILES 2\n \n@@ -634,20 +635,9 @@ static int reflog_is_empty(const char *refname)\n \n static int do_drop_stash(struct stash_info *info, int quiet)\n {\n-\tint ret;\n-\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n-\n-\t/*\n-\t * reflog does not provide a simple function for deleting refs. One will\n-\t * need to be added to avoid implementing too much reflog code here\n-\t */\n-\n-\tcp_reflog.git_cmd = 1;\n-\tstrvec_pushl(&cp_reflog.args, \"reflog\", \"delete\", \"--updateref\",\n-\t\t     \"--rewrite\", NULL);\n-\tstrvec_push(&cp_reflog.args, info->revision.buf);\n-\tret = run_command(&cp_reflog);\n-\tif (!ret) {\n+\tif (!reflog_delete(info->revision.buf,\n+\t\t\t   EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF,\n+\t\t\t   0)) {\n \t\tif (!quiet)\n \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n \t\t\t\t  oid_to_hex(&info->w_commit));\n-- \ngitgitgadget\n"},{"id":"449229","messageId":"220223.864k4q6jpr.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"6e136b62ca4588cc58f2cb59b635eeaf14e6e20d.1645554652.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-23T08:54:47Z","receivedAt":"2022-02-23T09:00:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Feb 22 2022, John Cai via GitGitGadget wrote:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> There is missing test coverage to ensure that the resulting reflogs\n> after a git stash drop has had its old oid rewritten if applicable, and\n> if the refs/stash has been updated if applicable.\n\nThis test looks good, and if 3/3 is applied and either of the flags\nyou're passing is omitted they'll fail, so we know we have the missing\ncoverage here.\n\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n> ---\n>  t/t3903-stash.sh | 25 ++++++++++++++++++++++++-\n>  1 file changed, 24 insertions(+), 1 deletion(-)\n>\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index b149e2af441..ec9cc5646d6 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -185,10 +185,33 @@ test_expect_success 'drop middle stash by index' '\n>  \ttest 1 = $(git show HEAD:file)\n>  '\n>  \n> +test_expect_success 'drop stash reflog updates refs/stash' '\n> +\tgit reset --hard &&\n> +\tgit rev-parse refs/stash >expect &&\n> +\techo 9 >file &&\n> +\tgit stash &&\n> +\tgit stash drop stash@{0} &&\n> +\tgit rev-parse refs/stash >actual &&\n> +\ttest_cmp expect actual\n> +'\n\nThis one will be portable to the reftable backend.\n\n> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n\nBut as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it\nwas easy to miss) this test will need to depend on REFFILES. So just\nchanging this line to:\n\n    test_expect_success REFFILES 'drop stash[...]'\n\n> +\tgit reset --hard &&\n> +\techo 9 >file &&\n> +\tgit stash &&\n> +\toid=\"$(git rev-parse stash@{0})\" &&\n> +\tgit stash drop stash@{1} &&\n> +\tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t$(test_oid zero) $oid\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n\nThen:\n\n>  test_expect_success 'stash pop' '\n>  \tgit reset --hard &&\n>  \tgit stash pop &&\n> -\ttest 3 = $(cat file) &&\n> +\ttest 9 = $(cat file) &&\n>  \ttest 1 = $(git show :file) &&\n>  \ttest 1 = $(git show HEAD:file) &&\n>  \ttest 0 = $(git stash list | wc -l)\n\nThis test was already a bit broken in needing the preceding tests, but\nit will break now if REFFILES isn't true, which you can reproduce\ne.g. with:\n\n    ./t3903-stash.sh --run=1-16,18-50 -vixd\n\nPerhaps the least sucky solution to that is:\n\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex ec9cc5646d6..1d11c9bda20 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n \tcat >expect <<-EOF &&\n \t$(test_oid zero) $oid\n \tEOF\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\t>dropped-stash\n '\n \n test_expect_success 'stash pop' '\n \tgit reset --hard &&\n \tgit stash pop &&\n-\ttest 9 = $(cat file) &&\n+\tif test -e dropped-stash\n+\tthen\n+\t\ttest 9 = $(cat file)\n+\telse\n+\t\ttest 3 = $(cat file)\n+\tfi &&\n \ttest 1 = $(git show :file) &&\n \ttest 1 = $(git show HEAD:file) &&\n \ttest 0 = $(git stash list | wc -l)\n"},{"id":"449230","messageId":"220223.86zgmi54q6.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"e7c950218b1b6b91a3cecedf3d2339230522e2e0.1645554652.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/3] reflog: libify delete reflog function and helpers","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-23T09:02:54Z","receivedAt":"2022-02-23T09:09:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Feb 22 2022, John Cai via GitGitGadget wrote:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> Currently stash shells out to reflog in order to delete refs. In an\n> effort to reduce how much we shell out to a subprocess, libify the\n> functionality that stash needs into reflog.c.\n>\n> Add a reflog_delete function that is pretty much the logic in the while\n> loop in builtin/reflog.c cmd_reflog_delete(). This is a function that\n> builtin/reflog.c and builtin/stash.c can both call.\n>\n> Also move functions needed by reflog_delete and export them.\n>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n> ---\n>  Makefile         |   1 +\n>  builtin/reflog.c | 451 +----------------------------------------------\n>  object.h         |   2 +-\n>  reflog.c         | 435 +++++++++++++++++++++++++++++++++++++++++++++\n>  reflog.h         |  49 +++++\n>  5 files changed, 490 insertions(+), 448 deletions(-)\n>  create mode 100644 reflog.c\n>  create mode 100644 reflog.h\n>\n> diff --git a/Makefile b/Makefile\n> index 6f0b4b775fe..876d4dfd6cb 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -989,6 +989,7 @@ LIB_OBJS += rebase-interactive.o\n>  LIB_OBJS += rebase.o\n>  LIB_OBJS += ref-filter.o\n>  LIB_OBJS += reflog-walk.o\n> +LIB_OBJS += reflog.o\n>  LIB_OBJS += refs.o\n>  LIB_OBJS += refs/debug.o\n>  LIB_OBJS += refs/files-backend.o\n> diff --git a/builtin/reflog.c b/builtin/reflog.c\n> index 85b838720c3..03d347e5832 100644\n> --- a/builtin/reflog.c\n> +++ b/builtin/reflog.c\n> @@ -1,16 +1,13 @@\n>  #include \"builtin.h\"\n>  #include \"config.h\"\n>  #include \"lockfile.h\"\n> -#include \"object-store.h\"\n>  #include \"repository.h\"\n> -#include \"commit.h\"\n> -#include \"refs.h\"\n>  #include \"dir.h\"\n> -#include \"tree-walk.h\"\n>  #include \"diff.h\"\n>  #include \"revision.h\"\n>  #include \"reachable.h\"\n>  #include \"worktree.h\"\n> +#include \"reflog.h\"\n> [...]\n> diff --git a/reflog.c b/reflog.c\n> new file mode 100644\n> index 00000000000..8d57dc43503\n> --- /dev/null\n> +++ b/reflog.c\n> @@ -0,0 +1,435 @@\n> +#include \"cache.h\"\n> +#include \"commit.h\"\n> +#include \"object-store.h\"\n> +#include \"reachable.h\"\n> +#include \"reflog.h\"\n> +#include \"refs.h\"\n> +#include \"revision.h\"\n> +#include \"tree-walk.h\"\n> +#include \"worktree.h\"\n\nI think you missed some now-redundant headers, and copied over others we\ndidn't need. This compiles for me with this on top:\n\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 03d347e5832..940db196f62 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -1,9 +1,5 @@\n #include \"builtin.h\"\n #include \"config.h\"\n-#include \"lockfile.h\"\n-#include \"repository.h\"\n-#include \"dir.h\"\n-#include \"diff.h\"\n #include \"revision.h\"\n #include \"reachable.h\"\n #include \"worktree.h\"\ndiff --git a/reflog.c b/reflog.c\nindex 8d57dc43503..333fd8708fe 100644\n--- a/reflog.c\n+++ b/reflog.c\n@@ -1,11 +1,8 @@\n #include \"cache.h\"\n-#include \"commit.h\"\n #include \"object-store.h\"\n-#include \"reachable.h\"\n #include \"reflog.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"tree-walk.h\"\n #include \"worktree.h\"\n\nBut perhaps some of those are really \"needed\" but brought in implicitly?\n\n> [...]\n> diff --git a/reflog.h b/reflog.h\n> new file mode 100644\n> index 00000000000..3427021cdc2\n> --- /dev/null\n> +++ b/reflog.h\n> @@ -0,0 +1,49 @@\n> +#ifndef REFLOG_H\n> +#define REFLOG_H\n> +\n> +#include \"refs.h\"\n\nJust a nit but I think the reflog_delete() should be wrapped (ends up at\n80 cols), and the usual style in this project is to not whitespace-pad\nso much, i.e. this on top:\n\ndiff --git a/reflog.h b/reflog.h\nindex 3427021cdc2..d2906fb9f8d 100644\n--- a/reflog.h\n+++ b/reflog.h\n@@ -1,6 +1,5 @@\n #ifndef REFLOG_H\n #define REFLOG_H\n-\n #include \"refs.h\"\n \n struct cmd_reflog_expire_cb {\n@@ -25,25 +24,20 @@ struct expire_reflog_policy_cb {\n \tunsigned int dry_run:1;\n };\n \n-int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose);\n-\n+int reflog_delete(const char *rev, enum expire_reflog_flags flags,\n+\t\t  int verbose);\n void reflog_expiry_cleanup(void *cb_data);\n-\n void reflog_expiry_prepare(const char *refname, const struct object_id *oid,\n \t\t\t   void *cb_data);\n-\n int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n \t\t\t     const char *email, timestamp_t timestamp, int tz,\n \t\t\t     const char *message, void *cb_data);\n-\n int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n \t\t     const char *email, timestamp_t timestamp, int tz,\n \t\t     const char *message, void *cb_data);\n-\n int should_expire_reflog_ent_verbose(struct object_id *ooid,\n \t\t\t\t     struct object_id *noid,\n \t\t\t\t     const char *email,\n \t\t\t\t     timestamp_t timestamp, int tz,\n \t\t\t\t     const char *message, void *cb_data);\n-\n #endif /* REFLOG_H */\n\n\nOther than all that I really can't find anything at all to comment on,\nand I see that all the points raised in previous rounds by others were\naddressed.\n"},{"id":"449329","messageId":"252AF60B-0B06-4AE0-B717-8024030673F2@gmail.com","threadId":"57437","inReplyTo":"220223.86zgmi54q6.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 2/3] reflog: libify delete reflog function and helpers","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-23T18:40:44Z","receivedAt":"2022-02-23T18:40:49Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Ævar,\n\nThanks for the review!\n\nOn 23 Feb 2022, at 4:02, Ævar Arnfjörð Bjarmason wrote:\n\n> On Tue, Feb 22 2022, John Cai via GitGitGadget wrote:\n>\n>> From: John Cai <johncai86@gmail.com>\n>>\n>> Currently stash shells out to reflog in order to delete refs. In an\n>> effort to reduce how much we shell out to a subprocess, libify the\n>> functionality that stash needs into reflog.c.\n>>\n>> Add a reflog_delete function that is pretty much the logic in the while\n>> loop in builtin/reflog.c cmd_reflog_delete(). This is a function that\n>> builtin/reflog.c and builtin/stash.c can both call.\n>>\n>> Also move functions needed by reflog_delete and export them.\n>>\n>> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>> Signed-off-by: John Cai <johncai86@gmail.com>\n>> ---\n>>  Makefile         |   1 +\n>>  builtin/reflog.c | 451 +----------------------------------------------\n>>  object.h         |   2 +-\n>>  reflog.c         | 435 +++++++++++++++++++++++++++++++++++++++++++++\n>>  reflog.h         |  49 +++++\n>>  5 files changed, 490 insertions(+), 448 deletions(-)\n>>  create mode 100644 reflog.c\n>>  create mode 100644 reflog.h\n>>\n>> diff --git a/Makefile b/Makefile\n>> index 6f0b4b775fe..876d4dfd6cb 100644\n>> --- a/Makefile\n>> +++ b/Makefile\n>> @@ -989,6 +989,7 @@ LIB_OBJS += rebase-interactive.o\n>>  LIB_OBJS += rebase.o\n>>  LIB_OBJS += ref-filter.o\n>>  LIB_OBJS += reflog-walk.o\n>> +LIB_OBJS += reflog.o\n>>  LIB_OBJS += refs.o\n>>  LIB_OBJS += refs/debug.o\n>>  LIB_OBJS += refs/files-backend.o\n>> diff --git a/builtin/reflog.c b/builtin/reflog.c\n>> index 85b838720c3..03d347e5832 100644\n>> --- a/builtin/reflog.c\n>> +++ b/builtin/reflog.c\n>> @@ -1,16 +1,13 @@\n>>  #include \"builtin.h\"\n>>  #include \"config.h\"\n>>  #include \"lockfile.h\"\n>> -#include \"object-store.h\"\n>>  #include \"repository.h\"\n>> -#include \"commit.h\"\n>> -#include \"refs.h\"\n>>  #include \"dir.h\"\n>> -#include \"tree-walk.h\"\n>>  #include \"diff.h\"\n>>  #include \"revision.h\"\n>>  #include \"reachable.h\"\n>>  #include \"worktree.h\"\n>> +#include \"reflog.h\"\n>> [...]\n>> diff --git a/reflog.c b/reflog.c\n>> new file mode 100644\n>> index 00000000000..8d57dc43503\n>> --- /dev/null\n>> +++ b/reflog.c\n>> @@ -0,0 +1,435 @@\n>> +#include \"cache.h\"\n>> +#include \"commit.h\"\n>> +#include \"object-store.h\"\n>> +#include \"reachable.h\"\n>> +#include \"reflog.h\"\n>> +#include \"refs.h\"\n>> +#include \"revision.h\"\n>> +#include \"tree-walk.h\"\n>> +#include \"worktree.h\"\n>\n> I think you missed some now-redundant headers, and copied over others we\n> didn't need. This compiles for me with this on top:\n\nAh yeah, looks I left these in by mistake. Thanks for catching this.\n\n>\n> diff --git a/builtin/reflog.c b/builtin/reflog.c\n> index 03d347e5832..940db196f62 100644\n> --- a/builtin/reflog.c\n> +++ b/builtin/reflog.c\n> @@ -1,9 +1,5 @@\n>  #include \"builtin.h\"\n>  #include \"config.h\"\n> -#include \"lockfile.h\"\n> -#include \"repository.h\"\n> -#include \"dir.h\"\n> -#include \"diff.h\"\n>  #include \"revision.h\"\n>  #include \"reachable.h\"\n>  #include \"worktree.h\"\n> diff --git a/reflog.c b/reflog.c\n> index 8d57dc43503..333fd8708fe 100644\n> --- a/reflog.c\n> +++ b/reflog.c\n> @@ -1,11 +1,8 @@\n>  #include \"cache.h\"\n> -#include \"commit.h\"\n>  #include \"object-store.h\"\n> -#include \"reachable.h\"\n>  #include \"reflog.h\"\n>  #include \"refs.h\"\n>  #include \"revision.h\"\n> -#include \"tree-walk.h\"\n>  #include \"worktree.h\"\n>\n> But perhaps some of those are really \"needed\" but brought in implicitly?\n>\n>> [...]\n>> diff --git a/reflog.h b/reflog.h\n>> new file mode 100644\n>> index 00000000000..3427021cdc2\n>> --- /dev/null\n>> +++ b/reflog.h\n>> @@ -0,0 +1,49 @@\n>> +#ifndef REFLOG_H\n>> +#define REFLOG_H\n>> +\n>> +#include \"refs.h\"\n>\n> Just a nit but I think the reflog_delete() should be wrapped (ends up at\n> 80 cols), and the usual style in this project is to not whitespace-pad\n> so much, i.e. this on top:\n\nsounds good!\n\n>\n> diff --git a/reflog.h b/reflog.h\n> index 3427021cdc2..d2906fb9f8d 100644\n> --- a/reflog.h\n> +++ b/reflog.h\n> @@ -1,6 +1,5 @@\n>  #ifndef REFLOG_H\n>  #define REFLOG_H\n> -\n>  #include \"refs.h\"\n>\n>  struct cmd_reflog_expire_cb {\n> @@ -25,25 +24,20 @@ struct expire_reflog_policy_cb {\n>  \tunsigned int dry_run:1;\n>  };\n>\n> -int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose);\n> -\n> +int reflog_delete(const char *rev, enum expire_reflog_flags flags,\n> +\t\t  int verbose);\n>  void reflog_expiry_cleanup(void *cb_data);\n> -\n>  void reflog_expiry_prepare(const char *refname, const struct object_id *oid,\n>  \t\t\t   void *cb_data);\n> -\n>  int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n>  \t\t\t     const char *email, timestamp_t timestamp, int tz,\n>  \t\t\t     const char *message, void *cb_data);\n> -\n>  int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n>  \t\t     const char *email, timestamp_t timestamp, int tz,\n>  \t\t     const char *message, void *cb_data);\n> -\n>  int should_expire_reflog_ent_verbose(struct object_id *ooid,\n>  \t\t\t\t     struct object_id *noid,\n>  \t\t\t\t     const char *email,\n>  \t\t\t\t     timestamp_t timestamp, int tz,\n>  \t\t\t\t     const char *message, void *cb_data);\n> -\n>  #endif /* REFLOG_H */\n>\n>\n> Other than all that I really can't find anything at all to comment on,\n> and I see that all the points raised in previous rounds by others were\n> addressed.\n"},{"id":"449332","messageId":"F889CE8D-BFFD-4E3D-8CD1-0F2A1948805F@gmail.com","threadId":"57437","inReplyTo":"YhLv9P9VFQ6Iwv41@nand.local","subject":"Re: [PATCH 1/3] reflog: libify delete reflog function and helpers","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-23T19:50:11Z","receivedAt":"2022-02-23T19:50:15Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Taylor,\n\nOn 20 Feb 2022, at 20:50, Taylor Blau wrote:\n\n> On Sun, Feb 20, 2022 at 08:43:14PM -0500, John Cai wrote:\n>>> One question that I had which I don't see answered already is what the\n>>> plan is for existing reflog-related functions that live in refs.h.\n>>> Should or will those functions be moved to the new reflog-specific\n>>> header, too?\n>>\n>> Thanks for bringing ths up! Maybe this cleanup can be included in a separate\n>> patch series since this one is mainly about getting rid of the shell-out in\n>> stash.c. What do you think?\n>\n> Yeah; I think that's fine. We don't need to get all reflog-related\n> functions moved into reflog.h in the first series, so I think it's fine\n> to move things as you discover them.\n>\n> I was just wondering whether it was something you planned to do at some\n> point (as part of this series, or a future one).\n\nYes I will open a new series for this. I've been looking through refs.h, and as you\nalluded to there are a number of reflog related functions. But, they also relate to refs.\nI'm not sure which ones should go into reflog.h versus staying in refs.h. What are your\nthoughts?\n\n>\n> Thanks,\n> Taylor\n\nThanks!\nJohn\n"},{"id":"449344","messageId":"xmqq4k4ptgsv.fsf@gitster.g","threadId":"57437","inReplyTo":"220223.864k4q6jpr.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-23T21:27:12Z","receivedAt":"2022-02-23T21:27:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> This test was already a bit broken in needing the preceding tests, but\n> it will break now if REFFILES isn't true, which you can reproduce\n> e.g. with:\n>\n>     ./t3903-stash.sh --run=1-16,18-50 -vixd\n>\n> Perhaps the least sucky solution to that is:\n>\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index ec9cc5646d6..1d11c9bda20 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>  \tcat >expect <<-EOF &&\n>  \t$(test_oid zero) $oid\n>  \tEOF\n> -\ttest_cmp expect actual\n> +\ttest_cmp expect actual &&\n> +\t>dropped-stash\n>  '\n\nIf \"git stash drop\", invoked in earlier part of this test before the\nprecontext, fails, then test_cmp would fail and we leave\ndropped-stash untouched, even though we did run \"git stash drop\"\nalready.\n\nWhy does the next test need to depend on what has happened earlier?\n\n>  test_expect_success 'stash pop' '\n>  \tgit reset --hard &&\n>  \tgit stash pop &&\n> -\ttest 9 = $(cat file) &&\n> +\tif test -e dropped-stash\n> +\tthen\n> +\t\ttest 9 = $(cat file)\n> +\telse\n> +\t\ttest 3 = $(cat file)\n> +\tfi &&\n>  \ttest 1 = $(git show :file) &&\n>  \ttest 1 = $(git show HEAD:file) &&\n>  \ttest 0 = $(git stash list | wc -l)\n"},{"id":"449345","messageId":"xmqqzgmhs265.fsf@gitster.g","threadId":"57437","inReplyTo":"e7c950218b1b6b91a3cecedf3d2339230522e2e0.1645554652.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/3] reflog: libify delete reflog function and helpers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-23T21:28:34Z","receivedAt":"2022-02-23T21:28:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> Currently stash shells out to reflog in order to delete refs. In an\n> effort to reduce how much we shell out to a subprocess, libify the\n> functionality that stash needs into reflog.c.\n>\n> Add a reflog_delete function that is pretty much the logic in the while\n> loop in builtin/reflog.c cmd_reflog_delete(). This is a function that\n> builtin/reflog.c and builtin/stash.c can both call.\n>\n> Also move functions needed by reflog_delete and export them.\n>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n> ---\n>  Makefile         |   1 +\n>  builtin/reflog.c | 451 +----------------------------------------------\n>  object.h         |   2 +-\n>  reflog.c         | 435 +++++++++++++++++++++++++++++++++++++++++++++\n>  reflog.h         |  49 +++++\n>  5 files changed, 490 insertions(+), 448 deletions(-)\n>  create mode 100644 reflog.c\n>  create mode 100644 reflog.h\n\nThis round, without polluting global namespace in reflog.h by moving\ntoo many things there and instead keeping what is private to the\nimplementation in reflog.c, looks much better than the previous\nround.\n\nLooking good.\n\nThanks.\n"},{"id":"449350","messageId":"8A0E2968-8324-4A73-B857-F5D8E5C5EACD@gmail.com","threadId":"57437","inReplyTo":"xmqq4k4ptgsv.fsf@gitster.g","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-23T21:50:02Z","receivedAt":"2022-02-23T21:50:06Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Junio,\n\nOn 23 Feb 2022, at 16:27, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> This test was already a bit broken in needing the preceding tests, but\n>> it will break now if REFFILES isn't true, which you can reproduce\n>> e.g. with:\n>>\n>>     ./t3903-stash.sh --run=1-16,18-50 -vixd\n>>\n>> Perhaps the least sucky solution to that is:\n>>\n>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>> index ec9cc5646d6..1d11c9bda20 100755\n>> --- a/t/t3903-stash.sh\n>> +++ b/t/t3903-stash.sh\n>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>  \tcat >expect <<-EOF &&\n>>  \t$(test_oid zero) $oid\n>>  \tEOF\n>> -\ttest_cmp expect actual\n>> +\ttest_cmp expect actual &&\n>> +\t>dropped-stash\n>>  '\n>\n> If \"git stash drop\", invoked in earlier part of this test before the\n> precontext, fails, then test_cmp would fail and we leave\n> dropped-stash untouched, even though we did run \"git stash drop\"\n> already.\n>\n> Why does the next test need to depend on what has happened earlier?\n\nIdeally it shouldn't, but it seems like the way these test have been written\nmakes subsequent tests depend on what previous tests have stashed.\n\nI'm wondering now that while we're at it, if we should just clean up these tests\nso there are no dependencies between the tests. Otherwise it's quite painful the\nnext time someone needs to add a test here.\n\nWe could follow the pattern in 5ac15ad2509 (reflog tests: add --updateref tests,\n2021-10-16), where Ævar set up the tests and copied over the repo so each test\nis isolated from each other.\n\n>\n>>  test_expect_success 'stash pop' '\n>>  \tgit reset --hard &&\n>>  \tgit stash pop &&\n>> -\ttest 9 = $(cat file) &&\n>> +\tif test -e dropped-stash\n>> +\tthen\n>> +\t\ttest 9 = $(cat file)\n>> +\telse\n>> +\t\ttest 3 = $(cat file)\n>> +\tfi &&\n>>  \ttest 1 = $(git show :file) &&\n>>  \ttest 1 = $(git show HEAD:file) &&\n>>  \ttest 0 = $(git stash list | wc -l)\n"},{"id":"449352","messageId":"220223.86ley1b653.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"xmqq4k4ptgsv.fsf@gitster.g","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-23T21:50:02Z","receivedAt":"2022-02-23T21:54:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 23 2022, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> This test was already a bit broken in needing the preceding tests, but\n>> it will break now if REFFILES isn't true, which you can reproduce\n>> e.g. with:\n>>\n>>     ./t3903-stash.sh --run=1-16,18-50 -vixd\n>>\n>> Perhaps the least sucky solution to that is:\n>>\n>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>> index ec9cc5646d6..1d11c9bda20 100755\n>> --- a/t/t3903-stash.sh\n>> +++ b/t/t3903-stash.sh\n>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>  \tcat >expect <<-EOF &&\n>>  \t$(test_oid zero) $oid\n>>  \tEOF\n>> -\ttest_cmp expect actual\n>> +\ttest_cmp expect actual &&\n>> +\t>dropped-stash\n>>  '\n>\n> If \"git stash drop\", invoked in earlier part of this test before the\n> precontext, fails, then test_cmp would fail and we leave\n> dropped-stash untouched, even though we did run \"git stash drop\"\n> already.\n\nYes, that's an edge case that's exposed here, but which I thought wasn't\nworth bothering with. I.e. if you get such a failure on test N getting\nN+1 failing as well isn't that big of a deal.\n\nThe big deal is rather that we know we're adding a REFFILES dependency\nto this, which won't run this at all, which will make the \"stash pop\"\nbelow fail.\n\n> Why does the next test need to depend on what has happened earlier?\n\nThey don't need to, and ideally wouldn't, but most of our test suite has\nthis issue already. Try e.g. running it with:\n\n    prove t[0-9]*.sh :: --run=10-20 --immediate\n\nAnd for this particular file running e.g. this on master:\n\n    ./t3903-stash.sh --run=1-10,30-40\n\nWill fail 7 tests in the 30-40 range.\n\nSo while it's ideal that we can combine tests with arbitrary --run\nparameters, i.e. all tests would tear down fully, not depend on earlier\ntests etc. we're really far from that being the case in practice.\n\nSo insisting on some general refactoring of this file as part of this\nseries seems a bit overzelous, which is why I'm suggesting the bare\nminimum to expect and work around the inevitable REFFILES failure, as\nHan-Wen is actively working in that area.\n\n>>  test_expect_success 'stash pop' '\n>>  \tgit reset --hard &&\n>>  \tgit stash pop &&\n>> -\ttest 9 = $(cat file) &&\n>> +\tif test -e dropped-stash\n>> +\tthen\n>> +\t\ttest 9 = $(cat file)\n>> +\telse\n>> +\t\ttest 3 = $(cat file)\n>> +\tfi &&\n>>  \ttest 1 = $(git show :file) &&\n>>  \ttest 1 = $(git show HEAD:file) &&\n>>  \ttest 0 = $(git stash list | wc -l)\n\n"},{"id":"449361","messageId":"xmqqbkyxqjrq.fsf@gitster.g","threadId":"57437","inReplyTo":"220223.864k4q6jpr.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-23T22:51:21Z","receivedAt":"2022-02-23T22:51:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n>> +test_expect_success 'drop stash reflog updates refs/stash' '\n>> +\tgit reset --hard &&\n>> +\tgit rev-parse refs/stash >expect &&\n>> +\techo 9 >file &&\n>> +\tgit stash &&\n>> +\tgit stash drop stash@{0} &&\n>> +\tgit rev-parse refs/stash >actual &&\n>> +\ttest_cmp expect actual\n>> +'\n>\n> This one will be portable to the reftable backend.\n>\n>> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>\n> But as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it\n> was easy to miss) this test will need to depend on REFFILES. So just\n> changing this line to:\n>\n>     test_expect_success REFFILES 'drop stash[...]'\n>\n>> +\tgit reset --hard &&\n>> +\techo 9 >file &&\n>> +\tgit stash &&\n>> +\toid=\"$(git rev-parse stash@{0})\" &&\n>> +\tgit stash drop stash@{1} &&\n>> +\tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n>> +\tcat >expect <<-EOF &&\n>> +\t$(test_oid zero) $oid\n>> +\tEOF\n>> +\ttest_cmp expect actual\n>> +'\n\nWhy should this be tested with \"cut\" in the first place, though?\n\nIf we start from\n\n    stash@{0} = A\n    stash@{1} = B\n    stash@{2} = C\n\nand after saying \"drop stash@{1}\", what we need to check is that\n\n    stash@{0} = A\n    stash@{1} = C\n\nnow holds, which can be done with \"git rev-parse\", and the fact that\nthe ref-files backend happens to record both before-and-after object\nIDs is an irrelevant implementation detail, no?\n\nI am still puzzled.\n\n\n"},{"id":"449364","messageId":"272D2409-1CAE-4203-96F7-9B104F7E5D4D@gmail.com","threadId":"57437","inReplyTo":"xmqqbkyxqjrq.fsf@gitster.g","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-23T23:12:51Z","receivedAt":"2022-02-23T23:13:00Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Junio,\n\nOn 23 Feb 2022, at 17:51, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>>> +test_expect_success 'drop stash reflog updates refs/stash' '\n>>> +\tgit reset --hard &&\n>>> +\tgit rev-parse refs/stash >expect &&\n>>> +\techo 9 >file &&\n>>> +\tgit stash &&\n>>> +\tgit stash drop stash@{0} &&\n>>> +\tgit rev-parse refs/stash >actual &&\n>>> +\ttest_cmp expect actual\n>>> +'\n>>\n>> This one will be portable to the reftable backend.\n>>\n>>> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>\n>> But as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it\n>> was easy to miss) this test will need to depend on REFFILES. So just\n>> changing this line to:\n>>\n>>     test_expect_success REFFILES 'drop stash[...]'\n>>\n>>> +\tgit reset --hard &&\n>>> +\techo 9 >file &&\n>>> +\tgit stash &&\n>>> +\toid=\"$(git rev-parse stash@{0})\" &&\n>>> +\tgit stash drop stash@{1} &&\n>>> +\tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n>>> +\tcat >expect <<-EOF &&\n>>> +\t$(test_oid zero) $oid\n>>> +\tEOF\n>>> +\ttest_cmp expect actual\n>>> +'\n>\n> Why should this be tested with \"cut\" in the first place, though?\n>\n> If we start from\n>\n>     stash@{0} = A\n>     stash@{1} = B\n>     stash@{2} = C\n>\n> and after saying \"drop stash@{1}\", what we need to check is that\n>\n>     stash@{0} = A\n>     stash@{1} = C\n\nYes, this is true but that doesn't seem to test the --rewrite functionality.\nI could be missing something, but it seems that the reflog --rewrite option\nwill write the LHS old oid value in the .git/logs/refs/stash file. When\n--rewrite isn't used, the reflog delete still does the right thing to the\nRHS entry.\n\nI couldn't find any way to check this LFS value other than reaching into the\nactual file. If there is a way that would be preferable.\n>\n> now holds, which can be done with \"git rev-parse\", and the fact that\n> the ref-files backend happens to record both before-and-after object\n> IDs is an irrelevant implementation detail, no?\n>\n> I am still puzzled.\n"},{"id":"449375","messageId":"xmqqczjdp2g8.fsf@gitster.g","threadId":"57437","inReplyTo":"272D2409-1CAE-4203-96F7-9B104F7E5D4D@gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-23T23:50:47Z","receivedAt":"2022-02-23T23:50:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"John Cai <johncai86@gmail.com> writes:\n\n> Yes, this is true but that doesn't seem to test the --rewrite functionality.\n> I could be missing something, but it seems that the reflog --rewrite option\n> will write the LHS old oid value in the .git/logs/refs/stash file. When\n> --rewrite isn't used, the reflog delete still does the right thing to the\n> RHS entry.\n>\n> I couldn't find any way to check this LFS value other than reaching into the\n> actual file. If there is a way that would be preferable.\n\nAh, that one.\n\nAs 2b81fab2 (git-reflog: add option --rewrite to update reflog\nentries while expiring, 2008-02-22) says, the redundant half of the\nreflog entry only matters to \"certain sanity checks\" and would not\nbe even visible to normal ref API users.  I wonder why we need to\neven say \"--rewrite\" in the first place.  Perhaps we should\nimplicitly set it always and eventually deprecate that option.\n\n\n\n"},{"id":"449376","messageId":"220224.868ru1b0ku.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"272D2409-1CAE-4203-96F7-9B104F7E5D4D@gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-23T23:27:51Z","receivedAt":"2022-02-23T23:55:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 23 2022, John Cai wrote:\n\n> Hi Junio,\n>\n> On 23 Feb 2022, at 17:51, Junio C Hamano wrote:\n>\n>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>>\n>>>> +test_expect_success 'drop stash reflog updates refs/stash' '\n>>>> +\tgit reset --hard &&\n>>>> +\tgit rev-parse refs/stash >expect &&\n>>>> +\techo 9 >file &&\n>>>> +\tgit stash &&\n>>>> +\tgit stash drop stash@{0} &&\n>>>> +\tgit rev-parse refs/stash >actual &&\n>>>> +\ttest_cmp expect actual\n>>>> +'\n>>>\n>>> This one will be portable to the reftable backend.\n>>>\n>>>> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>>\n>>> But as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it\n>>> was easy to miss) this test will need to depend on REFFILES. So just\n>>> changing this line to:\n>>>\n>>>     test_expect_success REFFILES 'drop stash[...]'\n>>>\n>>>> +\tgit reset --hard &&\n>>>> +\techo 9 >file &&\n>>>> +\tgit stash &&\n>>>> +\toid=\"$(git rev-parse stash@{0})\" &&\n>>>> +\tgit stash drop stash@{1} &&\n>>>> +\tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n>>>> +\tcat >expect <<-EOF &&\n>>>> +\t$(test_oid zero) $oid\n>>>> +\tEOF\n>>>> +\ttest_cmp expect actual\n>>>> +'\n>>\n>> Why should this be tested with \"cut\" in the first place, though?\n>>\n>> If we start from\n>>\n>>     stash@{0} = A\n>>     stash@{1} = B\n>>     stash@{2} = C\n>>\n>> and after saying \"drop stash@{1}\", what we need to check is that\n>>\n>>     stash@{0} = A\n>>     stash@{1} = C\n>\n> Yes, this is true but that doesn't seem to test the --rewrite functionality.\n> I could be missing something, but it seems that the reflog --rewrite option\n> will write the LHS old oid value in the .git/logs/refs/stash file. When\n> --rewrite isn't used, the reflog delete still does the right thing to the\n> RHS entry.\n>\n> I couldn't find any way to check this LFS value other than reaching into the\n> actual file. If there is a way that would be preferable.\n\nThanks for that summary that's accurate as far as I know. I think that's\nhow this all works, and I don't know of another way to extract this\ninformation than this reaching behind the curtain.\n\nWhich, I think is a lot clearer if we amend the test like this. Note\nthat this doesn't really add anything for catching a regression goes,\nbut I think helps guide the human reader through this step-by-step. So\nperhaps it would be good to fix the test up to have it (or maybe not):\n\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex ec9cc5646d6..bc58e99e3e6 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -198,12 +198,25 @@ test_expect_success 'drop stash reflog updates refs/stash' '\n test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n \tgit reset --hard &&\n \techo 9 >file &&\n+\n+\t# Our two stashes\n+\told_oid=\"$(git rev-parse stash@{0})\" &&\n \tgit stash &&\n-\toid=\"$(git rev-parse stash@{0})\" &&\n+\tnew_oid=\"$(git rev-parse stash@{0})\" &&\n+\n+\t# Our stash <old oid>/<new oid> before \"drop\"\n+\tcat >expect <<-EOF &&\n+\t$(test_oid zero) $old_oid\n+\t$old_oid $new_oid\n+\tEOF\n+\tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Our stash <old oid>/<new oid> after \"drop\"\n \tgit stash drop stash@{1} &&\n \tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n \tcat >expect <<-EOF &&\n-\t$(test_oid zero) $oid\n+\t$(test_oid zero) $new_oid\n \tEOF\n \ttest_cmp expect actual\n '\n\nIf this series is amended to drop the \"EXPIRE_REFLOGS_REWRITE\" flag then\nthis will fail on that last test_cmp like:\n    \n    + diff -u expect actual\n    --- expect      2022-02-23 23:37:40.438221222 +0000\n    +++ actual      2022-02-23 23:37:40.434221258 +0000\n    @@ -1 +1 @@\n    -0000000000000000000000000000000000000000 236c59f58e239e74e90b6832a98fa4b7f4b33647\n    +5c6ad4ca28e71ae3a007e6c77043d04bc42fa9ee 236c59f58e239e74e90b6832a98fa4b7f4b33647\n\nI.e. our <old oid> is now referring to the now-deleted stash entry we\njust deleted, since we didn't rewrite it.\n\nAnd as we can see with some manual inspection the state before we\ndropped stash@{1} was:\n\n    0000000000000000000000000000000000000000 5c6ad4ca28e71ae3a007e6c77043d04bc42fa9ee\n    5c6ad4ca28e71ae3a007e6c77043d04bc42fa9ee 236c59f58e239e74e90b6832a98fa4b7f4b33647\n\nMy usual method of checking my assumption about this not being otherwise\ninspectable would be something like:\n        \n    diff --git a/refs/files-backend.c b/refs/files-backend.c\n    index f59589d6cce..590c13e7a2b 100644\n    --- a/refs/files-backend.c\n    +++ b/refs/files-backend.c\n    @@ -3133,7 +3133,7 @@ static int files_reflog_expire(struct ref_store *ref_store,\n            const struct object_id *oid;\n     \n            memset(&cb, 0, sizeof(cb));\n    -       cb.rewrite = !!(expire_flags & EXPIRE_REFLOGS_REWRITE);\n    +       cb.rewrite = 0;\n            cb.dry_run = !!(expire_flags & EXPIRE_REFLOGS_DRY_RUN);\n            cb.policy_cb = policy_cb_data;\n            cb.should_prune_fn = should_prune_fn;\n\nI.e. let's intentionally break the flag, and see what else fails (it's\nset in a few places, but this is the only place where it's checked).\n\nThat should normally find the other things that are testing this, maybe\nthere's a better way.\n\nBut, no such luck :) The only thing that'll fail is this new test being\nadded here.\n\nSo just like my 5ac15ad2509 (reflog tests: add --updateref tests,\n2021-10-16) this is covering a true blindspot in the \"git reflog\"\nfunctionality.\n\nThe only tests that used --rewrite were a test added in c41a87dd80c\n(refs: make rev-parse --quiet actually quiet, 2014-09-18), which will\npass if --rewrite is omitted.\n\nAnd the ones I added in 5ac15ad2509, which I added not to test --rewrite\nper-se, but to test that the --updateref part of it behaved as expected\nin combination with whatever effect it was having.\n"},{"id":"449443","messageId":"9AC71167-BED8-4003-A5B2-072B7913FA62@gmail.com","threadId":"57437","inReplyTo":"xmqqczjdp2g8.fsf@gitster.g","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-24T14:53:13Z","receivedAt":"2022-02-24T14:53:18Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Junio,\n\nOn 23 Feb 2022, at 18:50, Junio C Hamano wrote:\n\n> John Cai <johncai86@gmail.com> writes:\n>\n>> Yes, this is true but that doesn't seem to test the --rewrite functionality.\n>> I could be missing something, but it seems that the reflog --rewrite option\n>> will write the LHS old oid value in the .git/logs/refs/stash file. When\n>> --rewrite isn't used, the reflog delete still does the right thing to the\n>> RHS entry.\n>>\n>> I couldn't find any way to check this LFS value other than reaching into the\n>> actual file. If there is a way that would be preferable.\n>\n> Ah, that one.\n>\n> As 2b81fab2 (git-reflog: add option --rewrite to update reflog\n> entries while expiring, 2008-02-22) says, the redundant half of the\n> reflog entry only matters to \"certain sanity checks\" and would not\n> be even visible to normal ref API users.  I wonder why we need to\n> even say \"--rewrite\" in the first place.  Perhaps we should\n> implicitly set it always and eventually deprecate that option.\n\nYeah, that makes sense. I had this thought as I was figuring out\nhow to test this. I can take care of this in a separate patch series\n"},{"id":"449488","messageId":"865928A5-3F54-4B51-B502-07E24D98CEDB@gmail.com","threadId":"57437","inReplyTo":"220223.86ley1b653.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-02-24T18:21:02Z","receivedAt":"2022-02-24T18:21:17Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Ævar,\n\nOn 23 Feb 2022, at 16:50, Ævar Arnfjörð Bjarmason wrote:\n\n> On Wed, Feb 23 2022, Junio C Hamano wrote:\n>\n>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>>\n>>> This test was already a bit broken in needing the preceding tests, but\n>>> it will break now if REFFILES isn't true, which you can reproduce\n>>> e.g. with:\n>>>\n>>>     ./t3903-stash.sh --run=1-16,18-50 -vixd\n>>>\n>>> Perhaps the least sucky solution to that is:\n>>>\n>>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>>> index ec9cc5646d6..1d11c9bda20 100755\n>>> --- a/t/t3903-stash.sh\n>>> +++ b/t/t3903-stash.sh\n>>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>>  \tcat >expect <<-EOF &&\n>>>  \t$(test_oid zero) $oid\n>>>  \tEOF\n>>> -\ttest_cmp expect actual\n>>> +\ttest_cmp expect actual &&\n>>> +\t>dropped-stash\n>>>  '\n>>\n>> If \"git stash drop\", invoked in earlier part of this test before the\n>> precontext, fails, then test_cmp would fail and we leave\n>> dropped-stash untouched, even though we did run \"git stash drop\"\n>> already.\n>\n> Yes, that's an edge case that's exposed here, but which I thought wasn't\n> worth bothering with. I.e. if you get such a failure on test N getting\n> N+1 failing as well isn't that big of a deal.\n>\n> The big deal is rather that we know we're adding a REFFILES dependency\n> to this, which won't run this at all, which will make the \"stash pop\"\n> below fail.\n>\n>> Why does the next test need to depend on what has happened earlier?\n>\n> They don't need to, and ideally wouldn't, but most of our test suite has\n> this issue already. Try e.g. running it with:\n>\n>     prove t[0-9]*.sh :: --run=10-20 --immediate\n>\n> And for this particular file running e.g. this on master:\n>\n>     ./t3903-stash.sh --run=1-10,30-40\n>\n> Will fail 7 tests in the 30-40 range.\n>\n> So while it's ideal that we can combine tests with arbitrary --run\n> parameters, i.e. all tests would tear down fully, not depend on earlier\n> tests etc. we're really far from that being the case in practice.\n>\n> So insisting on some general refactoring of this file as part of this\n> series seems a bit overzelous, which is why I'm suggesting the bare\n> minimum to expect and work around the inevitable REFFILES failure, as\n> Han-Wen is actively working in that area.\n\nCurious what your thoughts are on an effort to isolate these tests from each other.\nI like your approach in t/t1417 in creating a test repo and copying it over each time.\nSomething like this?\n\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex ac345eced8cb..40254f8dc99c 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -41,7 +41,9 @@ diff_cmp () {\n        rm -f \"$1.compare\" \"$2.compare\"\n }\n\n-test_expect_success 'stash some dirty working directory' '\n+test_expect_success 'setup' '\n+       git init repo &&\n+       cd repo &&\n        echo 1 >file &&\n        git add file &&\n        echo unrelated >other-file &&\n@@ -54,48 +56,54 @@ test_expect_success 'stash some dirty working directory' '\n        test_tick &&\n        git stash &&\n        git diff-files --quiet &&\n-       git diff-index --cached --quiet HEAD\n+       git diff-index --cached --quiet HEAD &&\n+       cat >expect <<-EOF &&\n+       diff --git a/file b/file\n+       index 0cfbf08..00750ed 100644\n+       --- a/file\n+       +++ b/file\n+       @@ -1 +1 @@\n+       -2\n+       +3\n+       EOF\n+       cd ../\n '\n\n-cat >expect <<EOF\n-diff --git a/file b/file\n-index 0cfbf08..00750ed 100644\n---- a/file\n-+++ b/file\n-@@ -1 +1 @@\n--2\n-+3\n-EOF\n+test_stash () {\n+       cp -R repo copy &&\n+       cd copy &&\n+       test_expect_success \"$@\" &&\n+       cd ../ &&\n+       rm -rf copy\n+}\n\n-test_expect_success 'parents of stash' '\n+test_stash 'parents of stash' '\n        test $(git rev-parse stash^) = $(git rev-parse HEAD) &&\n        git diff stash^2..stash >output &&\n        diff_cmp expect output\n '\n\nNot sure if it's worth it though?\n\n\n>\n>>>  test_expect_success 'stash pop' '\n>>>  \tgit reset --hard &&\n>>>  \tgit stash pop &&\n>>> -\ttest 9 = $(cat file) &&\n>>> +\tif test -e dropped-stash\n>>> +\tthen\n>>> +\t\ttest 9 = $(cat file)\n>>> +\telse\n>>> +\t\ttest 3 = $(cat file)\n>>> +\tfi &&\n>>>  \ttest 1 = $(git show :file) &&\n>>>  \ttest 1 = $(git show HEAD:file) &&\n>>>  \ttest 0 = $(git stash list | wc -l)\n"},{"id":"449574","messageId":"220225.86lexz88sp.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"865928A5-3F54-4B51-B502-07E24D98CEDB@gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-25T11:45:19Z","receivedAt":"2022-02-25T11:50:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Feb 24 2022, John Cai wrote:\n\n> Hi Ævar,\n>\n> On 23 Feb 2022, at 16:50, Ævar Arnfjörð Bjarmason wrote:\n>\n>> On Wed, Feb 23 2022, Junio C Hamano wrote:\n>>\n>>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>>>\n>>>> This test was already a bit broken in needing the preceding tests, but\n>>>> it will break now if REFFILES isn't true, which you can reproduce\n>>>> e.g. with:\n>>>>\n>>>>     ./t3903-stash.sh --run=1-16,18-50 -vixd\n>>>>\n>>>> Perhaps the least sucky solution to that is:\n>>>>\n>>>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>>>> index ec9cc5646d6..1d11c9bda20 100755\n>>>> --- a/t/t3903-stash.sh\n>>>> +++ b/t/t3903-stash.sh\n>>>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>>>  \tcat >expect <<-EOF &&\n>>>>  \t$(test_oid zero) $oid\n>>>>  \tEOF\n>>>> -\ttest_cmp expect actual\n>>>> +\ttest_cmp expect actual &&\n>>>> +\t>dropped-stash\n>>>>  '\n>>>\n>>> If \"git stash drop\", invoked in earlier part of this test before the\n>>> precontext, fails, then test_cmp would fail and we leave\n>>> dropped-stash untouched, even though we did run \"git stash drop\"\n>>> already.\n>>\n>> Yes, that's an edge case that's exposed here, but which I thought wasn't\n>> worth bothering with. I.e. if you get such a failure on test N getting\n>> N+1 failing as well isn't that big of a deal.\n>>\n>> The big deal is rather that we know we're adding a REFFILES dependency\n>> to this, which won't run this at all, which will make the \"stash pop\"\n>> below fail.\n>>\n>>> Why does the next test need to depend on what has happened earlier?\n>>\n>> They don't need to, and ideally wouldn't, but most of our test suite has\n>> this issue already. Try e.g. running it with:\n>>\n>>     prove t[0-9]*.sh :: --run=10-20 --immediate\n>>\n>> And for this particular file running e.g. this on master:\n>>\n>>     ./t3903-stash.sh --run=1-10,30-40\n>>\n>> Will fail 7 tests in the 30-40 range.\n>>\n>> So while it's ideal that we can combine tests with arbitrary --run\n>> parameters, i.e. all tests would tear down fully, not depend on earlier\n>> tests etc. we're really far from that being the case in practice.\n>>\n>> So insisting on some general refactoring of this file as part of this\n>> series seems a bit overzelous, which is why I'm suggesting the bare\n>> minimum to expect and work around the inevitable REFFILES failure, as\n>> Han-Wen is actively working in that area.\n>\n> Curious what your thoughts are on an effort to isolate these tests from each other.\n> I like your approach in t/t1417 in creating a test repo and copying it over each time.\n> Something like this?\n\nThat looks good to me if you're willing to do that legwork, probably\nbetter in a preceding cleanup commit.\n\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index ac345eced8cb..40254f8dc99c 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -41,7 +41,9 @@ diff_cmp () {\n>         rm -f \"$1.compare\" \"$2.compare\"\n>  }\n>\n> -test_expect_success 'stash some dirty working directory' '\n> +test_expect_success 'setup' '\n> +       git init repo &&\n> +       cd repo &&\n>         echo 1 >file &&\n>         git add file &&\n>         echo unrelated >other-file &&\n> @@ -54,48 +56,54 @@ test_expect_success 'stash some dirty working directory' '\n>         test_tick &&\n>         git stash &&\n>         git diff-files --quiet &&\n> -       git diff-index --cached --quiet HEAD\n> +       git diff-index --cached --quiet HEAD &&\n> +       cat >expect <<-EOF &&\n\nnit: you can add \\ to that, i.e. <<-\\EOF. Helps readability, i.e.  it's\nobvious right away that no variables are in play..\n\n> +       diff --git a/file b/file\n> +       index 0cfbf08..00750ed 100644\n> +       --- a/file\n> +       +++ b/file\n> +       @@ -1 +1 @@\n> +       -2\n> +       +3\n> +       EOF\n> +       cd ../\n>  '\n>\n> -cat >expect <<EOF\n> -diff --git a/file b/file\n> -index 0cfbf08..00750ed 100644\n> ---- a/file\n> -+++ b/file\n> -@@ -1 +1 @@\n> --2\n> -+3\n> -EOF\n> +test_stash () {\n> +       cp -R repo copy &&\n> +       cd copy &&\n> +       test_expect_success \"$@\" &&\n> +       cd ../ &&\n> +       rm -rf copy\n> +}\n>\n>\n> -test_expect_success 'parents of stash' '\n> +test_stash 'parents of stash' '\n>         test $(git rev-parse stash^) = $(git rev-parse HEAD) &&\n>         git diff stash^2..stash >output &&\n>         diff_cmp expect output\n>  '\n\nFor this sort of thing I think it's usually better to override\n\"test_expect_success\" as a last resort, i.e. to have that\n\"test_setup_stash_copy\" just be a \"setup_stash\" or whatever function\ncalled from within your test_expect_success.\n\nAnd instead of the \"rm -rf\" later, just do:\n\n    test_when_finished \"rm -rf copy\" &&\n    cp -R repo copy &&\n    [...]\n\nThe test still needs to deal with the sub-repo, but it could cd or use\n\"-C\".\n\nIt's bad to add \"cd ..\" in a &&-chain, because if earlier steps fail\nwe're in the wrong directory for the next test, so either -C or a\nsub-shell...\n\n> Not sure if it's worth it though?\n\nMaybe not, which is why I suggested upthread to maybe go for some\nsmallest possible change here and focus on the lib-ificitaion :)\n\n>\n>\n>>\n>>>>  test_expect_success 'stash pop' '\n>>>>  \tgit reset --hard &&\n>>>>  \tgit stash pop &&\n>>>> -\ttest 9 = $(cat file) &&\n>>>> +\tif test -e dropped-stash\n>>>> +\tthen\n>>>> +\t\ttest 9 = $(cat file)\n>>>> +\telse\n>>>> +\t\ttest 3 = $(cat file)\n>>>> +\tfi &&\n>>>>  \ttest 1 = $(git show :file) &&\n>>>>  \ttest 1 = $(git show HEAD:file) &&\n>>>>  \ttest 0 = $(git stash list | wc -l)\n\n"},{"id":"449610","messageId":"xmqq5yp2g8rr.fsf@gitster.g","threadId":"57437","inReplyTo":"220225.86lexz88sp.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-25T17:23:36Z","receivedAt":"2022-02-25T17:23:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n>> Curious what your thoughts are on an effort to isolate these tests from each other.\n>> I like your approach in t/t1417 in creating a test repo and copying it over each time.\n>> Something like this?\n>\n> That looks good to me if you're willing to do that legwork, probably\n> better in a preceding cleanup commit.\n\nYup.  Thanks for helping other contributors.  I agree with many\nthings you said in your review.\n\n>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>> index ac345eced8cb..40254f8dc99c 100755\n>> --- a/t/t3903-stash.sh\n>> +++ b/t/t3903-stash.sh\n>> @@ -41,7 +41,9 @@ diff_cmp () {\n>>         rm -f \"$1.compare\" \"$2.compare\"\n>>  }\n>>\n>> -test_expect_success 'stash some dirty working directory' '\n>> +test_expect_success 'setup' '\n>> +       git init repo &&\n>> +       cd repo &&\n\nWe do not want to \"chdir\" around without isolating it in a\nsubprocess.  If this test fails after it goes to \"repo\" but before it\ndoes \"cd ..\", the next test begins in the \"repo\" directory, but it\nis most likely not expecting that.\n\n>> -cat >expect <<EOF\n>> -diff --git a/file b/file\n>> -index 0cfbf08..00750ed 100644\n>> ---- a/file\n>> -+++ b/file\n>> -@@ -1 +1 @@\n>> --2\n>> -+3\n>> -EOF\n>> +test_stash () {\n>> +       cp -R repo copy &&\n>> +       cd copy &&\n>> +       test_expect_success \"$@\" &&\n>> +       cd ../ &&\n>> +       rm -rf copy\n>> +}\n\nThis will create an anti-pattern, because you would want to have the\npart between \"cd copy\" and \"cd ..\" in a subshell, but you do not\nwant to do test_expect_success inside a subshell.  Hence, this is a\nbad helper that does not help and should not be used, I would think.\n\n>> -test_expect_success 'parents of stash' '\n>> +test_stash 'parents of stash' '\n>>         test $(git rev-parse stash^) = $(git rev-parse HEAD) &&\n>>         git diff stash^2..stash >output &&\n>>         diff_cmp expect output\n>>  '\n>\n> For this sort of thing I think it's usually better to override\n> \"test_expect_success\" as a last resort, i.e. to have that\n> \"test_setup_stash_copy\" just be a \"setup_stash\" or whatever function\n> called from within your test_expect_success.\n>\n> And instead of the \"rm -rf\" later, just do:\n>\n>     test_when_finished \"rm -rf copy\" &&\n>     cp -R repo copy &&\n>     [...]\n\nYup.  I think this is how we would write it:\n\n\ttest_expect_success 'parents of stash' '\n\t\ttest_when_finished \"rm -fr copy\" &&\n\t\tcp -R repo copy &&\n\t\t(\n\t\t\tcd copy &&\n\t\t\t... the real body of the test here, like ...\n\t\t\ttest $(git rev-parse stash^) = $(git rev-parse HEAD) &&\n\t\t)\n\t'\n\n> The test still needs to deal with the sub-repo, but it could cd or use\n> \"-C\".\n\nI am not sure about this.  test_expect_success does not take \"-C\".\n"},{"id":"449629","messageId":"33299825fc47fb0e2bc54e1f125282e7825e1924.1645817452.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v3.git.git.1645817452.gitgitgadget@gmail.com","subject":"[PATCH v3 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-25T19:30:50Z","receivedAt":"2022-02-25T19:30:59Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nThere is missing test coverage to ensure that the resulting reflogs\nafter a git stash drop has had its old oid rewritten if applicable, and\nif the refs/stash has been updated if applicable.\n\nAdd two tests that verify both of these happen.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n t/t3903-stash.sh | 65 ++++++++++++++++++++++++++++++++++++++++--------\n 1 file changed, 54 insertions(+), 11 deletions(-)\n\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex b149e2af441..73785cf862f 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -41,20 +41,29 @@ diff_cmp () {\n \trm -f \"$1.compare\" \"$2.compare\"\n }\n \n-test_expect_success 'stash some dirty working directory' '\n-\techo 1 >file &&\n-\tgit add file &&\n-\techo unrelated >other-file &&\n-\tgit add other-file &&\n+setup_stash() {\n+\trepo_dir=$1\n+\tif test -z $repo_dir; then\n+\t\trepo_dir=\".\"\n+\tfi\n+\n+\techo 1 >$repo_dir/file &&\n+\tgit -C $repo_dir add file &&\n+\techo unrelated >$repo_dir/other-file &&\n+\tgit -C $repo_dir add other-file &&\n \ttest_tick &&\n-\tgit commit -m initial &&\n-\techo 2 >file &&\n+\tgit -C $repo_dir commit -m initial &&\n+\techo 2 >$repo_dir/file &&\n \tgit add file &&\n-\techo 3 >file &&\n+\techo 3 >$repo_dir/file &&\n \ttest_tick &&\n-\tgit stash &&\n-\tgit diff-files --quiet &&\n-\tgit diff-index --cached --quiet HEAD\n+\tgit -C $repo_dir stash &&\n+\tgit -C $repo_dir diff-files --quiet &&\n+\tgit -C $repo_dir diff-index --cached --quiet HEAD\n+}\n+\n+test_expect_success 'stash some dirty working directory' '\n+\tsetup_stash\n '\n \n cat >expect <<EOF\n@@ -185,6 +194,40 @@ test_expect_success 'drop middle stash by index' '\n \ttest 1 = $(git show HEAD:file)\n '\n \n+test_expect_success 'drop stash reflog updates refs/stash' '\n+\tgit reset --hard &&\n+\tgit rev-parse refs/stash >expect &&\n+\techo 9 >file &&\n+\tgit stash &&\n+\tgit stash drop stash@{0} &&\n+\tgit rev-parse refs/stash >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n+\tgit init repo &&\n+\tsetup_stash repo &&\n+\techo 9 >repo/file &&\n+\n+\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n+\tgit -C repo stash &&\n+\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n+\n+\tcat >expect <<-EOF &&\n+\t$(test_oid zero) $old_oid\n+\t$old_oid $new_oid\n+\tEOF\n+\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tgit -C repo stash drop stash@{1} &&\n+\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n+\tcat >expect <<-EOF &&\n+\t$(test_oid zero) $new_oid\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'stash pop' '\n \tgit reset --hard &&\n \tgit stash pop &&\n-- \ngitgitgadget\n\n"},{"id":"449627","messageId":"pull.1218.v3.git.git.1645817452.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v2.git.git.1645554651.gitgitgadget@gmail.com","subject":"[PATCH v3 0/3] libify reflog","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-25T19:30:49Z","receivedAt":"2022-02-25T19:31:00Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"In [1], there was a discussion around a bug report of stash not recovering\nin the middle of the process when killed with ctl-c. It turned out to not be\na bug we need to fix. However, out of that discussion came the idea of\nlibifying reflog. This can stand alone as a code improvement.\n\nstash.c currently shells out to call reflog to delete reflogs. Libify reflog\ndelete and call it from both builtin/reflog.c and builtin/stash.c.\n\nThis patch has three parts:\n\n * add missing test coverage for git stash delete\n * libify reflog's delete functionality and move some of the helpers into a\n   reflog.c library and call reflog_delete from builtin/reflog.c\n * call reflog_delete from builtin/stash.c\n\nUpdates since v2:\n\n * removed unnecessary includes\n * adjusted wrapping/whitespace in reflog.h\n * adjusted test to be isolated from other tests since currently tests for\n   stash depend on each other. There was some discussion around this and\n   even a possibility to refactor the tests. However, it would have been a\n   larger effort than is worth for this series, so instead I just made one\n   of the tests I added be isolated from the others.\n\nUpdates since v1:\n\n * added missing test coverage\n * squashed 1/3 and 2/3 together\n * moved enum into reflog.c\n * updated object.h's flag allocation mapping\n\n 1. https://lore.kernel.org/git/220126.86h79qe692.gmgdl@evledraar.gmail.com/\n\nJohn Cai (3):\n  stash: add tests to ensure reflog --rewrite --updatref behavior\n  reflog: libify delete reflog function and helpers\n  stash: call reflog_delete() in reflog.c\n\n Makefile         |   1 +\n builtin/reflog.c | 455 +----------------------------------------------\n builtin/stash.c  |  18 +-\n object.h         |   2 +-\n reflog.c         | 432 ++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  43 +++++\n t/t3903-stash.sh |  65 +++++--\n 7 files changed, 539 insertions(+), 477 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\n\nbase-commit: e6ebfd0e8cbbd10878070c8a356b5ad1b3ca464e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1218%2Fjohn-cai%2Fjc-libify-reflog-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1218/john-cai/jc-libify-reflog-v3\nPull-Request: https://github.com/git/git/pull/1218\n\nRange-diff vs v2:\n\n 1:  6e136b62ca4 ! 1:  33299825fc4 stash: add test to ensure reflog --rewrite --updatref behavior\n     @@ Metadata\n      Author: John Cai <johncai86@gmail.com>\n      \n       ## Commit message ##\n     -    stash: add test to ensure reflog --rewrite --updatref behavior\n     +    stash: add tests to ensure reflog --rewrite --updatref behavior\n      \n          There is missing test coverage to ensure that the resulting reflogs\n          after a git stash drop has had its old oid rewritten if applicable, and\n     @@ Commit message\n          Signed-off-by: John Cai <johncai86@gmail.com>\n      \n       ## t/t3903-stash.sh ##\n     +@@ t/t3903-stash.sh: diff_cmp () {\n     + \trm -f \"$1.compare\" \"$2.compare\"\n     + }\n     + \n     +-test_expect_success 'stash some dirty working directory' '\n     +-\techo 1 >file &&\n     +-\tgit add file &&\n     +-\techo unrelated >other-file &&\n     +-\tgit add other-file &&\n     ++setup_stash() {\n     ++\trepo_dir=$1\n     ++\tif test -z $repo_dir; then\n     ++\t\trepo_dir=\".\"\n     ++\tfi\n     ++\n     ++\techo 1 >$repo_dir/file &&\n     ++\tgit -C $repo_dir add file &&\n     ++\techo unrelated >$repo_dir/other-file &&\n     ++\tgit -C $repo_dir add other-file &&\n     + \ttest_tick &&\n     +-\tgit commit -m initial &&\n     +-\techo 2 >file &&\n     ++\tgit -C $repo_dir commit -m initial &&\n     ++\techo 2 >$repo_dir/file &&\n     + \tgit add file &&\n     +-\techo 3 >file &&\n     ++\techo 3 >$repo_dir/file &&\n     + \ttest_tick &&\n     +-\tgit stash &&\n     +-\tgit diff-files --quiet &&\n     +-\tgit diff-index --cached --quiet HEAD\n     ++\tgit -C $repo_dir stash &&\n     ++\tgit -C $repo_dir diff-files --quiet &&\n     ++\tgit -C $repo_dir diff-index --cached --quiet HEAD\n     ++}\n     ++\n     ++test_expect_success 'stash some dirty working directory' '\n     ++\tsetup_stash\n     + '\n     + \n     + cat >expect <<EOF\n      @@ t/t3903-stash.sh: test_expect_success 'drop middle stash by index' '\n       \ttest 1 = $(git show HEAD:file)\n       '\n     @@ t/t3903-stash.sh: test_expect_success 'drop middle stash by index' '\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n     -+\tgit reset --hard &&\n     -+\techo 9 >file &&\n     -+\tgit stash &&\n     -+\toid=\"$(git rev-parse stash@{0})\" &&\n     -+\tgit stash drop stash@{1} &&\n     -+\tcut -d\" \" -f1-2 .git/logs/refs/stash >actual &&\n     ++test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n     ++\tgit init repo &&\n     ++\tsetup_stash repo &&\n     ++\techo 9 >repo/file &&\n     ++\n     ++\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n     ++\tgit -C repo stash &&\n     ++\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n     ++\n     ++\tcat >expect <<-EOF &&\n     ++\t$(test_oid zero) $old_oid\n     ++\t$old_oid $new_oid\n     ++\tEOF\n     ++\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n     ++\ttest_cmp expect actual &&\n     ++\n     ++\tgit -C repo stash drop stash@{1} &&\n     ++\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n      +\tcat >expect <<-EOF &&\n     -+\t$(test_oid zero) $oid\n     ++\t$(test_oid zero) $new_oid\n      +\tEOF\n      +\ttest_cmp expect actual\n      +'\n     @@ t/t3903-stash.sh: test_expect_success 'drop middle stash by index' '\n       test_expect_success 'stash pop' '\n       \tgit reset --hard &&\n       \tgit stash pop &&\n     --\ttest 3 = $(cat file) &&\n     -+\ttest 9 = $(cat file) &&\n     - \ttest 1 = $(git show :file) &&\n     - \ttest 1 = $(git show HEAD:file) &&\n     - \ttest 0 = $(git stash list | wc -l)\n 2:  e7c950218b1 ! 2:  33adfee4ca6 reflog: libify delete reflog function and helpers\n     @@ builtin/reflog.c\n      @@\n       #include \"builtin.h\"\n       #include \"config.h\"\n     - #include \"lockfile.h\"\n     +-#include \"lockfile.h\"\n      -#include \"object-store.h\"\n     - #include \"repository.h\"\n     +-#include \"repository.h\"\n      -#include \"commit.h\"\n      -#include \"refs.h\"\n     - #include \"dir.h\"\n     +-#include \"dir.h\"\n      -#include \"tree-walk.h\"\n     - #include \"diff.h\"\n     +-#include \"diff.h\"\n       #include \"revision.h\"\n       #include \"reachable.h\"\n       #include \"worktree.h\"\n     @@ object.h: struct object_array {\n       ## reflog.c (new) ##\n      @@\n      +#include \"cache.h\"\n     -+#include \"commit.h\"\n      +#include \"object-store.h\"\n     -+#include \"reachable.h\"\n      +#include \"reflog.h\"\n      +#include \"refs.h\"\n      +#include \"revision.h\"\n     -+#include \"tree-walk.h\"\n      +#include \"worktree.h\"\n      +\n      +/* Remember to update object flag allocation in object.h */\n     @@ reflog.h (new)\n      @@\n      +#ifndef REFLOG_H\n      +#define REFLOG_H\n     -+\n      +#include \"refs.h\"\n      +\n      +struct cmd_reflog_expire_cb {\n     @@ reflog.h (new)\n      +\tunsigned int dry_run:1;\n      +};\n      +\n     -+int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose);\n     -+\n     ++int reflog_delete(const char *rev, enum expire_reflog_flags flags,\n     ++\t\t  int verbose);\n      +void reflog_expiry_cleanup(void *cb_data);\n     -+\n      +void reflog_expiry_prepare(const char *refname, const struct object_id *oid,\n      +\t\t\t   void *cb_data);\n     -+\n      +int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n      +\t\t\t     const char *email, timestamp_t timestamp, int tz,\n      +\t\t\t     const char *message, void *cb_data);\n     -+\n      +int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n      +\t\t     const char *email, timestamp_t timestamp, int tz,\n      +\t\t     const char *message, void *cb_data);\n     -+\n      +int should_expire_reflog_ent_verbose(struct object_id *ooid,\n      +\t\t\t\t     struct object_id *noid,\n      +\t\t\t\t     const char *email,\n      +\t\t\t\t     timestamp_t timestamp, int tz,\n      +\t\t\t\t     const char *message, void *cb_data);\n     -+\n      +#endif /* REFLOG_H */\n 3:  a023a70092b = 3:  b17d8e5d43a stash: call reflog_delete() in reflog.c\n\n-- \ngitgitgadget\n"},{"id":"449628","messageId":"b17d8e5d43ab98f76b4dd1387873eff5156f94af.1645817452.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v3.git.git.1645817452.gitgitgadget@gmail.com","subject":"[PATCH v3 3/3] stash: call reflog_delete() in reflog.c","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-25T19:30:52Z","receivedAt":"2022-02-25T19:31:03Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nNow that cmd_reflog_delete has been libified an exported it into a new\nreflog.c library so we can call it directly from builtin/stash.c. This\nnot only gives us a performance gain since we don't need to create a\nsubprocess, but it also allows us to use the ref transactions api in the\nfuture.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n builtin/stash.c | 18 ++++--------------\n 1 file changed, 4 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 5897febfbec..3e2f478b761 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -17,6 +17,7 @@\n #include \"diffcore.h\"\n #include \"exec-cmd.h\"\n #include \"entry.h\"\n+#include \"reflog.h\"\n \n #define INCLUDE_ALL_FILES 2\n \n@@ -634,20 +635,9 @@ static int reflog_is_empty(const char *refname)\n \n static int do_drop_stash(struct stash_info *info, int quiet)\n {\n-\tint ret;\n-\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n-\n-\t/*\n-\t * reflog does not provide a simple function for deleting refs. One will\n-\t * need to be added to avoid implementing too much reflog code here\n-\t */\n-\n-\tcp_reflog.git_cmd = 1;\n-\tstrvec_pushl(&cp_reflog.args, \"reflog\", \"delete\", \"--updateref\",\n-\t\t     \"--rewrite\", NULL);\n-\tstrvec_push(&cp_reflog.args, info->revision.buf);\n-\tret = run_command(&cp_reflog);\n-\tif (!ret) {\n+\tif (!reflog_delete(info->revision.buf,\n+\t\t\t   EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF,\n+\t\t\t   0)) {\n \t\tif (!quiet)\n \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n \t\t\t\t  oid_to_hex(&info->w_commit));\n-- \ngitgitgadget\n"},{"id":"449630","messageId":"33adfee4ca6af1dd5ae396b8ba2ad46bbdafa865.1645817452.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v3.git.git.1645817452.gitgitgadget@gmail.com","subject":"[PATCH v3 2/3] reflog: libify delete reflog function and helpers","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-25T19:30:51Z","receivedAt":"2022-02-25T19:31:06Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nCurrently stash shells out to reflog in order to delete refs. In an\neffort to reduce how much we shell out to a subprocess, libify the\nfunctionality that stash needs into reflog.c.\n\nAdd a reflog_delete function that is pretty much the logic in the while\nloop in builtin/reflog.c cmd_reflog_delete(). This is a function that\nbuiltin/reflog.c and builtin/stash.c can both call.\n\nAlso move functions needed by reflog_delete and export them.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n Makefile         |   1 +\n builtin/reflog.c | 455 +----------------------------------------------\n object.h         |   2 +-\n reflog.c         | 432 ++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  43 +++++\n 5 files changed, 481 insertions(+), 452 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\ndiff --git a/Makefile b/Makefile\nindex 6f0b4b775fe..876d4dfd6cb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -989,6 +989,7 @@ LIB_OBJS += rebase-interactive.o\n LIB_OBJS += rebase.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += reflog-walk.o\n+LIB_OBJS += reflog.o\n LIB_OBJS += refs.o\n LIB_OBJS += refs/debug.o\n LIB_OBJS += refs/files-backend.o\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 85b838720c3..940db196f62 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -1,16 +1,9 @@\n #include \"builtin.h\"\n #include \"config.h\"\n-#include \"lockfile.h\"\n-#include \"object-store.h\"\n-#include \"repository.h\"\n-#include \"commit.h\"\n-#include \"refs.h\"\n-#include \"dir.h\"\n-#include \"tree-walk.h\"\n-#include \"diff.h\"\n #include \"revision.h\"\n #include \"reachable.h\"\n #include \"worktree.h\"\n+#include \"reflog.h\"\n \n static const char reflog_exists_usage[] =\n N_(\"git reflog exists <ref>\");\n@@ -18,404 +11,11 @@ N_(\"git reflog exists <ref>\");\n static timestamp_t default_reflog_expire;\n static timestamp_t default_reflog_expire_unreachable;\n \n-struct cmd_reflog_expire_cb {\n-\tint stalefix;\n-\tint explicit_expiry;\n-\ttimestamp_t expire_total;\n-\ttimestamp_t expire_unreachable;\n-\tint recno;\n-};\n-\n-struct expire_reflog_policy_cb {\n-\tenum {\n-\t\tUE_NORMAL,\n-\t\tUE_ALWAYS,\n-\t\tUE_HEAD\n-\t} unreachable_expire_kind;\n-\tstruct commit_list *mark_list;\n-\tunsigned long mark_limit;\n-\tstruct cmd_reflog_expire_cb cmd;\n-\tstruct commit *tip_commit;\n-\tstruct commit_list *tips;\n-\tunsigned int dry_run:1;\n-};\n-\n struct worktree_reflogs {\n \tstruct worktree *worktree;\n \tstruct string_list reflogs;\n };\n \n-/* Remember to update object flag allocation in object.h */\n-#define INCOMPLETE\t(1u<<10)\n-#define STUDYING\t(1u<<11)\n-#define REACHABLE\t(1u<<12)\n-\n-static int tree_is_complete(const struct object_id *oid)\n-{\n-\tstruct tree_desc desc;\n-\tstruct name_entry entry;\n-\tint complete;\n-\tstruct tree *tree;\n-\n-\ttree = lookup_tree(the_repository, oid);\n-\tif (!tree)\n-\t\treturn 0;\n-\tif (tree->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (tree->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\n-\tif (!tree->buffer) {\n-\t\tenum object_type type;\n-\t\tunsigned long size;\n-\t\tvoid *data = read_object_file(oid, &type, &size);\n-\t\tif (!data) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\treturn 0;\n-\t\t}\n-\t\ttree->buffer = data;\n-\t\ttree->size = size;\n-\t}\n-\tinit_tree_desc(&desc, tree->buffer, tree->size);\n-\tcomplete = 1;\n-\twhile (tree_entry(&desc, &entry)) {\n-\t\tif (!has_object_file(&entry.oid) ||\n-\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\tcomplete = 0;\n-\t\t}\n-\t}\n-\tfree_tree_buffer(tree);\n-\n-\tif (complete)\n-\t\ttree->object.flags |= SEEN;\n-\treturn complete;\n-}\n-\n-static int commit_is_complete(struct commit *commit)\n-{\n-\tstruct object_array study;\n-\tstruct object_array found;\n-\tint is_incomplete = 0;\n-\tint i;\n-\n-\t/* early return */\n-\tif (commit->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (commit->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\t/*\n-\t * Find all commits that are reachable and are not marked as\n-\t * SEEN.  Then make sure the trees and blobs contained are\n-\t * complete.  After that, mark these commits also as SEEN.\n-\t * If some of the objects that are needed to complete this\n-\t * commit are missing, mark this commit as INCOMPLETE.\n-\t */\n-\tmemset(&study, 0, sizeof(study));\n-\tmemset(&found, 0, sizeof(found));\n-\tadd_object_array(&commit->object, NULL, &study);\n-\tadd_object_array(&commit->object, NULL, &found);\n-\tcommit->object.flags |= STUDYING;\n-\twhile (study.nr) {\n-\t\tstruct commit *c;\n-\t\tstruct commit_list *parent;\n-\n-\t\tc = (struct commit *)object_array_pop(&study);\n-\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n-\t\t\tc->object.flags |= INCOMPLETE;\n-\n-\t\tif (c->object.flags & INCOMPLETE) {\n-\t\t\tis_incomplete = 1;\n-\t\t\tbreak;\n-\t\t}\n-\t\telse if (c->object.flags & SEEN)\n-\t\t\tcontinue;\n-\t\tfor (parent = c->parents; parent; parent = parent->next) {\n-\t\t\tstruct commit *p = parent->item;\n-\t\t\tif (p->object.flags & STUDYING)\n-\t\t\t\tcontinue;\n-\t\t\tp->object.flags |= STUDYING;\n-\t\t\tadd_object_array(&p->object, NULL, &study);\n-\t\t\tadd_object_array(&p->object, NULL, &found);\n-\t\t}\n-\t}\n-\tif (!is_incomplete) {\n-\t\t/*\n-\t\t * make sure all commits in \"found\" array have all the\n-\t\t * necessary objects.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++) {\n-\t\t\tstruct commit *c =\n-\t\t\t\t(struct commit *)found.objects[i].item;\n-\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n-\t\t\t\tis_incomplete = 1;\n-\t\t\t\tc->object.flags |= INCOMPLETE;\n-\t\t\t}\n-\t\t}\n-\t\tif (!is_incomplete) {\n-\t\t\t/* mark all found commits as complete, iow SEEN */\n-\t\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t\t}\n-\t}\n-\t/* clear flags from the objects we traversed */\n-\tfor (i = 0; i < found.nr; i++)\n-\t\tfound.objects[i].item->flags &= ~STUDYING;\n-\tif (is_incomplete)\n-\t\tcommit->object.flags |= INCOMPLETE;\n-\telse {\n-\t\t/*\n-\t\t * If we come here, we have (1) traversed the ancestry chain\n-\t\t * from the \"commit\" until we reach SEEN commits (which are\n-\t\t * known to be complete), and (2) made sure that the commits\n-\t\t * encountered during the above traversal refer to trees that\n-\t\t * are complete.  Which means that we know *all* the commits\n-\t\t * we have seen during this process are complete.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t}\n-\t/* free object arrays */\n-\tobject_array_clear(&study);\n-\tobject_array_clear(&found);\n-\treturn !is_incomplete;\n-}\n-\n-static int keep_entry(struct commit **it, struct object_id *oid)\n-{\n-\tstruct commit *commit;\n-\n-\tif (is_null_oid(oid))\n-\t\treturn 1;\n-\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!commit)\n-\t\treturn 0;\n-\n-\t/*\n-\t * Make sure everything in this commit exists.\n-\t *\n-\t * We have walked all the objects reachable from the refs\n-\t * and cache earlier.  The commits reachable by this commit\n-\t * must meet SEEN commits -- and then we should mark them as\n-\t * SEEN as well.\n-\t */\n-\tif (!commit_is_complete(commit))\n-\t\treturn 0;\n-\t*it = commit;\n-\treturn 1;\n-}\n-\n-/*\n- * Starting from commits in the cb->mark_list, mark commits that are\n- * reachable from them.  Stop the traversal at commits older than\n- * the expire_limit and queue them back, so that the caller can call\n- * us again to restart the traversal with longer expire_limit.\n- */\n-static void mark_reachable(struct expire_reflog_policy_cb *cb)\n-{\n-\tstruct commit_list *pending;\n-\ttimestamp_t expire_limit = cb->mark_limit;\n-\tstruct commit_list *leftover = NULL;\n-\n-\tfor (pending = cb->mark_list; pending; pending = pending->next)\n-\t\tpending->item->object.flags &= ~REACHABLE;\n-\n-\tpending = cb->mark_list;\n-\twhile (pending) {\n-\t\tstruct commit_list *parent;\n-\t\tstruct commit *commit = pop_commit(&pending);\n-\t\tif (commit->object.flags & REACHABLE)\n-\t\t\tcontinue;\n-\t\tif (parse_commit(commit))\n-\t\t\tcontinue;\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tif (commit->date < expire_limit) {\n-\t\t\tcommit_list_insert(commit, &leftover);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tparent = commit->parents;\n-\t\twhile (parent) {\n-\t\t\tcommit = parent->item;\n-\t\t\tparent = parent->next;\n-\t\t\tif (commit->object.flags & REACHABLE)\n-\t\t\t\tcontinue;\n-\t\t\tcommit_list_insert(commit, &pending);\n-\t\t}\n-\t}\n-\tcb->mark_list = leftover;\n-}\n-\n-static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n-{\n-\t/*\n-\t * We may or may not have the commit yet - if not, look it\n-\t * up using the supplied sha1.\n-\t */\n-\tif (!commit) {\n-\t\tif (is_null_oid(oid))\n-\t\t\treturn 0;\n-\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n-\t\t\t\t\t\t\t1);\n-\n-\t\t/* Not a commit -- keep it */\n-\t\tif (!commit)\n-\t\t\treturn 0;\n-\t}\n-\n-\t/* Reachable from the current ref?  Don't prune. */\n-\tif (commit->object.flags & REACHABLE)\n-\t\treturn 0;\n-\n-\tif (cb->mark_list && cb->mark_limit) {\n-\t\tcb->mark_limit = 0; /* dig down to the root */\n-\t\tmark_reachable(cb);\n-\t}\n-\n-\treturn !(commit->object.flags & REACHABLE);\n-}\n-\n-/*\n- * Return true iff the specified reflog entry should be expired.\n- */\n-static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n-\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit *old_commit, *new_commit;\n-\n-\tif (timestamp < cb->cmd.expire_total)\n-\t\treturn 1;\n-\n-\told_commit = new_commit = NULL;\n-\tif (cb->cmd.stalefix &&\n-\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n-\t\treturn 1;\n-\n-\tif (timestamp < cb->cmd.expire_unreachable) {\n-\t\tswitch (cb->unreachable_expire_kind) {\n-\t\tcase UE_ALWAYS:\n-\t\t\treturn 1;\n-\t\tcase UE_NORMAL:\n-\t\tcase UE_HEAD:\n-\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n-\t\t\t\treturn 1;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\n-\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n-\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n-static int should_expire_reflog_ent_verbose(struct object_id *ooid,\n-\t\t\t\t\t    struct object_id *noid,\n-\t\t\t\t\t    const char *email,\n-\t\t\t\t\t    timestamp_t timestamp, int tz,\n-\t\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tint expire;\n-\n-\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n-\t\t\t\t\t  message, cb);\n-\n-\tif (!expire)\n-\t\tprintf(\"keep %s\", message);\n-\telse if (cb->dry_run)\n-\t\tprintf(\"would prune %s\", message);\n-\telse\n-\t\tprintf(\"prune %s\", message);\n-\n-\treturn expire;\n-}\n-\n-static int push_tip_to_list(const char *refname, const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n-{\n-\tstruct commit_list **list = cb_data;\n-\tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n-\t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!tip_commit)\n-\t\treturn 0;\n-\tcommit_list_insert(tip_commit, list);\n-\treturn 0;\n-}\n-\n-static int is_head(const char *refname)\n-{\n-\tswitch (ref_type(refname)) {\n-\tcase REF_TYPE_OTHER_PSEUDOREF:\n-\tcase REF_TYPE_MAIN_PSEUDOREF:\n-\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n-\t\t\tBUG(\"not a worktree ref: %s\", refname);\n-\t\tbreak;\n-\tdefault:\n-\t\tbreak;\n-\t}\n-\treturn !strcmp(refname, \"HEAD\");\n-}\n-\n-static void reflog_expiry_prepare(const char *refname,\n-\t\t\t\t  const struct object_id *oid,\n-\t\t\t\t  void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\tstruct commit *commit = NULL;\n-\n-\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n-\t\tcb->unreachable_expire_kind = UE_HEAD;\n-\t} else {\n-\t\tcommit = lookup_commit(the_repository, oid);\n-\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n-\t}\n-\n-\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n-\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tcommit_list_insert(commit, &cb->mark_list);\n-\t\t/* For reflog_expiry_cleanup() below */\n-\t\tcb->tip_commit = commit;\n-\t}\n-\tcb->mark_limit = cb->cmd.expire_total;\n-\tmark_reachable(cb);\n-}\n-\n-static void reflog_expiry_cleanup(void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tclear_commit_marks(elem->item, REACHABLE);\n-\t\tfree_commit_list(cb->tips);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n-\t\tbreak;\n-\t}\n-}\n-\n static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)\n {\n \tstruct worktree_reflogs *cb = cb_data;\n@@ -704,16 +304,6 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n-static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\tconst char *email, timestamp_t timestamp, int tz,\n-\t\tconst char *message, void *cb_data)\n-{\n-\tstruct cmd_reflog_expire_cb *cb = cb_data;\n-\tif (!cb->expire_total || timestamp < cb->expire_total)\n-\t\tcb->recno++;\n-\treturn 0;\n-}\n-\n static const char * reflog_delete_usage[] = {\n \tN_(\"git reflog delete [--rewrite] [--updateref] \"\n \t   \"[--dry-run | -n] [--verbose] <refs>...\"),\n@@ -722,11 +312,10 @@ static const char * reflog_delete_usage[] = {\n \n static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n {\n-\tstruct cmd_reflog_expire_cb cmd = { 0 };\n \tint i, status = 0;\n \tunsigned int flags = 0;\n \tint verbose = 0;\n-\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n+\n \tconst struct option options[] = {\n \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\n \t\t\tEXPIRE_REFLOGS_DRY_RUN),\n@@ -742,48 +331,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);\n \n-\tif (verbose)\n-\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n-\n \tif (argc < 1)\n \t\treturn error(_(\"no reflog specified to delete\"));\n \n-\tfor (i = 0; i < argc; i++) {\n-\t\tconst char *spec = strstr(argv[i], \"@{\");\n-\t\tchar *ep, *ref;\n-\t\tint recno;\n-\t\tstruct expire_reflog_policy_cb cb = {\n-\t\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n-\t\t};\n-\n-\t\tif (!spec) {\n-\t\t\tstatus |= error(_(\"not a reflog: %s\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n+\tfor (i = 0; i < argc; i++)\n+\t\tstatus |= reflog_delete(argv[i], flags, verbose);\n \n-\t\tif (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {\n-\t\t\tstatus |= error(_(\"no reflog for '%s'\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\trecno = strtoul(spec + 2, &ep, 10);\n-\t\tif (*ep == '}') {\n-\t\t\tcmd.recno = -recno;\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t} else {\n-\t\t\tcmd.expire_total = approxidate(spec + 2);\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t\tcmd.expire_total = 0;\n-\t\t}\n-\n-\t\tcb.cmd = cmd;\n-\t\tstatus |= reflog_expire(ref, flags,\n-\t\t\t\t\treflog_expiry_prepare,\n-\t\t\t\t\tshould_prune_fn,\n-\t\t\t\t\treflog_expiry_cleanup,\n-\t\t\t\t\t&cb);\n-\t\tfree(ref);\n-\t}\n \treturn status;\n }\n \ndiff --git a/object.h b/object.h\nindex cb556ab7753..a2219464c2b 100644\n--- a/object.h\n+++ b/object.h\n@@ -75,7 +75,7 @@ struct object_array {\n  * builtin/fsck.c:           0--3\n  * builtin/gc.c:             0\n  * builtin/index-pack.c:                                     2021\n- * builtin/reflog.c:                   10--12\n+ * reflog.c:                           10--12\n  * builtin/show-branch.c:    0-------------------------------------------26\n  * builtin/unpack-objects.c:                                 2021\n  */\ndiff --git a/reflog.c b/reflog.c\nnew file mode 100644\nindex 00000000000..333fd8708fe\n--- /dev/null\n+++ b/reflog.c\n@@ -0,0 +1,432 @@\n+#include \"cache.h\"\n+#include \"object-store.h\"\n+#include \"reflog.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"worktree.h\"\n+\n+/* Remember to update object flag allocation in object.h */\n+#define INCOMPLETE\t(1u<<10)\n+#define STUDYING\t(1u<<11)\n+#define REACHABLE\t(1u<<12)\n+\n+static int tree_is_complete(const struct object_id *oid)\n+{\n+\tstruct tree_desc desc;\n+\tstruct name_entry entry;\n+\tint complete;\n+\tstruct tree *tree;\n+\n+\ttree = lookup_tree(the_repository, oid);\n+\tif (!tree)\n+\t\treturn 0;\n+\tif (tree->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (tree->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\n+\tif (!tree->buffer) {\n+\t\tenum object_type type;\n+\t\tunsigned long size;\n+\t\tvoid *data = read_object_file(oid, &type, &size);\n+\t\tif (!data) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\treturn 0;\n+\t\t}\n+\t\ttree->buffer = data;\n+\t\ttree->size = size;\n+\t}\n+\tinit_tree_desc(&desc, tree->buffer, tree->size);\n+\tcomplete = 1;\n+\twhile (tree_entry(&desc, &entry)) {\n+\t\tif (!has_object_file(&entry.oid) ||\n+\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\tcomplete = 0;\n+\t\t}\n+\t}\n+\tfree_tree_buffer(tree);\n+\n+\tif (complete)\n+\t\ttree->object.flags |= SEEN;\n+\treturn complete;\n+}\n+\n+static int commit_is_complete(struct commit *commit)\n+{\n+\tstruct object_array study;\n+\tstruct object_array found;\n+\tint is_incomplete = 0;\n+\tint i;\n+\n+\t/* early return */\n+\tif (commit->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (commit->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\t/*\n+\t * Find all commits that are reachable and are not marked as\n+\t * SEEN.  Then make sure the trees and blobs contained are\n+\t * complete.  After that, mark these commits also as SEEN.\n+\t * If some of the objects that are needed to complete this\n+\t * commit are missing, mark this commit as INCOMPLETE.\n+\t */\n+\tmemset(&study, 0, sizeof(study));\n+\tmemset(&found, 0, sizeof(found));\n+\tadd_object_array(&commit->object, NULL, &study);\n+\tadd_object_array(&commit->object, NULL, &found);\n+\tcommit->object.flags |= STUDYING;\n+\twhile (study.nr) {\n+\t\tstruct commit *c;\n+\t\tstruct commit_list *parent;\n+\n+\t\tc = (struct commit *)object_array_pop(&study);\n+\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n+\t\t\tc->object.flags |= INCOMPLETE;\n+\n+\t\tif (c->object.flags & INCOMPLETE) {\n+\t\t\tis_incomplete = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t\telse if (c->object.flags & SEEN)\n+\t\t\tcontinue;\n+\t\tfor (parent = c->parents; parent; parent = parent->next) {\n+\t\t\tstruct commit *p = parent->item;\n+\t\t\tif (p->object.flags & STUDYING)\n+\t\t\t\tcontinue;\n+\t\t\tp->object.flags |= STUDYING;\n+\t\t\tadd_object_array(&p->object, NULL, &study);\n+\t\t\tadd_object_array(&p->object, NULL, &found);\n+\t\t}\n+\t}\n+\tif (!is_incomplete) {\n+\t\t/*\n+\t\t * make sure all commits in \"found\" array have all the\n+\t\t * necessary objects.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++) {\n+\t\t\tstruct commit *c =\n+\t\t\t\t(struct commit *)found.objects[i].item;\n+\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n+\t\t\t\tis_incomplete = 1;\n+\t\t\t\tc->object.flags |= INCOMPLETE;\n+\t\t\t}\n+\t\t}\n+\t\tif (!is_incomplete) {\n+\t\t\t/* mark all found commits as complete, iow SEEN */\n+\t\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t\t}\n+\t}\n+\t/* clear flags from the objects we traversed */\n+\tfor (i = 0; i < found.nr; i++)\n+\t\tfound.objects[i].item->flags &= ~STUDYING;\n+\tif (is_incomplete)\n+\t\tcommit->object.flags |= INCOMPLETE;\n+\telse {\n+\t\t/*\n+\t\t * If we come here, we have (1) traversed the ancestry chain\n+\t\t * from the \"commit\" until we reach SEEN commits (which are\n+\t\t * known to be complete), and (2) made sure that the commits\n+\t\t * encountered during the above traversal refer to trees that\n+\t\t * are complete.  Which means that we know *all* the commits\n+\t\t * we have seen during this process are complete.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t}\n+\t/* free object arrays */\n+\tobject_array_clear(&study);\n+\tobject_array_clear(&found);\n+\treturn !is_incomplete;\n+}\n+\n+static int keep_entry(struct commit **it, struct object_id *oid)\n+{\n+\tstruct commit *commit;\n+\n+\tif (is_null_oid(oid))\n+\t\treturn 1;\n+\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!commit)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Make sure everything in this commit exists.\n+\t *\n+\t * We have walked all the objects reachable from the refs\n+\t * and cache earlier.  The commits reachable by this commit\n+\t * must meet SEEN commits -- and then we should mark them as\n+\t * SEEN as well.\n+\t */\n+\tif (!commit_is_complete(commit))\n+\t\treturn 0;\n+\t*it = commit;\n+\treturn 1;\n+}\n+\n+/*\n+ * Starting from commits in the cb->mark_list, mark commits that are\n+ * reachable from them.  Stop the traversal at commits older than\n+ * the expire_limit and queue them back, so that the caller can call\n+ * us again to restart the traversal with longer expire_limit.\n+ */\n+static void mark_reachable(struct expire_reflog_policy_cb *cb)\n+{\n+\tstruct commit_list *pending;\n+\ttimestamp_t expire_limit = cb->mark_limit;\n+\tstruct commit_list *leftover = NULL;\n+\n+\tfor (pending = cb->mark_list; pending; pending = pending->next)\n+\t\tpending->item->object.flags &= ~REACHABLE;\n+\n+\tpending = cb->mark_list;\n+\twhile (pending) {\n+\t\tstruct commit_list *parent;\n+\t\tstruct commit *commit = pop_commit(&pending);\n+\t\tif (commit->object.flags & REACHABLE)\n+\t\t\tcontinue;\n+\t\tif (parse_commit(commit))\n+\t\t\tcontinue;\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tif (commit->date < expire_limit) {\n+\t\t\tcommit_list_insert(commit, &leftover);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tparent = commit->parents;\n+\t\twhile (parent) {\n+\t\t\tcommit = parent->item;\n+\t\t\tparent = parent->next;\n+\t\t\tif (commit->object.flags & REACHABLE)\n+\t\t\t\tcontinue;\n+\t\t\tcommit_list_insert(commit, &pending);\n+\t\t}\n+\t}\n+\tcb->mark_list = leftover;\n+}\n+\n+static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n+{\n+\t/*\n+\t * We may or may not have the commit yet - if not, look it\n+\t * up using the supplied sha1.\n+\t */\n+\tif (!commit) {\n+\t\tif (is_null_oid(oid))\n+\t\t\treturn 0;\n+\n+\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n+\t\t\t\t\t\t\t1);\n+\n+\t\t/* Not a commit -- keep it */\n+\t\tif (!commit)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Reachable from the current ref?  Don't prune. */\n+\tif (commit->object.flags & REACHABLE)\n+\t\treturn 0;\n+\n+\tif (cb->mark_list && cb->mark_limit) {\n+\t\tcb->mark_limit = 0; /* dig down to the root */\n+\t\tmark_reachable(cb);\n+\t}\n+\n+\treturn !(commit->object.flags & REACHABLE);\n+}\n+\n+/*\n+ * Return true iff the specified reflog entry should be expired.\n+ */\n+int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n+\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit *old_commit, *new_commit;\n+\n+\tif (timestamp < cb->cmd.expire_total)\n+\t\treturn 1;\n+\n+\told_commit = new_commit = NULL;\n+\tif (cb->cmd.stalefix &&\n+\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n+\t\treturn 1;\n+\n+\tif (timestamp < cb->cmd.expire_unreachable) {\n+\t\tswitch (cb->unreachable_expire_kind) {\n+\t\tcase UE_ALWAYS:\n+\t\t\treturn 1;\n+\t\tcase UE_NORMAL:\n+\t\tcase UE_HEAD:\n+\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n+\t\t\t\treturn 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+int should_expire_reflog_ent_verbose(struct object_id *ooid,\n+\t\t\t\t\t    struct object_id *noid,\n+\t\t\t\t\t    const char *email,\n+\t\t\t\t\t    timestamp_t timestamp, int tz,\n+\t\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tint expire;\n+\n+\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n+\t\t\t\t\t  message, cb);\n+\n+\tif (!expire)\n+\t\tprintf(\"keep %s\", message);\n+\telse if (cb->dry_run)\n+\t\tprintf(\"would prune %s\", message);\n+\telse\n+\t\tprintf(\"prune %s\", message);\n+\n+\treturn expire;\n+}\n+\n+static int push_tip_to_list(const char *refname, const struct object_id *oid,\n+\t\t\t    int flags, void *cb_data)\n+{\n+\tstruct commit_list **list = cb_data;\n+\tstruct commit *tip_commit;\n+\tif (flags & REF_ISSYMREF)\n+\t\treturn 0;\n+\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!tip_commit)\n+\t\treturn 0;\n+\tcommit_list_insert(tip_commit, list);\n+\treturn 0;\n+}\n+\n+static int is_head(const char *refname)\n+{\n+\tswitch (ref_type(refname)) {\n+\tcase REF_TYPE_OTHER_PSEUDOREF:\n+\tcase REF_TYPE_MAIN_PSEUDOREF:\n+\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n+\t\t\tBUG(\"not a worktree ref: %s\", refname);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\treturn !strcmp(refname, \"HEAD\");\n+}\n+\n+void reflog_expiry_prepare(const char *refname,\n+\t\t\t\t  const struct object_id *oid,\n+\t\t\t\t  void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\tstruct commit *commit = NULL;\n+\n+\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n+\t\tcb->unreachable_expire_kind = UE_HEAD;\n+\t} else {\n+\t\tcommit = lookup_commit(the_repository, oid);\n+\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n+\t}\n+\n+\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n+\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tcommit_list_insert(commit, &cb->mark_list);\n+\t\t/* For reflog_expiry_cleanup() below */\n+\t\tcb->tip_commit = commit;\n+\t}\n+\tcb->mark_limit = cb->cmd.expire_total;\n+\tmark_reachable(cb);\n+}\n+\n+void reflog_expiry_cleanup(void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tclear_commit_marks(elem->item, REACHABLE);\n+\t\tfree_commit_list(cb->tips);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n+\t\tbreak;\n+\t}\n+}\n+\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\tconst char *email, timestamp_t timestamp, int tz,\n+\t\tconst char *message, void *cb_data)\n+{\n+\tstruct cmd_reflog_expire_cb *cb = cb_data;\n+\tif (!cb->expire_total || timestamp < cb->expire_total)\n+\t\tcb->recno++;\n+\treturn 0;\n+}\n+\n+int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose)\n+{\n+\tstruct cmd_reflog_expire_cb cmd = { 0 };\n+\tint status = 0;\n+\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n+\tconst char *spec = strstr(rev, \"@{\");\n+\tchar *ep, *ref;\n+\tint recno;\n+\tstruct expire_reflog_policy_cb cb = {\n+\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n+\t};\n+\n+\tif (verbose)\n+\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n+\n+\tif (!spec)\n+\t\treturn error(_(\"not a reflog: %s\"), rev);\n+\n+\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n+\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n+\t\tgoto cleanup;\n+\t}\n+\n+\trecno = strtoul(spec + 2, &ep, 10);\n+\tif (*ep == '}') {\n+\t\tcmd.recno = -recno;\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t} else {\n+\t\tcmd.expire_total = approxidate(spec + 2);\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t\tcmd.expire_total = 0;\n+\t}\n+\n+\tcb.cmd = cmd;\n+\tstatus |= reflog_expire(ref, flags,\n+\t\t\t\treflog_expiry_prepare,\n+\t\t\t\tshould_prune_fn,\n+\t\t\t\treflog_expiry_cleanup,\n+\t\t\t\t&cb);\n+\n+ cleanup:\n+\tfree(ref);\n+\treturn status;\n+}\ndiff --git a/reflog.h b/reflog.h\nnew file mode 100644\nindex 00000000000..d2906fb9f8d\n--- /dev/null\n+++ b/reflog.h\n@@ -0,0 +1,43 @@\n+#ifndef REFLOG_H\n+#define REFLOG_H\n+#include \"refs.h\"\n+\n+struct cmd_reflog_expire_cb {\n+\tint stalefix;\n+\tint explicit_expiry;\n+\ttimestamp_t expire_total;\n+\ttimestamp_t expire_unreachable;\n+\tint recno;\n+};\n+\n+struct expire_reflog_policy_cb {\n+\tenum {\n+\t\tUE_NORMAL,\n+\t\tUE_ALWAYS,\n+\t\tUE_HEAD\n+\t} unreachable_expire_kind;\n+\tstruct commit_list *mark_list;\n+\tunsigned long mark_limit;\n+\tstruct cmd_reflog_expire_cb cmd;\n+\tstruct commit *tip_commit;\n+\tstruct commit_list *tips;\n+\tunsigned int dry_run:1;\n+};\n+\n+int reflog_delete(const char *rev, enum expire_reflog_flags flags,\n+\t\t  int verbose);\n+void reflog_expiry_cleanup(void *cb_data);\n+void reflog_expiry_prepare(const char *refname, const struct object_id *oid,\n+\t\t\t   void *cb_data);\n+int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t\t     const char *email, timestamp_t timestamp, int tz,\n+\t\t\t     const char *message, void *cb_data);\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t     const char *email, timestamp_t timestamp, int tz,\n+\t\t     const char *message, void *cb_data);\n+int should_expire_reflog_ent_verbose(struct object_id *ooid,\n+\t\t\t\t     struct object_id *noid,\n+\t\t\t\t     const char *email,\n+\t\t\t\t     timestamp_t timestamp, int tz,\n+\t\t\t\t     const char *message, void *cb_data);\n+#endif /* REFLOG_H */\n-- \ngitgitgadget\n\n"},{"id":"449631","messageId":"YhkwG5JGNKB2yl3i@nand.local","threadId":"57437","inReplyTo":"pull.1218.v3.git.git.1645817452.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/3] libify reflog","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-02-25T19:38:03Z","receivedAt":"2022-02-25T19:38:12Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Feb 25, 2022 at 07:30:49PM +0000, John Cai via GitGitGadget wrote:\n> John Cai (3):\n>   stash: add tests to ensure reflog --rewrite --updatref behavior\n>   reflog: libify delete reflog function and helpers\n>   stash: call reflog_delete() in reflog.c\n>\n>  Makefile         |   1 +\n>  builtin/reflog.c | 455 +----------------------------------------------\n>  builtin/stash.c  |  18 +-\n>  object.h         |   2 +-\n>  reflog.c         | 432 ++++++++++++++++++++++++++++++++++++++++++++\n>  reflog.h         |  43 +++++\n>  t/t3903-stash.sh |  65 +++++--\n>  7 files changed, 539 insertions(+), 477 deletions(-)\n>  create mode 100644 reflog.c\n>  create mode 100644 reflog.h\n\nThanks; I glossed over the discussion about tests (since it looks like\nyou and Ævar already have a good handle on how things are going there).\n\nThe rest of this version of the series (which I looked at more closely)\nlooks good to me.\n\nThanks\nTaylor\n"},{"id":"450112","messageId":"69041BB1-569D-4462-A1CE-2F1BDA9A76C0@gmail.com","threadId":"57437","inReplyTo":"YhkwG5JGNKB2yl3i@nand.local","subject":"Re: [PATCH v3 0/3] libify reflog","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-03-02T16:43:29Z","receivedAt":"2022-03-02T16:43:35Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi,\n\nJust wanted to bump this thread. It'd be good to get another ack on these\nlast set of changes.\n\nOn 25 Feb 2022, at 14:38, Taylor Blau wrote:\n\n> On Fri, Feb 25, 2022 at 07:30:49PM +0000, John Cai via GitGitGadget wrote:\n>> John Cai (3):\n>>   stash: add tests to ensure reflog --rewrite --updatref behavior\n>>   reflog: libify delete reflog function and helpers\n>>   stash: call reflog_delete() in reflog.c\n>>\n>>  Makefile         |   1 +\n>>  builtin/reflog.c | 455 +----------------------------------------------\n>>  builtin/stash.c  |  18 +-\n>>  object.h         |   2 +-\n>>  reflog.c         | 432 ++++++++++++++++++++++++++++++++++++++++++++\n>>  reflog.h         |  43 +++++\n>>  t/t3903-stash.sh |  65 +++++--\n>>  7 files changed, 539 insertions(+), 477 deletions(-)\n>>  create mode 100644 reflog.c\n>>  create mode 100644 reflog.h\n>\n> Thanks; I glossed over the discussion about tests (since it looks like\n> you and Ævar already have a good handle on how things are going there).\n>\n> The rest of this version of the series (which I looked at more closely)\n> looks good to me.\n>\n> Thanks\n> Taylor\n"},{"id":"450148","messageId":"220302.86ee3k6v9a.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"33299825fc47fb0e2bc54e1f125282e7825e1924.1645817452.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-02T18:52:25Z","receivedAt":"2022-03-02T18:54:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 25 2022, John Cai via GitGitGadget wrote:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> There is missing test coverage to ensure that the resulting reflogs\n> after a git stash drop has had its old oid rewritten if applicable, and\n> if the refs/stash has been updated if applicable.\n>\n> Add two tests that verify both of these happen.\n>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n> ---\n>  t/t3903-stash.sh | 65 ++++++++++++++++++++++++++++++++++++++++--------\n>  1 file changed, 54 insertions(+), 11 deletions(-)\n>\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index b149e2af441..73785cf862f 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -41,20 +41,29 @@ diff_cmp () {\n>  \trm -f \"$1.compare\" \"$2.compare\"\n>  }\n>  \n> -test_expect_success 'stash some dirty working directory' '\n> -\techo 1 >file &&\n> -\tgit add file &&\n> -\techo unrelated >other-file &&\n> -\tgit add other-file &&\n> +setup_stash() {\n> +\trepo_dir=$1\n> +\tif test -z $repo_dir; then\n> +\t\trepo_dir=\".\"\n> +\tfi\n> +\n> +\techo 1 >$repo_dir/file &&\n> +\tgit -C $repo_dir add file &&\n> +\techo unrelated >$repo_dir/other-file &&\n> +\tgit -C $repo_dir add other-file &&\n>  \ttest_tick &&\n> -\tgit commit -m initial &&\n> -\techo 2 >file &&\n> +\tgit -C $repo_dir commit -m initial &&\n> +\techo 2 >$repo_dir/file &&\n>  \tgit add file &&\n> -\techo 3 >file &&\n> +\techo 3 >$repo_dir/file &&\n>  \ttest_tick &&\n> -\tgit stash &&\n> -\tgit diff-files --quiet &&\n> -\tgit diff-index --cached --quiet HEAD\n> +\tgit -C $repo_dir stash &&\n> +\tgit -C $repo_dir diff-files --quiet &&\n> +\tgit -C $repo_dir diff-index --cached --quiet HEAD\n> +}\n> +\n> +test_expect_success 'stash some dirty working directory' '\n> +\tsetup_stash\n>  '\n>  \n>  cat >expect <<EOF\n> @@ -185,6 +194,40 @@ test_expect_success 'drop middle stash by index' '\n>  \ttest 1 = $(git show HEAD:file)\n>  '\n>  \n> +test_expect_success 'drop stash reflog updates refs/stash' '\n> +\tgit reset --hard &&\n> +\tgit rev-parse refs/stash >expect &&\n> +\techo 9 >file &&\n> +\tgit stash &&\n> +\tgit stash drop stash@{0} &&\n> +\tgit rev-parse refs/stash >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n> +\tgit init repo &&\n> +\tsetup_stash repo &&\n> +\techo 9 >repo/file &&\n> +\n> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n> +\tgit -C repo stash &&\n> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n> +\n> +\tcat >expect <<-EOF &&\n> +\t$(test_oid zero) $old_oid\n> +\t$old_oid $new_oid\n> +\tEOF\n> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n> +\ttest_cmp expect actual &&\n> +\n> +\tgit -C repo stash drop stash@{1} &&\n> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t$(test_oid zero) $new_oid\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'stash pop' '\n>  \tgit reset --hard &&\n>  \tgit stash pop &&\n\nI just looked at the post-image of this and didn't notice it was altered\nexisting code at first, so I was going to suggest just this:\n\t\n\tdiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n\tindex 73785cf862f..422d1f7a306 100755\n\t--- a/t/t3903-stash.sh\n\t+++ b/t/t3903-stash.sh\n\t@@ -42,24 +42,14 @@ diff_cmp () {\n\t }\n\t \n\t setup_stash() {\n\t-\trepo_dir=$1\n\t-\tif test -z $repo_dir; then\n\t-\t\trepo_dir=\".\"\n\t-\tfi\n\t-\n\t-\techo 1 >$repo_dir/file &&\n\t-\tgit -C $repo_dir add file &&\n\t-\techo unrelated >$repo_dir/other-file &&\n\t-\tgit -C $repo_dir add other-file &&\n\t-\ttest_tick &&\n\t-\tgit -C $repo_dir commit -m initial &&\n\t-\techo 2 >$repo_dir/file &&\n\t+\ttest_commit initial file 1 &&\n\t+\ttest_commit second other-file unrelated &&\n\t+\techo 2 >file &&\n\t \tgit add file &&\n\t-\techo 3 >$repo_dir/file &&\n\t-\ttest_tick &&\n\t-\tgit -C $repo_dir stash &&\n\t-\tgit -C $repo_dir diff-files --quiet &&\n\t-\tgit -C $repo_dir diff-index --cached --quiet HEAD\n\t+\techo 3 >file &&\n\t+\tgit stash &&\n\t+\tgit diff-files --quiet &&\n\t+\tgit diff-index --cached --quiet HEAD\n\t }\n\t \n\t test_expect_success 'stash some dirty working directory' '\n\t@@ -206,7 +196,10 @@ test_expect_success 'drop stash reflog updates refs/stash' '\n\t \n\t test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n\t \tgit init repo &&\n\t-\tsetup_stash repo &&\n\t+\t(\n\t+\t\tcd repo &&\n\t+\t\tsetup_stash repo\n\t+\t) &&\n\t \techo 9 >repo/file &&\n\t \n\t \told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n\nBut I think much better as a replacement is:\n\t\n\tdiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n\tindex b149e2af441..9dd05a497bc 100755\n\t--- a/t/t3903-stash.sh\n\t+++ b/t/t3903-stash.sh\n\t@@ -41,7 +41,7 @@ diff_cmp () {\n\t \trm -f \"$1.compare\" \"$2.compare\"\n\t }\n\t \n\t-test_expect_success 'stash some dirty working directory' '\n\t+setup_stash() {\n\t \techo 1 >file &&\n\t \tgit add file &&\n\t \techo unrelated >other-file &&\n\t@@ -55,6 +55,10 @@ test_expect_success 'stash some dirty working directory' '\n\t \tgit stash &&\n\t \tgit diff-files --quiet &&\n\t \tgit diff-index --cached --quiet HEAD\n\t+}\n\t+\n\t+test_expect_success 'stash some dirty working directory' '\n\t+\tsetup_stash\n\t '\n\t \n\t cat >expect <<EOF\n\t@@ -185,6 +189,43 @@ test_expect_success 'drop middle stash by index' '\n\t \ttest 1 = $(git show HEAD:file)\n\t '\n\t \n\t+test_expect_success 'drop stash reflog updates refs/stash' '\n\t+\tgit reset --hard &&\n\t+\tgit rev-parse refs/stash >expect &&\n\t+\techo 9 >file &&\n\t+\tgit stash &&\n\t+\tgit stash drop stash@{0} &&\n\t+\tgit rev-parse refs/stash >actual &&\n\t+\ttest_cmp expect actual\n\t+'\n\t+\n\t+test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n\t+\tgit init repo &&\n\t+\t(\n\t+\t\tcd repo &&\n\t+\t\tsetup_stash repo\n\t+\t) &&\n\t+\techo 9 >repo/file &&\n\t+\n\t+\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n\t+\tgit -C repo stash &&\n\t+\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n\t+\n\t+\tcat >expect <<-EOF &&\n\t+\t$(test_oid zero) $old_oid\n\t+\t$old_oid $new_oid\n\t+\tEOF\n\t+\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n\t+\ttest_cmp expect actual &&\n\t+\n\t+\tgit -C repo stash drop stash@{1} &&\n\t+\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n\t+\tcat >expect <<-EOF &&\n\t+\t$(test_oid zero) $new_oid\n\t+\tEOF\n\t+\ttest_cmp expect actual\n\t+'\n\t+\n\t test_expect_success 'stash pop' '\n\t \tgit reset --hard &&\n\t \tgit stash pop &&\n\nI>e. the only reason you need to add that -C etc. is because you'd like\nto set it up in a subdirectory. Easier to just add the tiny bity of\nlogic to make that a sub-shell instead.\n\n"},{"id":"450150","messageId":"220302.86a6e86v35.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"69041BB1-569D-4462-A1CE-2F1BDA9A76C0@gmail.com","subject":"Re: [PATCH v3 0/3] libify reflog","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-02T18:55:52Z","receivedAt":"2022-03-02T18:57:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 02 2022, John Cai wrote:\n\n> Just wanted to bump this thread. It'd be good to get another ack on these\n> last set of changes.\n\nHi. Sorry that I didn't look at it earlier.\n\nThis looks good to me and I think everything that's been brought up has\nbeen addressed.\n\nI left a nit on 1/3 suggesting a way to make that diff a bit smaller by\nusing a subshell instead of refactoring an existing function.\n\nBut I think with or without that & a ro-roll this would be good to\nadvance to \"next\" etc.\n\nThanks!\n\n"},{"id":"450185","messageId":"pull.1218.v4.git.git.1646260044.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v3.git.git.1645817452.gitgitgadget@gmail.com","subject":"[PATCH v4 0/3] libify reflog","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-03-02T22:27:21Z","receivedAt":"2022-03-02T22:27:30Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"In [1], there was a discussion around a bug report of stash not recovering\nin the middle of the process when killed with ctl-c. It turned out to not be\na bug we need to fix. However, out of that discussion came the idea of\nlibifying reflog. This can stand alone as a code improvement.\n\nstash.c currently shells out to call reflog to delete reflogs. Libify reflog\ndelete and call it from both builtin/reflog.c and builtin/stash.c.\n\nThis patch has three parts:\n\n * add missing test coverage for git stash delete\n * libify reflog's delete functionality and move some of the helpers into a\n   reflog.c library and call reflog_delete from builtin/reflog.c\n * call reflog_delete from builtin/stash.c\n\nUpdates since v3:\n\n * refactored test to have a smaller diff\n\nUpdates since v2:\n\n * removed unnecessary includes\n * adjusted wrapping/whitespace in reflog.h\n * adjusted test to be isolated from other tests since currently tests for\n   stash depend on each other. There was some discussion around this and\n   even a possibility to refactor the tests. However, it would have been a\n   larger effort than is worth for this series, so instead I just made one\n   of the tests I added be isolated from the others.\n\nUpdates since v1:\n\n * added missing test coverage\n * squashed 1/3 and 2/3 together\n * moved enum into reflog.c\n * updated object.h's flag allocation mapping\n\n 1. https://lore.kernel.org/git/220126.86h79qe692.gmgdl@evledraar.gmail.com/\n\nJohn Cai (3):\n  stash: add tests to ensure reflog --rewrite --updatref behavior\n  reflog: libify delete reflog function and helpers\n  stash: call reflog_delete() in reflog.c\n\n Makefile         |   1 +\n builtin/reflog.c | 455 +----------------------------------------------\n builtin/stash.c  |  18 +-\n object.h         |   2 +-\n reflog.c         | 432 ++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  43 +++++\n t/t3903-stash.sh |  43 ++++-\n 7 files changed, 527 insertions(+), 467 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\n\nbase-commit: e6ebfd0e8cbbd10878070c8a356b5ad1b3ca464e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1218%2Fjohn-cai%2Fjc-libify-reflog-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1218/john-cai/jc-libify-reflog-v4\nPull-Request: https://github.com/git/git/pull/1218\n\nRange-diff vs v3:\n\n 1:  33299825fc4 ! 1:  08bb8d3a9b9 stash: add tests to ensure reflog --rewrite --updatref behavior\n     @@ t/t3903-stash.sh: diff_cmp () {\n       }\n       \n      -test_expect_success 'stash some dirty working directory' '\n     --\techo 1 >file &&\n     --\tgit add file &&\n     --\techo unrelated >other-file &&\n     --\tgit add other-file &&\n      +setup_stash() {\n     -+\trepo_dir=$1\n     -+\tif test -z $repo_dir; then\n     -+\t\trepo_dir=\".\"\n     -+\tfi\n     -+\n     -+\techo 1 >$repo_dir/file &&\n     -+\tgit -C $repo_dir add file &&\n     -+\techo unrelated >$repo_dir/other-file &&\n     -+\tgit -C $repo_dir add other-file &&\n     - \ttest_tick &&\n     --\tgit commit -m initial &&\n     --\techo 2 >file &&\n     -+\tgit -C $repo_dir commit -m initial &&\n     -+\techo 2 >$repo_dir/file &&\n     + \techo 1 >file &&\n       \tgit add file &&\n     --\techo 3 >file &&\n     -+\techo 3 >$repo_dir/file &&\n     - \ttest_tick &&\n     --\tgit stash &&\n     --\tgit diff-files --quiet &&\n     --\tgit diff-index --cached --quiet HEAD\n     -+\tgit -C $repo_dir stash &&\n     -+\tgit -C $repo_dir diff-files --quiet &&\n     -+\tgit -C $repo_dir diff-index --cached --quiet HEAD\n     + \techo unrelated >other-file &&\n     +@@ t/t3903-stash.sh: test_expect_success 'stash some dirty working directory' '\n     + \tgit stash &&\n     + \tgit diff-files --quiet &&\n     + \tgit diff-index --cached --quiet HEAD\n      +}\n      +\n      +test_expect_success 'stash some dirty working directory' '\n     @@ t/t3903-stash.sh: test_expect_success 'drop middle stash by index' '\n      +\n      +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n      +\tgit init repo &&\n     -+\tsetup_stash repo &&\n     ++\t(\n     ++\t\tcd repo &&\n     ++\t\tsetup_stash\n     ++\t) &&\n      +\techo 9 >repo/file &&\n      +\n      +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n 2:  33adfee4ca6 = 2:  50471c2ee6f reflog: libify delete reflog function and helpers\n 3:  b17d8e5d43a = 3:  cb32d9bfe60 stash: call reflog_delete() in reflog.c\n\n-- \ngitgitgadget\n"},{"id":"450186","messageId":"08bb8d3a9b9cd75c8b2aed11db9456baef6f415b.1646260044.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v4.git.git.1646260044.gitgitgadget@gmail.com","subject":"[PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-03-02T22:27:22Z","receivedAt":"2022-03-02T22:27:33Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nThere is missing test coverage to ensure that the resulting reflogs\nafter a git stash drop has had its old oid rewritten if applicable, and\nif the refs/stash has been updated if applicable.\n\nAdd two tests that verify both of these happen.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n t/t3903-stash.sh | 43 ++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 42 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex b149e2af441..a2f8d0c52e7 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -41,7 +41,7 @@ diff_cmp () {\n \trm -f \"$1.compare\" \"$2.compare\"\n }\n \n-test_expect_success 'stash some dirty working directory' '\n+setup_stash() {\n \techo 1 >file &&\n \tgit add file &&\n \techo unrelated >other-file &&\n@@ -55,6 +55,10 @@ test_expect_success 'stash some dirty working directory' '\n \tgit stash &&\n \tgit diff-files --quiet &&\n \tgit diff-index --cached --quiet HEAD\n+}\n+\n+test_expect_success 'stash some dirty working directory' '\n+\tsetup_stash\n '\n \n cat >expect <<EOF\n@@ -185,6 +189,43 @@ test_expect_success 'drop middle stash by index' '\n \ttest 1 = $(git show HEAD:file)\n '\n \n+test_expect_success 'drop stash reflog updates refs/stash' '\n+\tgit reset --hard &&\n+\tgit rev-parse refs/stash >expect &&\n+\techo 9 >file &&\n+\tgit stash &&\n+\tgit stash drop stash@{0} &&\n+\tgit rev-parse refs/stash >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tsetup_stash\n+\t) &&\n+\techo 9 >repo/file &&\n+\n+\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n+\tgit -C repo stash &&\n+\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n+\n+\tcat >expect <<-EOF &&\n+\t$(test_oid zero) $old_oid\n+\t$old_oid $new_oid\n+\tEOF\n+\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tgit -C repo stash drop stash@{1} &&\n+\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n+\tcat >expect <<-EOF &&\n+\t$(test_oid zero) $new_oid\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'stash pop' '\n \tgit reset --hard &&\n \tgit stash pop &&\n-- \ngitgitgadget\n\n"},{"id":"450187","messageId":"cb32d9bfe60a6c5588260a88702bf058e6113ceb.1646260044.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v4.git.git.1646260044.gitgitgadget@gmail.com","subject":"[PATCH v4 3/3] stash: call reflog_delete() in reflog.c","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-03-02T22:27:24Z","receivedAt":"2022-03-02T22:27:34Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nNow that cmd_reflog_delete has been libified an exported it into a new\nreflog.c library so we can call it directly from builtin/stash.c. This\nnot only gives us a performance gain since we don't need to create a\nsubprocess, but it also allows us to use the ref transactions api in the\nfuture.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n builtin/stash.c | 18 ++++--------------\n 1 file changed, 4 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 5897febfbec..3e2f478b761 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -17,6 +17,7 @@\n #include \"diffcore.h\"\n #include \"exec-cmd.h\"\n #include \"entry.h\"\n+#include \"reflog.h\"\n \n #define INCLUDE_ALL_FILES 2\n \n@@ -634,20 +635,9 @@ static int reflog_is_empty(const char *refname)\n \n static int do_drop_stash(struct stash_info *info, int quiet)\n {\n-\tint ret;\n-\tstruct child_process cp_reflog = CHILD_PROCESS_INIT;\n-\n-\t/*\n-\t * reflog does not provide a simple function for deleting refs. One will\n-\t * need to be added to avoid implementing too much reflog code here\n-\t */\n-\n-\tcp_reflog.git_cmd = 1;\n-\tstrvec_pushl(&cp_reflog.args, \"reflog\", \"delete\", \"--updateref\",\n-\t\t     \"--rewrite\", NULL);\n-\tstrvec_push(&cp_reflog.args, info->revision.buf);\n-\tret = run_command(&cp_reflog);\n-\tif (!ret) {\n+\tif (!reflog_delete(info->revision.buf,\n+\t\t\t   EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF,\n+\t\t\t   0)) {\n \t\tif (!quiet)\n \t\t\tprintf_ln(_(\"Dropped %s (%s)\"), info->revision.buf,\n \t\t\t\t  oid_to_hex(&info->w_commit));\n-- \ngitgitgadget\n"},{"id":"450188","messageId":"50471c2ee6fb8897267d86c9fe655aabb564249c.1646260044.git.gitgitgadget@gmail.com","threadId":"57437","inReplyTo":"pull.1218.v4.git.git.1646260044.gitgitgadget@gmail.com","subject":"[PATCH v4 2/3] reflog: libify delete reflog function and helpers","fromName":"John Cai via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-03-02T22:27:23Z","receivedAt":"2022-03-02T22:27:42Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"From: John Cai <johncai86@gmail.com>\n\nCurrently stash shells out to reflog in order to delete refs. In an\neffort to reduce how much we shell out to a subprocess, libify the\nfunctionality that stash needs into reflog.c.\n\nAdd a reflog_delete function that is pretty much the logic in the while\nloop in builtin/reflog.c cmd_reflog_delete(). This is a function that\nbuiltin/reflog.c and builtin/stash.c can both call.\n\nAlso move functions needed by reflog_delete and export them.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: John Cai <johncai86@gmail.com>\n---\n Makefile         |   1 +\n builtin/reflog.c | 455 +----------------------------------------------\n object.h         |   2 +-\n reflog.c         | 432 ++++++++++++++++++++++++++++++++++++++++++++\n reflog.h         |  43 +++++\n 5 files changed, 481 insertions(+), 452 deletions(-)\n create mode 100644 reflog.c\n create mode 100644 reflog.h\n\ndiff --git a/Makefile b/Makefile\nindex 6f0b4b775fe..876d4dfd6cb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -989,6 +989,7 @@ LIB_OBJS += rebase-interactive.o\n LIB_OBJS += rebase.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += reflog-walk.o\n+LIB_OBJS += reflog.o\n LIB_OBJS += refs.o\n LIB_OBJS += refs/debug.o\n LIB_OBJS += refs/files-backend.o\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 85b838720c3..940db196f62 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -1,16 +1,9 @@\n #include \"builtin.h\"\n #include \"config.h\"\n-#include \"lockfile.h\"\n-#include \"object-store.h\"\n-#include \"repository.h\"\n-#include \"commit.h\"\n-#include \"refs.h\"\n-#include \"dir.h\"\n-#include \"tree-walk.h\"\n-#include \"diff.h\"\n #include \"revision.h\"\n #include \"reachable.h\"\n #include \"worktree.h\"\n+#include \"reflog.h\"\n \n static const char reflog_exists_usage[] =\n N_(\"git reflog exists <ref>\");\n@@ -18,404 +11,11 @@ N_(\"git reflog exists <ref>\");\n static timestamp_t default_reflog_expire;\n static timestamp_t default_reflog_expire_unreachable;\n \n-struct cmd_reflog_expire_cb {\n-\tint stalefix;\n-\tint explicit_expiry;\n-\ttimestamp_t expire_total;\n-\ttimestamp_t expire_unreachable;\n-\tint recno;\n-};\n-\n-struct expire_reflog_policy_cb {\n-\tenum {\n-\t\tUE_NORMAL,\n-\t\tUE_ALWAYS,\n-\t\tUE_HEAD\n-\t} unreachable_expire_kind;\n-\tstruct commit_list *mark_list;\n-\tunsigned long mark_limit;\n-\tstruct cmd_reflog_expire_cb cmd;\n-\tstruct commit *tip_commit;\n-\tstruct commit_list *tips;\n-\tunsigned int dry_run:1;\n-};\n-\n struct worktree_reflogs {\n \tstruct worktree *worktree;\n \tstruct string_list reflogs;\n };\n \n-/* Remember to update object flag allocation in object.h */\n-#define INCOMPLETE\t(1u<<10)\n-#define STUDYING\t(1u<<11)\n-#define REACHABLE\t(1u<<12)\n-\n-static int tree_is_complete(const struct object_id *oid)\n-{\n-\tstruct tree_desc desc;\n-\tstruct name_entry entry;\n-\tint complete;\n-\tstruct tree *tree;\n-\n-\ttree = lookup_tree(the_repository, oid);\n-\tif (!tree)\n-\t\treturn 0;\n-\tif (tree->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (tree->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\n-\tif (!tree->buffer) {\n-\t\tenum object_type type;\n-\t\tunsigned long size;\n-\t\tvoid *data = read_object_file(oid, &type, &size);\n-\t\tif (!data) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\treturn 0;\n-\t\t}\n-\t\ttree->buffer = data;\n-\t\ttree->size = size;\n-\t}\n-\tinit_tree_desc(&desc, tree->buffer, tree->size);\n-\tcomplete = 1;\n-\twhile (tree_entry(&desc, &entry)) {\n-\t\tif (!has_object_file(&entry.oid) ||\n-\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n-\t\t\ttree->object.flags |= INCOMPLETE;\n-\t\t\tcomplete = 0;\n-\t\t}\n-\t}\n-\tfree_tree_buffer(tree);\n-\n-\tif (complete)\n-\t\ttree->object.flags |= SEEN;\n-\treturn complete;\n-}\n-\n-static int commit_is_complete(struct commit *commit)\n-{\n-\tstruct object_array study;\n-\tstruct object_array found;\n-\tint is_incomplete = 0;\n-\tint i;\n-\n-\t/* early return */\n-\tif (commit->object.flags & SEEN)\n-\t\treturn 1;\n-\tif (commit->object.flags & INCOMPLETE)\n-\t\treturn 0;\n-\t/*\n-\t * Find all commits that are reachable and are not marked as\n-\t * SEEN.  Then make sure the trees and blobs contained are\n-\t * complete.  After that, mark these commits also as SEEN.\n-\t * If some of the objects that are needed to complete this\n-\t * commit are missing, mark this commit as INCOMPLETE.\n-\t */\n-\tmemset(&study, 0, sizeof(study));\n-\tmemset(&found, 0, sizeof(found));\n-\tadd_object_array(&commit->object, NULL, &study);\n-\tadd_object_array(&commit->object, NULL, &found);\n-\tcommit->object.flags |= STUDYING;\n-\twhile (study.nr) {\n-\t\tstruct commit *c;\n-\t\tstruct commit_list *parent;\n-\n-\t\tc = (struct commit *)object_array_pop(&study);\n-\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n-\t\t\tc->object.flags |= INCOMPLETE;\n-\n-\t\tif (c->object.flags & INCOMPLETE) {\n-\t\t\tis_incomplete = 1;\n-\t\t\tbreak;\n-\t\t}\n-\t\telse if (c->object.flags & SEEN)\n-\t\t\tcontinue;\n-\t\tfor (parent = c->parents; parent; parent = parent->next) {\n-\t\t\tstruct commit *p = parent->item;\n-\t\t\tif (p->object.flags & STUDYING)\n-\t\t\t\tcontinue;\n-\t\t\tp->object.flags |= STUDYING;\n-\t\t\tadd_object_array(&p->object, NULL, &study);\n-\t\t\tadd_object_array(&p->object, NULL, &found);\n-\t\t}\n-\t}\n-\tif (!is_incomplete) {\n-\t\t/*\n-\t\t * make sure all commits in \"found\" array have all the\n-\t\t * necessary objects.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++) {\n-\t\t\tstruct commit *c =\n-\t\t\t\t(struct commit *)found.objects[i].item;\n-\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n-\t\t\t\tis_incomplete = 1;\n-\t\t\t\tc->object.flags |= INCOMPLETE;\n-\t\t\t}\n-\t\t}\n-\t\tif (!is_incomplete) {\n-\t\t\t/* mark all found commits as complete, iow SEEN */\n-\t\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t\t}\n-\t}\n-\t/* clear flags from the objects we traversed */\n-\tfor (i = 0; i < found.nr; i++)\n-\t\tfound.objects[i].item->flags &= ~STUDYING;\n-\tif (is_incomplete)\n-\t\tcommit->object.flags |= INCOMPLETE;\n-\telse {\n-\t\t/*\n-\t\t * If we come here, we have (1) traversed the ancestry chain\n-\t\t * from the \"commit\" until we reach SEEN commits (which are\n-\t\t * known to be complete), and (2) made sure that the commits\n-\t\t * encountered during the above traversal refer to trees that\n-\t\t * are complete.  Which means that we know *all* the commits\n-\t\t * we have seen during this process are complete.\n-\t\t */\n-\t\tfor (i = 0; i < found.nr; i++)\n-\t\t\tfound.objects[i].item->flags |= SEEN;\n-\t}\n-\t/* free object arrays */\n-\tobject_array_clear(&study);\n-\tobject_array_clear(&found);\n-\treturn !is_incomplete;\n-}\n-\n-static int keep_entry(struct commit **it, struct object_id *oid)\n-{\n-\tstruct commit *commit;\n-\n-\tif (is_null_oid(oid))\n-\t\treturn 1;\n-\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!commit)\n-\t\treturn 0;\n-\n-\t/*\n-\t * Make sure everything in this commit exists.\n-\t *\n-\t * We have walked all the objects reachable from the refs\n-\t * and cache earlier.  The commits reachable by this commit\n-\t * must meet SEEN commits -- and then we should mark them as\n-\t * SEEN as well.\n-\t */\n-\tif (!commit_is_complete(commit))\n-\t\treturn 0;\n-\t*it = commit;\n-\treturn 1;\n-}\n-\n-/*\n- * Starting from commits in the cb->mark_list, mark commits that are\n- * reachable from them.  Stop the traversal at commits older than\n- * the expire_limit and queue them back, so that the caller can call\n- * us again to restart the traversal with longer expire_limit.\n- */\n-static void mark_reachable(struct expire_reflog_policy_cb *cb)\n-{\n-\tstruct commit_list *pending;\n-\ttimestamp_t expire_limit = cb->mark_limit;\n-\tstruct commit_list *leftover = NULL;\n-\n-\tfor (pending = cb->mark_list; pending; pending = pending->next)\n-\t\tpending->item->object.flags &= ~REACHABLE;\n-\n-\tpending = cb->mark_list;\n-\twhile (pending) {\n-\t\tstruct commit_list *parent;\n-\t\tstruct commit *commit = pop_commit(&pending);\n-\t\tif (commit->object.flags & REACHABLE)\n-\t\t\tcontinue;\n-\t\tif (parse_commit(commit))\n-\t\t\tcontinue;\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tif (commit->date < expire_limit) {\n-\t\t\tcommit_list_insert(commit, &leftover);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tcommit->object.flags |= REACHABLE;\n-\t\tparent = commit->parents;\n-\t\twhile (parent) {\n-\t\t\tcommit = parent->item;\n-\t\t\tparent = parent->next;\n-\t\t\tif (commit->object.flags & REACHABLE)\n-\t\t\t\tcontinue;\n-\t\t\tcommit_list_insert(commit, &pending);\n-\t\t}\n-\t}\n-\tcb->mark_list = leftover;\n-}\n-\n-static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n-{\n-\t/*\n-\t * We may or may not have the commit yet - if not, look it\n-\t * up using the supplied sha1.\n-\t */\n-\tif (!commit) {\n-\t\tif (is_null_oid(oid))\n-\t\t\treturn 0;\n-\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n-\t\t\t\t\t\t\t1);\n-\n-\t\t/* Not a commit -- keep it */\n-\t\tif (!commit)\n-\t\t\treturn 0;\n-\t}\n-\n-\t/* Reachable from the current ref?  Don't prune. */\n-\tif (commit->object.flags & REACHABLE)\n-\t\treturn 0;\n-\n-\tif (cb->mark_list && cb->mark_limit) {\n-\t\tcb->mark_limit = 0; /* dig down to the root */\n-\t\tmark_reachable(cb);\n-\t}\n-\n-\treturn !(commit->object.flags & REACHABLE);\n-}\n-\n-/*\n- * Return true iff the specified reflog entry should be expired.\n- */\n-static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n-\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit *old_commit, *new_commit;\n-\n-\tif (timestamp < cb->cmd.expire_total)\n-\t\treturn 1;\n-\n-\told_commit = new_commit = NULL;\n-\tif (cb->cmd.stalefix &&\n-\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n-\t\treturn 1;\n-\n-\tif (timestamp < cb->cmd.expire_unreachable) {\n-\t\tswitch (cb->unreachable_expire_kind) {\n-\t\tcase UE_ALWAYS:\n-\t\t\treturn 1;\n-\t\tcase UE_NORMAL:\n-\t\tcase UE_HEAD:\n-\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n-\t\t\t\treturn 1;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\n-\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n-\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n-static int should_expire_reflog_ent_verbose(struct object_id *ooid,\n-\t\t\t\t\t    struct object_id *noid,\n-\t\t\t\t\t    const char *email,\n-\t\t\t\t\t    timestamp_t timestamp, int tz,\n-\t\t\t\t\t    const char *message, void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tint expire;\n-\n-\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n-\t\t\t\t\t  message, cb);\n-\n-\tif (!expire)\n-\t\tprintf(\"keep %s\", message);\n-\telse if (cb->dry_run)\n-\t\tprintf(\"would prune %s\", message);\n-\telse\n-\t\tprintf(\"prune %s\", message);\n-\n-\treturn expire;\n-}\n-\n-static int push_tip_to_list(const char *refname, const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n-{\n-\tstruct commit_list **list = cb_data;\n-\tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n-\t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n-\tif (!tip_commit)\n-\t\treturn 0;\n-\tcommit_list_insert(tip_commit, list);\n-\treturn 0;\n-}\n-\n-static int is_head(const char *refname)\n-{\n-\tswitch (ref_type(refname)) {\n-\tcase REF_TYPE_OTHER_PSEUDOREF:\n-\tcase REF_TYPE_MAIN_PSEUDOREF:\n-\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n-\t\t\tBUG(\"not a worktree ref: %s\", refname);\n-\t\tbreak;\n-\tdefault:\n-\t\tbreak;\n-\t}\n-\treturn !strcmp(refname, \"HEAD\");\n-}\n-\n-static void reflog_expiry_prepare(const char *refname,\n-\t\t\t\t  const struct object_id *oid,\n-\t\t\t\t  void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\tstruct commit *commit = NULL;\n-\n-\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n-\t\tcb->unreachable_expire_kind = UE_HEAD;\n-\t} else {\n-\t\tcommit = lookup_commit(the_repository, oid);\n-\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n-\t}\n-\n-\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n-\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tcommit_list_insert(commit, &cb->mark_list);\n-\t\t/* For reflog_expiry_cleanup() below */\n-\t\tcb->tip_commit = commit;\n-\t}\n-\tcb->mark_limit = cb->cmd.expire_total;\n-\tmark_reachable(cb);\n-}\n-\n-static void reflog_expiry_cleanup(void *cb_data)\n-{\n-\tstruct expire_reflog_policy_cb *cb = cb_data;\n-\tstruct commit_list *elem;\n-\n-\tswitch (cb->unreachable_expire_kind) {\n-\tcase UE_ALWAYS:\n-\t\treturn;\n-\tcase UE_HEAD:\n-\t\tfor (elem = cb->tips; elem; elem = elem->next)\n-\t\t\tclear_commit_marks(elem->item, REACHABLE);\n-\t\tfree_commit_list(cb->tips);\n-\t\tbreak;\n-\tcase UE_NORMAL:\n-\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n-\t\tbreak;\n-\t}\n-}\n-\n static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)\n {\n \tstruct worktree_reflogs *cb = cb_data;\n@@ -704,16 +304,6 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n-static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n-\t\tconst char *email, timestamp_t timestamp, int tz,\n-\t\tconst char *message, void *cb_data)\n-{\n-\tstruct cmd_reflog_expire_cb *cb = cb_data;\n-\tif (!cb->expire_total || timestamp < cb->expire_total)\n-\t\tcb->recno++;\n-\treturn 0;\n-}\n-\n static const char * reflog_delete_usage[] = {\n \tN_(\"git reflog delete [--rewrite] [--updateref] \"\n \t   \"[--dry-run | -n] [--verbose] <refs>...\"),\n@@ -722,11 +312,10 @@ static const char * reflog_delete_usage[] = {\n \n static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n {\n-\tstruct cmd_reflog_expire_cb cmd = { 0 };\n \tint i, status = 0;\n \tunsigned int flags = 0;\n \tint verbose = 0;\n-\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n+\n \tconst struct option options[] = {\n \t\tOPT_BIT(0, \"dry-run\", &flags, N_(\"do not actually prune any entries\"),\n \t\t\tEXPIRE_REFLOGS_DRY_RUN),\n@@ -742,48 +331,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);\n \n-\tif (verbose)\n-\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n-\n \tif (argc < 1)\n \t\treturn error(_(\"no reflog specified to delete\"));\n \n-\tfor (i = 0; i < argc; i++) {\n-\t\tconst char *spec = strstr(argv[i], \"@{\");\n-\t\tchar *ep, *ref;\n-\t\tint recno;\n-\t\tstruct expire_reflog_policy_cb cb = {\n-\t\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n-\t\t};\n-\n-\t\tif (!spec) {\n-\t\t\tstatus |= error(_(\"not a reflog: %s\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n+\tfor (i = 0; i < argc; i++)\n+\t\tstatus |= reflog_delete(argv[i], flags, verbose);\n \n-\t\tif (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {\n-\t\t\tstatus |= error(_(\"no reflog for '%s'\"), argv[i]);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\trecno = strtoul(spec + 2, &ep, 10);\n-\t\tif (*ep == '}') {\n-\t\t\tcmd.recno = -recno;\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t} else {\n-\t\t\tcmd.expire_total = approxidate(spec + 2);\n-\t\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n-\t\t\tcmd.expire_total = 0;\n-\t\t}\n-\n-\t\tcb.cmd = cmd;\n-\t\tstatus |= reflog_expire(ref, flags,\n-\t\t\t\t\treflog_expiry_prepare,\n-\t\t\t\t\tshould_prune_fn,\n-\t\t\t\t\treflog_expiry_cleanup,\n-\t\t\t\t\t&cb);\n-\t\tfree(ref);\n-\t}\n \treturn status;\n }\n \ndiff --git a/object.h b/object.h\nindex cb556ab7753..a2219464c2b 100644\n--- a/object.h\n+++ b/object.h\n@@ -75,7 +75,7 @@ struct object_array {\n  * builtin/fsck.c:           0--3\n  * builtin/gc.c:             0\n  * builtin/index-pack.c:                                     2021\n- * builtin/reflog.c:                   10--12\n+ * reflog.c:                           10--12\n  * builtin/show-branch.c:    0-------------------------------------------26\n  * builtin/unpack-objects.c:                                 2021\n  */\ndiff --git a/reflog.c b/reflog.c\nnew file mode 100644\nindex 00000000000..333fd8708fe\n--- /dev/null\n+++ b/reflog.c\n@@ -0,0 +1,432 @@\n+#include \"cache.h\"\n+#include \"object-store.h\"\n+#include \"reflog.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"worktree.h\"\n+\n+/* Remember to update object flag allocation in object.h */\n+#define INCOMPLETE\t(1u<<10)\n+#define STUDYING\t(1u<<11)\n+#define REACHABLE\t(1u<<12)\n+\n+static int tree_is_complete(const struct object_id *oid)\n+{\n+\tstruct tree_desc desc;\n+\tstruct name_entry entry;\n+\tint complete;\n+\tstruct tree *tree;\n+\n+\ttree = lookup_tree(the_repository, oid);\n+\tif (!tree)\n+\t\treturn 0;\n+\tif (tree->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (tree->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\n+\tif (!tree->buffer) {\n+\t\tenum object_type type;\n+\t\tunsigned long size;\n+\t\tvoid *data = read_object_file(oid, &type, &size);\n+\t\tif (!data) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\treturn 0;\n+\t\t}\n+\t\ttree->buffer = data;\n+\t\ttree->size = size;\n+\t}\n+\tinit_tree_desc(&desc, tree->buffer, tree->size);\n+\tcomplete = 1;\n+\twhile (tree_entry(&desc, &entry)) {\n+\t\tif (!has_object_file(&entry.oid) ||\n+\t\t    (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {\n+\t\t\ttree->object.flags |= INCOMPLETE;\n+\t\t\tcomplete = 0;\n+\t\t}\n+\t}\n+\tfree_tree_buffer(tree);\n+\n+\tif (complete)\n+\t\ttree->object.flags |= SEEN;\n+\treturn complete;\n+}\n+\n+static int commit_is_complete(struct commit *commit)\n+{\n+\tstruct object_array study;\n+\tstruct object_array found;\n+\tint is_incomplete = 0;\n+\tint i;\n+\n+\t/* early return */\n+\tif (commit->object.flags & SEEN)\n+\t\treturn 1;\n+\tif (commit->object.flags & INCOMPLETE)\n+\t\treturn 0;\n+\t/*\n+\t * Find all commits that are reachable and are not marked as\n+\t * SEEN.  Then make sure the trees and blobs contained are\n+\t * complete.  After that, mark these commits also as SEEN.\n+\t * If some of the objects that are needed to complete this\n+\t * commit are missing, mark this commit as INCOMPLETE.\n+\t */\n+\tmemset(&study, 0, sizeof(study));\n+\tmemset(&found, 0, sizeof(found));\n+\tadd_object_array(&commit->object, NULL, &study);\n+\tadd_object_array(&commit->object, NULL, &found);\n+\tcommit->object.flags |= STUDYING;\n+\twhile (study.nr) {\n+\t\tstruct commit *c;\n+\t\tstruct commit_list *parent;\n+\n+\t\tc = (struct commit *)object_array_pop(&study);\n+\t\tif (!c->object.parsed && !parse_object(the_repository, &c->object.oid))\n+\t\t\tc->object.flags |= INCOMPLETE;\n+\n+\t\tif (c->object.flags & INCOMPLETE) {\n+\t\t\tis_incomplete = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t\telse if (c->object.flags & SEEN)\n+\t\t\tcontinue;\n+\t\tfor (parent = c->parents; parent; parent = parent->next) {\n+\t\t\tstruct commit *p = parent->item;\n+\t\t\tif (p->object.flags & STUDYING)\n+\t\t\t\tcontinue;\n+\t\t\tp->object.flags |= STUDYING;\n+\t\t\tadd_object_array(&p->object, NULL, &study);\n+\t\t\tadd_object_array(&p->object, NULL, &found);\n+\t\t}\n+\t}\n+\tif (!is_incomplete) {\n+\t\t/*\n+\t\t * make sure all commits in \"found\" array have all the\n+\t\t * necessary objects.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++) {\n+\t\t\tstruct commit *c =\n+\t\t\t\t(struct commit *)found.objects[i].item;\n+\t\t\tif (!tree_is_complete(get_commit_tree_oid(c))) {\n+\t\t\t\tis_incomplete = 1;\n+\t\t\t\tc->object.flags |= INCOMPLETE;\n+\t\t\t}\n+\t\t}\n+\t\tif (!is_incomplete) {\n+\t\t\t/* mark all found commits as complete, iow SEEN */\n+\t\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t\t}\n+\t}\n+\t/* clear flags from the objects we traversed */\n+\tfor (i = 0; i < found.nr; i++)\n+\t\tfound.objects[i].item->flags &= ~STUDYING;\n+\tif (is_incomplete)\n+\t\tcommit->object.flags |= INCOMPLETE;\n+\telse {\n+\t\t/*\n+\t\t * If we come here, we have (1) traversed the ancestry chain\n+\t\t * from the \"commit\" until we reach SEEN commits (which are\n+\t\t * known to be complete), and (2) made sure that the commits\n+\t\t * encountered during the above traversal refer to trees that\n+\t\t * are complete.  Which means that we know *all* the commits\n+\t\t * we have seen during this process are complete.\n+\t\t */\n+\t\tfor (i = 0; i < found.nr; i++)\n+\t\t\tfound.objects[i].item->flags |= SEEN;\n+\t}\n+\t/* free object arrays */\n+\tobject_array_clear(&study);\n+\tobject_array_clear(&found);\n+\treturn !is_incomplete;\n+}\n+\n+static int keep_entry(struct commit **it, struct object_id *oid)\n+{\n+\tstruct commit *commit;\n+\n+\tif (is_null_oid(oid))\n+\t\treturn 1;\n+\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!commit)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Make sure everything in this commit exists.\n+\t *\n+\t * We have walked all the objects reachable from the refs\n+\t * and cache earlier.  The commits reachable by this commit\n+\t * must meet SEEN commits -- and then we should mark them as\n+\t * SEEN as well.\n+\t */\n+\tif (!commit_is_complete(commit))\n+\t\treturn 0;\n+\t*it = commit;\n+\treturn 1;\n+}\n+\n+/*\n+ * Starting from commits in the cb->mark_list, mark commits that are\n+ * reachable from them.  Stop the traversal at commits older than\n+ * the expire_limit and queue them back, so that the caller can call\n+ * us again to restart the traversal with longer expire_limit.\n+ */\n+static void mark_reachable(struct expire_reflog_policy_cb *cb)\n+{\n+\tstruct commit_list *pending;\n+\ttimestamp_t expire_limit = cb->mark_limit;\n+\tstruct commit_list *leftover = NULL;\n+\n+\tfor (pending = cb->mark_list; pending; pending = pending->next)\n+\t\tpending->item->object.flags &= ~REACHABLE;\n+\n+\tpending = cb->mark_list;\n+\twhile (pending) {\n+\t\tstruct commit_list *parent;\n+\t\tstruct commit *commit = pop_commit(&pending);\n+\t\tif (commit->object.flags & REACHABLE)\n+\t\t\tcontinue;\n+\t\tif (parse_commit(commit))\n+\t\t\tcontinue;\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tif (commit->date < expire_limit) {\n+\t\t\tcommit_list_insert(commit, &leftover);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tcommit->object.flags |= REACHABLE;\n+\t\tparent = commit->parents;\n+\t\twhile (parent) {\n+\t\t\tcommit = parent->item;\n+\t\t\tparent = parent->next;\n+\t\t\tif (commit->object.flags & REACHABLE)\n+\t\t\t\tcontinue;\n+\t\t\tcommit_list_insert(commit, &pending);\n+\t\t}\n+\t}\n+\tcb->mark_list = leftover;\n+}\n+\n+static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)\n+{\n+\t/*\n+\t * We may or may not have the commit yet - if not, look it\n+\t * up using the supplied sha1.\n+\t */\n+\tif (!commit) {\n+\t\tif (is_null_oid(oid))\n+\t\t\treturn 0;\n+\n+\t\tcommit = lookup_commit_reference_gently(the_repository, oid,\n+\t\t\t\t\t\t\t1);\n+\n+\t\t/* Not a commit -- keep it */\n+\t\tif (!commit)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Reachable from the current ref?  Don't prune. */\n+\tif (commit->object.flags & REACHABLE)\n+\t\treturn 0;\n+\n+\tif (cb->mark_list && cb->mark_limit) {\n+\t\tcb->mark_limit = 0; /* dig down to the root */\n+\t\tmark_reachable(cb);\n+\t}\n+\n+\treturn !(commit->object.flags & REACHABLE);\n+}\n+\n+/*\n+ * Return true iff the specified reflog entry should be expired.\n+ */\n+int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t\t\t    const char *email, timestamp_t timestamp, int tz,\n+\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit *old_commit, *new_commit;\n+\n+\tif (timestamp < cb->cmd.expire_total)\n+\t\treturn 1;\n+\n+\told_commit = new_commit = NULL;\n+\tif (cb->cmd.stalefix &&\n+\t    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))\n+\t\treturn 1;\n+\n+\tif (timestamp < cb->cmd.expire_unreachable) {\n+\t\tswitch (cb->unreachable_expire_kind) {\n+\t\tcase UE_ALWAYS:\n+\t\t\treturn 1;\n+\t\tcase UE_NORMAL:\n+\t\tcase UE_HEAD:\n+\t\t\tif (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))\n+\t\t\t\treturn 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (cb->cmd.recno && --(cb->cmd.recno) == 0)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+int should_expire_reflog_ent_verbose(struct object_id *ooid,\n+\t\t\t\t\t    struct object_id *noid,\n+\t\t\t\t\t    const char *email,\n+\t\t\t\t\t    timestamp_t timestamp, int tz,\n+\t\t\t\t\t    const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tint expire;\n+\n+\texpire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,\n+\t\t\t\t\t  message, cb);\n+\n+\tif (!expire)\n+\t\tprintf(\"keep %s\", message);\n+\telse if (cb->dry_run)\n+\t\tprintf(\"would prune %s\", message);\n+\telse\n+\t\tprintf(\"prune %s\", message);\n+\n+\treturn expire;\n+}\n+\n+static int push_tip_to_list(const char *refname, const struct object_id *oid,\n+\t\t\t    int flags, void *cb_data)\n+{\n+\tstruct commit_list **list = cb_data;\n+\tstruct commit *tip_commit;\n+\tif (flags & REF_ISSYMREF)\n+\t\treturn 0;\n+\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\tif (!tip_commit)\n+\t\treturn 0;\n+\tcommit_list_insert(tip_commit, list);\n+\treturn 0;\n+}\n+\n+static int is_head(const char *refname)\n+{\n+\tswitch (ref_type(refname)) {\n+\tcase REF_TYPE_OTHER_PSEUDOREF:\n+\tcase REF_TYPE_MAIN_PSEUDOREF:\n+\t\tif (parse_worktree_ref(refname, NULL, NULL, &refname))\n+\t\t\tBUG(\"not a worktree ref: %s\", refname);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\treturn !strcmp(refname, \"HEAD\");\n+}\n+\n+void reflog_expiry_prepare(const char *refname,\n+\t\t\t\t  const struct object_id *oid,\n+\t\t\t\t  void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\tstruct commit *commit = NULL;\n+\n+\tif (!cb->cmd.expire_unreachable || is_head(refname)) {\n+\t\tcb->unreachable_expire_kind = UE_HEAD;\n+\t} else {\n+\t\tcommit = lookup_commit(the_repository, oid);\n+\t\tcb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;\n+\t}\n+\n+\tif (cb->cmd.expire_unreachable <= cb->cmd.expire_total)\n+\t\tcb->unreachable_expire_kind = UE_ALWAYS;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor_each_ref(push_tip_to_list, &cb->tips);\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tcommit_list_insert(elem->item, &cb->mark_list);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tcommit_list_insert(commit, &cb->mark_list);\n+\t\t/* For reflog_expiry_cleanup() below */\n+\t\tcb->tip_commit = commit;\n+\t}\n+\tcb->mark_limit = cb->cmd.expire_total;\n+\tmark_reachable(cb);\n+}\n+\n+void reflog_expiry_cleanup(void *cb_data)\n+{\n+\tstruct expire_reflog_policy_cb *cb = cb_data;\n+\tstruct commit_list *elem;\n+\n+\tswitch (cb->unreachable_expire_kind) {\n+\tcase UE_ALWAYS:\n+\t\treturn;\n+\tcase UE_HEAD:\n+\t\tfor (elem = cb->tips; elem; elem = elem->next)\n+\t\t\tclear_commit_marks(elem->item, REACHABLE);\n+\t\tfree_commit_list(cb->tips);\n+\t\tbreak;\n+\tcase UE_NORMAL:\n+\t\tclear_commit_marks(cb->tip_commit, REACHABLE);\n+\t\tbreak;\n+\t}\n+}\n+\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\tconst char *email, timestamp_t timestamp, int tz,\n+\t\tconst char *message, void *cb_data)\n+{\n+\tstruct cmd_reflog_expire_cb *cb = cb_data;\n+\tif (!cb->expire_total || timestamp < cb->expire_total)\n+\t\tcb->recno++;\n+\treturn 0;\n+}\n+\n+int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose)\n+{\n+\tstruct cmd_reflog_expire_cb cmd = { 0 };\n+\tint status = 0;\n+\treflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;\n+\tconst char *spec = strstr(rev, \"@{\");\n+\tchar *ep, *ref;\n+\tint recno;\n+\tstruct expire_reflog_policy_cb cb = {\n+\t\t.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),\n+\t};\n+\n+\tif (verbose)\n+\t\tshould_prune_fn = should_expire_reflog_ent_verbose;\n+\n+\tif (!spec)\n+\t\treturn error(_(\"not a reflog: %s\"), rev);\n+\n+\tif (!dwim_log(rev, spec - rev, NULL, &ref)) {\n+\t\tstatus |= error(_(\"no reflog for '%s'\"), rev);\n+\t\tgoto cleanup;\n+\t}\n+\n+\trecno = strtoul(spec + 2, &ep, 10);\n+\tif (*ep == '}') {\n+\t\tcmd.recno = -recno;\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t} else {\n+\t\tcmd.expire_total = approxidate(spec + 2);\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cmd);\n+\t\tcmd.expire_total = 0;\n+\t}\n+\n+\tcb.cmd = cmd;\n+\tstatus |= reflog_expire(ref, flags,\n+\t\t\t\treflog_expiry_prepare,\n+\t\t\t\tshould_prune_fn,\n+\t\t\t\treflog_expiry_cleanup,\n+\t\t\t\t&cb);\n+\n+ cleanup:\n+\tfree(ref);\n+\treturn status;\n+}\ndiff --git a/reflog.h b/reflog.h\nnew file mode 100644\nindex 00000000000..d2906fb9f8d\n--- /dev/null\n+++ b/reflog.h\n@@ -0,0 +1,43 @@\n+#ifndef REFLOG_H\n+#define REFLOG_H\n+#include \"refs.h\"\n+\n+struct cmd_reflog_expire_cb {\n+\tint stalefix;\n+\tint explicit_expiry;\n+\ttimestamp_t expire_total;\n+\ttimestamp_t expire_unreachable;\n+\tint recno;\n+};\n+\n+struct expire_reflog_policy_cb {\n+\tenum {\n+\t\tUE_NORMAL,\n+\t\tUE_ALWAYS,\n+\t\tUE_HEAD\n+\t} unreachable_expire_kind;\n+\tstruct commit_list *mark_list;\n+\tunsigned long mark_limit;\n+\tstruct cmd_reflog_expire_cb cmd;\n+\tstruct commit *tip_commit;\n+\tstruct commit_list *tips;\n+\tunsigned int dry_run:1;\n+};\n+\n+int reflog_delete(const char *rev, enum expire_reflog_flags flags,\n+\t\t  int verbose);\n+void reflog_expiry_cleanup(void *cb_data);\n+void reflog_expiry_prepare(const char *refname, const struct object_id *oid,\n+\t\t\t   void *cb_data);\n+int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t\t     const char *email, timestamp_t timestamp, int tz,\n+\t\t\t     const char *message, void *cb_data);\n+int count_reflog_ent(struct object_id *ooid, struct object_id *noid,\n+\t\t     const char *email, timestamp_t timestamp, int tz,\n+\t\t     const char *message, void *cb_data);\n+int should_expire_reflog_ent_verbose(struct object_id *ooid,\n+\t\t\t\t     struct object_id *noid,\n+\t\t\t\t     const char *email,\n+\t\t\t\t     timestamp_t timestamp, int tz,\n+\t\t\t\t     const char *message, void *cb_data);\n+#endif /* REFLOG_H */\n-- \ngitgitgadget\n\n"},{"id":"450193","messageId":"xmqqfso0exoy.fsf@gitster.g","threadId":"57437","inReplyTo":"pull.1218.v4.git.git.1646260044.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/3] libify reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-02T23:34:05Z","receivedAt":"2022-03-02T23:49:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This patch has three parts:\n>\n>  * add missing test coverage for git stash delete\n\nThis looking better, relative to the previous iteration.\n\n>  * libify reflog's delete functionality and move some of the helpers into a\n>    reflog.c library and call reflog_delete from builtin/reflog.c\n\nThis part hasn't changed since the implementation in the previous\nround I've looked at; looked sane.\n\n>  * call reflog_delete from builtin/stash.c\n\nLikewise.\n\nThanks.  Will queue.\n"},{"id":"450195","messageId":"xmqqlexsexse.fsf@gitster.g","threadId":"57437","inReplyTo":"08bb8d3a9b9cd75c8b2aed11db9456baef6f415b.1646260044.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-02T23:32:01Z","receivedAt":"2022-03-02T23:51:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: John Cai <johncai86@gmail.com>\n>\n> There is missing test coverage to ensure that the resulting reflogs\n> after a git stash drop has had its old oid rewritten if applicable, and\n> if the refs/stash has been updated if applicable.\n>\n> Add two tests that verify both of these happen.\n>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: John Cai <johncai86@gmail.com>\n> ---\n>  t/t3903-stash.sh | 43 ++++++++++++++++++++++++++++++++++++++++++-\n>  1 file changed, 42 insertions(+), 1 deletion(-)\n>\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index b149e2af441..a2f8d0c52e7 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -41,7 +41,7 @@ diff_cmp () {\n>  \trm -f \"$1.compare\" \"$2.compare\"\n>  }\n>  \n> -test_expect_success 'stash some dirty working directory' '\n> +setup_stash() {\n\nStyle.\n\n\tsetup_stash () {\n\nbut more importantly, is this really setting up \"stash\"?\n\"setup_stash_test\" or something, perhaps?\n\n> +test_expect_success 'stash some dirty working directory' '\n> +\tsetup_stash\n>  '\n\nOK.\n\n> +test_expect_success 'drop stash reflog updates refs/stash' '\n> +\tgit reset --hard &&\n> +\tgit rev-parse refs/stash >expect &&\n> +\techo 9 >file &&\n> +\tgit stash &&\n> +\tgit stash drop stash@{0} &&\n> +\tgit rev-parse refs/stash >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n> +\tgit init repo &&\n> +\t(\n> +\t\tcd repo &&\n> +\t\tsetup_stash\n> +\t) &&\n\nHmph, so this is done inside the subdirectory.  The implementation\nof the helper in this iteration does look cleaner than in the\nprevious iteration.\n\nBut these many references to \"repo/\" and \"-C repo\" we see below\nmakes me wonder why we do not put the whole thing inside the\nsubshell we started earlier.\n\ni.e.\n\n\tgit init repo &&\n\t(\n\t\tcd repo &&\n\t\tsetup_stash_test &&\n\n\t\techo 9 >file &&\n\t\told=$(git rev-parse stash@{0}) &&\n\t\tgit stash &&\n\t\tnew=$(git rev-parse stash@{0}) &&\n\t\t...\n\n\t\ttest_cmp expect actual\n\t)\n\n> +\techo 9 >repo/file &&\n> +\n> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n> +\tgit -C repo stash &&\n> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n> +\n> +\tcat >expect <<-EOF &&\n> +\t$(test_oid zero) $old_oid\n> +\t$old_oid $new_oid\n> +\tEOF\n> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n> +\ttest_cmp expect actual &&\n> +\n> +\tgit -C repo stash drop stash@{1} &&\n> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t$(test_oid zero) $new_oid\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'stash pop' '\n>  \tgit reset --hard &&\n>  \tgit stash pop &&\n"},{"id":"450266","messageId":"3F09AD9D-5A14-4F04-A9C5-D16C33B88101@gmail.com","threadId":"57437","inReplyTo":"xmqqlexsexse.fsf@gitster.g","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-03-03T15:22:40Z","receivedAt":"2022-03-03T15:22:47Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Junio,\n\nOn 2 Mar 2022, at 18:32, Junio C Hamano wrote:\n\n> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: John Cai <johncai86@gmail.com>\n>>\n>> There is missing test coverage to ensure that the resulting reflogs\n>> after a git stash drop has had its old oid rewritten if applicable, and\n>> if the refs/stash has been updated if applicable.\n>>\n>> Add two tests that verify both of these happen.\n>>\n>> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>> Signed-off-by: John Cai <johncai86@gmail.com>\n>> ---\n>>  t/t3903-stash.sh | 43 ++++++++++++++++++++++++++++++++++++++++++-\n>>  1 file changed, 42 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>> index b149e2af441..a2f8d0c52e7 100755\n>> --- a/t/t3903-stash.sh\n>> +++ b/t/t3903-stash.sh\n>> @@ -41,7 +41,7 @@ diff_cmp () {\n>>  \trm -f \"$1.compare\" \"$2.compare\"\n>>  }\n>>\n>> -test_expect_success 'stash some dirty working directory' '\n>> +setup_stash() {\n>\n> Style.\n>\n> \tsetup_stash () {\n>\n> but more importantly, is this really setting up \"stash\"?\n> \"setup_stash_test\" or something, perhaps?\n>\n>> +test_expect_success 'stash some dirty working directory' '\n>> +\tsetup_stash\n>>  '\n>\n> OK.\n>\n>> +test_expect_success 'drop stash reflog updates refs/stash' '\n>> +\tgit reset --hard &&\n>> +\tgit rev-parse refs/stash >expect &&\n>> +\techo 9 >file &&\n>> +\tgit stash &&\n>> +\tgit stash drop stash@{0} &&\n>> +\tgit rev-parse refs/stash >actual &&\n>> +\ttest_cmp expect actual\n>> +'\n>> +\n>> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n>> +\tgit init repo &&\n>> +\t(\n>> +\t\tcd repo &&\n>> +\t\tsetup_stash\n>> +\t) &&\n>\n> Hmph, so this is done inside the subdirectory.  The implementation\n> of the helper in this iteration does look cleaner than in the\n> previous iteration.\n>\n> But these many references to \"repo/\" and \"-C repo\" we see below\n> makes me wonder why we do not put the whole thing inside the\n> subshell we started earlier.\n>\n> i.e.\n>\n> \tgit init repo &&\n> \t(\n> \t\tcd repo &&\n> \t\tsetup_stash_test &&\n>\n> \t\techo 9 >file &&\n> \t\told=$(git rev-parse stash@{0}) &&\n> \t\tgit stash &&\n> \t\tnew=$(git rev-parse stash@{0}) &&\n> \t\t...\n>\n> \t\ttest_cmp expect actual\n> \t)\n>\n\nmakes sense to me. Is this worth a re-roll and sending out another series to the list? or is it sufficient to make the change on my branch?\n\n>> +\techo 9 >repo/file &&\n>> +\n>> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>> +\tgit -C repo stash &&\n>> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>> +\n>> +\tcat >expect <<-EOF &&\n>> +\t$(test_oid zero) $old_oid\n>> +\t$old_oid $new_oid\n>> +\tEOF\n>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>> +\ttest_cmp expect actual &&\n>> +\n>> +\tgit -C repo stash drop stash@{1} &&\n>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>> +\tcat >expect <<-EOF &&\n>> +\t$(test_oid zero) $new_oid\n>> +\tEOF\n>> +\ttest_cmp expect actual\n>> +'\n>> +\n>>  test_expect_success 'stash pop' '\n>>  \tgit reset --hard &&\n>>  \tgit stash pop &&\n"},{"id":"450281","messageId":"20334a5e-52b8-12a4-de93-a8baa5313858@gmail.com","threadId":"57437","inReplyTo":"xmqqlexsexse.fsf@gitster.g","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-03-03T16:11:12Z","receivedAt":"2022-03-03T16:11:20Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/03/2022 23:32, Junio C Hamano wrote:\n> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> [...]\n>> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n>> +\tgit init repo &&\n>> +\t(\n>> +\t\tcd repo &&\n>> +\t\tsetup_stash\n>> +\t) &&\n> \n> Hmph, so this is done inside the subdirectory.  The implementation\n> of the helper in this iteration does look cleaner than in the\n> previous iteration.\n> \n> But these many references to \"repo/\" and \"-C repo\" we see below\n> makes me wonder why we do not put the whole thing inside the\n> subshell we started earlier.\n> \n> i.e.\n> \n> \tgit init repo &&\n> \t(\n> \t\tcd repo &&\n> \t\tsetup_stash_test &&\n> \n> \t\techo 9 >file &&\n> \t\told=$(git rev-parse stash@{0}) &&\n> \t\tgit stash &&\n> \t\tnew=$(git rev-parse stash@{0}) &&\n> \t\t...\n> \n> \t\ttest_cmp expect actual\n> \t)\n>\n\nI wonder if we could avoid the subshell entirely and avoid relying on \nREFFILES (assuming we're not trying to test the implementation details \nof that refs backend) by doing something like\n\ntest_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n\told=$(git rev-parse stash@{0}) &&\n\tsetup_stash_test &&\n\tgit rev-list -g stash >tmp &&\n\tsed /$old/d tmp >expect &&\n\tgit rev-list -g stash >actual &&\n\ttest_cmp expect actual\n'\n\nBest Wishes\n\nPhillip\n\n>> +\techo 9 >repo/file &&\n>> +\n>> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>> +\tgit -C repo stash &&\n>> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>> +\n>> +\tcat >expect <<-EOF &&\n>> +\t$(test_oid zero) $old_oid\n>> +\t$old_oid $new_oid\n>> +\tEOF\n>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>> +\ttest_cmp expect actual &&\n>> +\n>> +\tgit -C repo stash drop stash@{1} &&\n>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>> +\tcat >expect <<-EOF &&\n>> +\t$(test_oid zero) $new_oid\n>> +\tEOF\n>> +\ttest_cmp expect actual\n>> +'\n>> +\n>>   test_expect_success 'stash pop' '\n>>   \tgit reset --hard &&\n>>   \tgit stash pop &&\n"},{"id":"450291","messageId":"220303.86a6e72d1g.gmgdl@evledraar.gmail.com","threadId":"57437","inReplyTo":"20334a5e-52b8-12a4-de93-a8baa5313858@gmail.com","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-03T16:52:23Z","receivedAt":"2022-03-03T16:53:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Mar 03 2022, Phillip Wood wrote:\n\n> On 02/03/2022 23:32, Junio C Hamano wrote:\n>> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> [...]\n>>> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n>>> +\tgit init repo &&\n>>> +\t(\n>>> +\t\tcd repo &&\n>>> +\t\tsetup_stash\n>>> +\t) &&\n>> Hmph, so this is done inside the subdirectory.  The implementation\n>> of the helper in this iteration does look cleaner than in the\n>> previous iteration.\n>> But these many references to \"repo/\" and \"-C repo\" we see below\n>> makes me wonder why we do not put the whole thing inside the\n>> subshell we started earlier.\n>> i.e.\n>> \tgit init repo &&\n>> \t(\n>> \t\tcd repo &&\n>> \t\tsetup_stash_test &&\n>> \t\techo 9 >file &&\n>> \t\told=$(git rev-parse stash@{0}) &&\n>> \t\tgit stash &&\n>> \t\tnew=$(git rev-parse stash@{0}) &&\n>> \t\t...\n>> \t\ttest_cmp expect actual\n>> \t)\n>>\n>\n> I wonder if we could avoid the subshell entirely and avoid relying on\n> REFFILES (assuming we're not trying to test the implementation details \n> of that refs backend) by doing something like\n>\n> test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n> \told=$(git rev-parse stash@{0}) &&\n> \tsetup_stash_test &&\n> \tgit rev-list -g stash >tmp &&\n> \tsed /$old/d tmp >expect &&\n> \tgit rev-list -g stash >actual &&\n> \ttest_cmp expect actual\n> '\n\nUnless I'm missing something that \"rev-list -g\" will emit only the RHS\nof the stash logs, i.e. no \"0000...\" etc.\n\nAnd if we only look at that the difference with specifying the flag\nisn't visible, no?\n\n>>> +\techo 9 >repo/file &&\n>>> +\n>>> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>> +\tgit -C repo stash &&\n>>> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>> +\n>>> +\tcat >expect <<-EOF &&\n>>> +\t$(test_oid zero) $old_oid\n>>> +\t$old_oid $new_oid\n>>> +\tEOF\n>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>> +\ttest_cmp expect actual &&\n>>> +\n>>> +\tgit -C repo stash drop stash@{1} &&\n>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>> +\tcat >expect <<-EOF &&\n>>> +\t$(test_oid zero) $new_oid\n>>> +\tEOF\n>>> +\ttest_cmp expect actual\n>>> +'\n>>> +\n>>>   test_expect_success 'stash pop' '\n>>>   \tgit reset --hard &&\n>>>   \tgit stash pop &&\n\n"},{"id":"450293","messageId":"d73db784-c09b-a889-3d19-d41be7748e66@gmail.com","threadId":"57437","inReplyTo":"220303.86a6e72d1g.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-03-03T17:28:26Z","receivedAt":"2022-03-03T17:28:32Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 03/03/2022 16:52, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Thu, Mar 03 2022, Phillip Wood wrote:\n> \n>> On 02/03/2022 23:32, Junio C Hamano wrote:\n>>> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>> [...]\n>>>> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n>>>> +\tgit init repo &&\n>>>> +\t(\n>>>> +\t\tcd repo &&\n>>>> +\t\tsetup_stash\n>>>> +\t) &&\n>>> Hmph, so this is done inside the subdirectory.  The implementation\n>>> of the helper in this iteration does look cleaner than in the\n>>> previous iteration.\n>>> But these many references to \"repo/\" and \"-C repo\" we see below\n>>> makes me wonder why we do not put the whole thing inside the\n>>> subshell we started earlier.\n>>> i.e.\n>>> \tgit init repo &&\n>>> \t(\n>>> \t\tcd repo &&\n>>> \t\tsetup_stash_test &&\n>>> \t\techo 9 >file &&\n>>> \t\told=$(git rev-parse stash@{0}) &&\n>>> \t\tgit stash &&\n>>> \t\tnew=$(git rev-parse stash@{0}) &&\n>>> \t\t...\n>>> \t\ttest_cmp expect actual\n>>> \t)\n>>>\n>>\n>> I wonder if we could avoid the subshell entirely and avoid relying on\n>> REFFILES (assuming we're not trying to test the implementation details\n>> of that refs backend) by doing something like\n>>\n>> test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>> \told=$(git rev-parse stash@{0}) &&\n>> \tsetup_stash_test &&\n>> \tgit rev-list -g stash >tmp &&\n>> \tsed /$old/d tmp >expect &&\n>> \tgit rev-list -g stash >actual &&\n>> \ttest_cmp expect actual\n>> '\n> \n> Unless I'm missing something that \"rev-list -g\" will emit only the RHS\n> of the stash logs, i.e. no \"0000...\" etc.\n> \n> And if we only look at that the difference with specifying the flag\n> isn't visible, no?\n\nMaybe I'm missing what this test is actually needs to do. I thought it \njust needed to check that the deleted stash is removed from the reflog \nand the others are unchanged. You're right that it wont show the LHS and \nif that is important then you need to read the log file directly.\n\nBest Wishes\n\nPhillip\n\n\n>>>> +\techo 9 >repo/file &&\n>>>> +\n>>>> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>> +\tgit -C repo stash &&\n>>>> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>> +\n>>>> +\tcat >expect <<-EOF &&\n>>>> +\t$(test_oid zero) $old_oid\n>>>> +\t$old_oid $new_oid\n>>>> +\tEOF\n>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>> +\ttest_cmp expect actual &&\n>>>> +\n>>>> +\tgit -C repo stash drop stash@{1} &&\n>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>> +\tcat >expect <<-EOF &&\n>>>> +\t$(test_oid zero) $new_oid\n>>>> +\tEOF\n>>>> +\ttest_cmp expect actual\n>>>> +'\n>>>> +\n>>>>    test_expect_success 'stash pop' '\n>>>>    \tgit reset --hard &&\n>>>>    \tgit stash pop &&\n> \n\n"},{"id":"450298","messageId":"F383C357-FEC3-4E1E-A0B7-97EDE05035FD@gmail.com","threadId":"57437","inReplyTo":"d73db784-c09b-a889-3d19-d41be7748e66@gmail.com","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-03-03T19:12:57Z","receivedAt":"2022-03-03T19:13:05Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Phillip,\n\nOn 3 Mar 2022, at 12:28, Phillip Wood wrote:\n\n> On 03/03/2022 16:52, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Thu, Mar 03 2022, Phillip Wood wrote:\n>>\n>>> On 02/03/2022 23:32, Junio C Hamano wrote:\n>>>> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>> [...]\n>>>>> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n>>>>> +\tgit init repo &&\n>>>>> +\t(\n>>>>> +\t\tcd repo &&\n>>>>> +\t\tsetup_stash\n>>>>> +\t) &&\n>>>> Hmph, so this is done inside the subdirectory.  The implementation\n>>>> of the helper in this iteration does look cleaner than in the\n>>>> previous iteration.\n>>>> But these many references to \"repo/\" and \"-C repo\" we see below\n>>>> makes me wonder why we do not put the whole thing inside the\n>>>> subshell we started earlier.\n>>>> i.e.\n>>>> \tgit init repo &&\n>>>> \t(\n>>>> \t\tcd repo &&\n>>>> \t\tsetup_stash_test &&\n>>>> \t\techo 9 >file &&\n>>>> \t\told=$(git rev-parse stash@{0}) &&\n>>>> \t\tgit stash &&\n>>>> \t\tnew=$(git rev-parse stash@{0}) &&\n>>>> \t\t...\n>>>> \t\ttest_cmp expect actual\n>>>> \t)\n>>>>\n>>>\n>>> I wonder if we could avoid the subshell entirely and avoid relying on\n>>> REFFILES (assuming we're not trying to test the implementation details\n>>> of that refs backend) by doing something like\n>>>\n>>> test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>> \told=$(git rev-parse stash@{0}) &&\n>>> \tsetup_stash_test &&\n>>> \tgit rev-list -g stash >tmp &&\n>>> \tsed /$old/d tmp >expect &&\n>>> \tgit rev-list -g stash >actual &&\n>>> \ttest_cmp expect actual\n>>> '\n>>\n>> Unless I'm missing something that \"rev-list -g\" will emit only the RHS\n>> of the stash logs, i.e. no \"0000...\" etc.\n>>\n>> And if we only look at that the difference with specifying the flag\n>> isn't visible, no?\n>\n> Maybe I'm missing what this test is actually needs to do. I thought it just needed to check that the deleted stash is removed from the reflog and the others are unchanged. You're right that it wont show the LHS and if that is important then you need to read the log file directly.\n\nWe had discussed this briefly in [1], but the --rewrite option for reflog delete will rewrite the LHS, which is not visible to normal ref API users. So the only way to test that this happened is to reach inside of the file.\n\n1. https://lore.kernel.org/git/xmqqczjdp2g8.fsf@gitster.g/\n>\n> Best Wishes\n>\n> Phillip\n\nthanks,\nJohn\n\n>\n>\n>>>>> +\techo 9 >repo/file &&\n>>>>> +\n>>>>> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>>> +\tgit -C repo stash &&\n>>>>> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>>> +\n>>>>> +\tcat >expect <<-EOF &&\n>>>>> +\t$(test_oid zero) $old_oid\n>>>>> +\t$old_oid $new_oid\n>>>>> +\tEOF\n>>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>>> +\ttest_cmp expect actual &&\n>>>>> +\n>>>>> +\tgit -C repo stash drop stash@{1} &&\n>>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>>> +\tcat >expect <<-EOF &&\n>>>>> +\t$(test_oid zero) $new_oid\n>>>>> +\tEOF\n>>>>> +\ttest_cmp expect actual\n>>>>> +'\n>>>>> +\n>>>>>    test_expect_success 'stash pop' '\n>>>>>    \tgit reset --hard &&\n>>>>>    \tgit stash pop &&\n>>\n"},{"id":"450719","messageId":"34452e16-cf8e-8d23-671e-3c24cdf4e6a4@gmail.com","threadId":"57437","inReplyTo":"F383C357-FEC3-4E1E-A0B7-97EDE05035FD@gmail.com","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-03-08T10:39:36Z","receivedAt":"2022-03-08T10:39:42Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi John\n\nOn 03/03/2022 19:12, John Cai wrote:\n> Hi Phillip,\n> \n> On 3 Mar 2022, at 12:28, Phillip Wood wrote:\n> \n>> On 03/03/2022 16:52, Ævar Arnfjörð Bjarmason wrote:\n>>>\n>>> On Thu, Mar 03 2022, Phillip Wood wrote:\n>>>\n>>>> On 02/03/2022 23:32, Junio C Hamano wrote:\n>>>>> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>>> [...]\n>>>>>> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n>>>>>> +\tgit init repo &&\n>>>>>> +\t(\n>>>>>> +\t\tcd repo &&\n>>>>>> +\t\tsetup_stash\n>>>>>> +\t) &&\n>>>>> Hmph, so this is done inside the subdirectory.  The implementation\n>>>>> of the helper in this iteration does look cleaner than in the\n>>>>> previous iteration.\n>>>>> But these many references to \"repo/\" and \"-C repo\" we see below\n>>>>> makes me wonder why we do not put the whole thing inside the\n>>>>> subshell we started earlier.\n>>>>> i.e.\n>>>>> \tgit init repo &&\n>>>>> \t(\n>>>>> \t\tcd repo &&\n>>>>> \t\tsetup_stash_test &&\n>>>>> \t\techo 9 >file &&\n>>>>> \t\told=$(git rev-parse stash@{0}) &&\n>>>>> \t\tgit stash &&\n>>>>> \t\tnew=$(git rev-parse stash@{0}) &&\n>>>>> \t\t...\n>>>>> \t\ttest_cmp expect actual\n>>>>> \t)\n>>>>>\n>>>>\n>>>> I wonder if we could avoid the subshell entirely and avoid relying on\n>>>> REFFILES (assuming we're not trying to test the implementation details\n>>>> of that refs backend) by doing something like\n>>>>\n>>>> test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>>> \told=$(git rev-parse stash@{0}) &&\n>>>> \tsetup_stash_test &&\n>>>> \tgit rev-list -g stash >tmp &&\n>>>> \tsed /$old/d tmp >expect &&\n>>>> \tgit rev-list -g stash >actual &&\n>>>> \ttest_cmp expect actual\n>>>> '\n>>>\n>>> Unless I'm missing something that \"rev-list -g\" will emit only the RHS\n>>> of the stash logs, i.e. no \"0000...\" etc.\n>>>\n>>> And if we only look at that the difference with specifying the flag\n>>> isn't visible, no?\n>>\n>> Maybe I'm missing what this test is actually needs to do. I thought it just needed to check that the deleted stash is removed from the reflog and the others are unchanged. You're right that it wont show the LHS and if that is important then you need to read the log file directly.\n> \n> We had discussed this briefly in [1], but the --rewrite option for reflog delete will rewrite the LHS, which is not visible to normal ref API users. So the only way to test that this happened is to reach inside of the file.\n> \n> 1. https://lore.kernel.org/git/xmqqczjdp2g8.fsf@gitster.g/\n\nThanks for the pointer, that was useful context that could maybe be \nadded to the commit message to explain why the test needs to check the \nlhs of the reflog if you reroll.\n\nBest Wishes\n\nPhillip\n\n>>\n>> Best Wishes\n>>\n>> Phillip\n> \n> thanks,\n> John\n> \n>>\n>>\n>>>>>> +\techo 9 >repo/file &&\n>>>>>> +\n>>>>>> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>>>> +\tgit -C repo stash &&\n>>>>>> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>>>> +\n>>>>>> +\tcat >expect <<-EOF &&\n>>>>>> +\t$(test_oid zero) $old_oid\n>>>>>> +\t$old_oid $new_oid\n>>>>>> +\tEOF\n>>>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>>>> +\ttest_cmp expect actual &&\n>>>>>> +\n>>>>>> +\tgit -C repo stash drop stash@{1} &&\n>>>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>>>> +\tcat >expect <<-EOF &&\n>>>>>> +\t$(test_oid zero) $new_oid\n>>>>>> +\tEOF\n>>>>>> +\ttest_cmp expect actual\n>>>>>> +'\n>>>>>> +\n>>>>>>     test_expect_success 'stash pop' '\n>>>>>>     \tgit reset --hard &&\n>>>>>>     \tgit stash pop &&\n>>>\n\n"},{"id":"450764","messageId":"A8BDCB7C-5F16-4EDE-B8B7-0B73C93B68D2@gmail.com","threadId":"57437","inReplyTo":"34452e16-cf8e-8d23-671e-3c24cdf4e6a4@gmail.com","subject":"Re: [PATCH v4 1/3] stash: add tests to ensure reflog --rewrite --updatref behavior","fromName":"John Cai","fromEmail":"johncai86@gmail.com","sentAt":"2022-03-08T18:09:52Z","receivedAt":"2022-03-08T18:10:03Z","isPatch":true,"sender":{"key":"johncai86@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2354211?v=4"},"body":"Hi Phillip,\n\nOn 8 Mar 2022, at 5:39, Phillip Wood wrote:\n\n> Hi John\n>\n> On 03/03/2022 19:12, John Cai wrote:\n>> Hi Phillip,\n>>\n>> On 3 Mar 2022, at 12:28, Phillip Wood wrote:\n>>\n>>> On 03/03/2022 16:52, Ævar Arnfjörð Bjarmason wrote:\n>>>>\n>>>> On Thu, Mar 03 2022, Phillip Wood wrote:\n>>>>\n>>>>> On 02/03/2022 23:32, Junio C Hamano wrote:\n>>>>>> \"John Cai via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>>>> [...]\n>>>>>>> +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '\n>>>>>>> +\tgit init repo &&\n>>>>>>> +\t(\n>>>>>>> +\t\tcd repo &&\n>>>>>>> +\t\tsetup_stash\n>>>>>>> +\t) &&\n>>>>>> Hmph, so this is done inside the subdirectory.  The implementation\n>>>>>> of the helper in this iteration does look cleaner than in the\n>>>>>> previous iteration.\n>>>>>> But these many references to \"repo/\" and \"-C repo\" we see below\n>>>>>> makes me wonder why we do not put the whole thing inside the\n>>>>>> subshell we started earlier.\n>>>>>> i.e.\n>>>>>> \tgit init repo &&\n>>>>>> \t(\n>>>>>> \t\tcd repo &&\n>>>>>> \t\tsetup_stash_test &&\n>>>>>> \t\techo 9 >file &&\n>>>>>> \t\told=$(git rev-parse stash@{0}) &&\n>>>>>> \t\tgit stash &&\n>>>>>> \t\tnew=$(git rev-parse stash@{0}) &&\n>>>>>> \t\t...\n>>>>>> \t\ttest_cmp expect actual\n>>>>>> \t)\n>>>>>>\n>>>>>\n>>>>> I wonder if we could avoid the subshell entirely and avoid relying on\n>>>>> REFFILES (assuming we're not trying to test the implementation details\n>>>>> of that refs backend) by doing something like\n>>>>>\n>>>>> test_expect_success 'drop stash reflog updates refs/stash with rewrite' '\n>>>>> \told=$(git rev-parse stash@{0}) &&\n>>>>> \tsetup_stash_test &&\n>>>>> \tgit rev-list -g stash >tmp &&\n>>>>> \tsed /$old/d tmp >expect &&\n>>>>> \tgit rev-list -g stash >actual &&\n>>>>> \ttest_cmp expect actual\n>>>>> '\n>>>>\n>>>> Unless I'm missing something that \"rev-list -g\" will emit only the RHS\n>>>> of the stash logs, i.e. no \"0000...\" etc.\n>>>>\n>>>> And if we only look at that the difference with specifying the flag\n>>>> isn't visible, no?\n>>>\n>>> Maybe I'm missing what this test is actually needs to do. I thought it just needed to check that the deleted stash is removed from the reflog and the others are unchanged. You're right that it wont show the LHS and if that is important then you need to read the log file directly.\n>>\n>> We had discussed this briefly in [1], but the --rewrite option for reflog delete will rewrite the LHS, which is not visible to normal ref API users. So the only way to test that this happened is to reach inside of the file.\n>>\n>> 1. https://lore.kernel.org/git/xmqqczjdp2g8.fsf@gitster.g/\n>\n> Thanks for the pointer, that was useful context that could maybe be added to the commit message to explain why the test needs to check the lhs of the reflog if you reroll.\n\nGood point. that would be helpful context since it took me a while to figure it out myself--will re-roll, thanks!\n\n>\n> Best Wishes\n>\n> Phillip\n>\n>>>\n>>> Best Wishes\n>>>\n>>> Phillip\n>>\n>> thanks,\n>> John\n>>\n>>>\n>>>\n>>>>>>> +\techo 9 >repo/file &&\n>>>>>>> +\n>>>>>>> +\told_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>>>>> +\tgit -C repo stash &&\n>>>>>>> +\tnew_oid=\"$(git -C repo rev-parse stash@{0})\" &&\n>>>>>>> +\n>>>>>>> +\tcat >expect <<-EOF &&\n>>>>>>> +\t$(test_oid zero) $old_oid\n>>>>>>> +\t$old_oid $new_oid\n>>>>>>> +\tEOF\n>>>>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>>>>> +\ttest_cmp expect actual &&\n>>>>>>> +\n>>>>>>> +\tgit -C repo stash drop stash@{1} &&\n>>>>>>> +\tcut -d\" \" -f1-2 repo/.git/logs/refs/stash >actual &&\n>>>>>>> +\tcat >expect <<-EOF &&\n>>>>>>> +\t$(test_oid zero) $new_oid\n>>>>>>> +\tEOF\n>>>>>>> +\ttest_cmp expect actual\n>>>>>>> +'\n>>>>>>> +\n>>>>>>>     test_expect_success 'stash pop' '\n>>>>>>>     \tgit reset --hard &&\n>>>>>>>     \tgit stash pop &&\n>>>>\n"}]}