{"thread":{"id":"57531","subject":"[PATCH 0/6] Importing and exporting stashes to refs","startedAt":"2022-03-10T17:35:25Z","lastAt":"2022-04-13T21:44:00Z","messageCount":76,"participants":["brian m. carlson","Junio C Hamano","Ævar Arnfjörð Bjarmason","Phillip Wood","Drew Stolee","Johannes Schindelin","Jonathan Tan"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"451042","messageId":"20220310173236.4165310-1-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":null,"subject":"[PATCH 0/6] Importing and exporting stashes to refs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T17:32:30Z","receivedAt":"2022-03-10T17:35:25Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Stashes are currently stored using the reflog in a given repository.\nThis is an interesting and novel way to handle them, but there is no way\nto easily move a stash across machines.  For example, stashes cannot be\nbundled, pushed, or fetched.\n\nThis is suboptimal for a lot of reasons.  First, there is a recent push\ntowards ephemeral development environments, but many users make heavy\nuse of their stashes and wish to persist them long term[0].  Additionally,\nit would be convenient to share a snapshot of in-progress work with a\ncolleague or a collaborator on a project.  And finally, many users wish\nto sync their in-progress state across machines, and we currently have\nno good way to do so, so they often do dangerous things like using cloud\nsyncing services for their repositories.\n\nLet's solve this problem by allowing users to import and export stashes\nto a chain of commits.  The commits used in a stash export are nearly\nidentical to those used in the stashes, with one notable change: the\nfirst parent of a stash is a pointer to the previous stash, or an empty\ncommit if there is no previous stash.  All of the other parents used in\nthe stash commit are present following it in their normal order.\n\nThis allows users to write their exported stashes to a single ref and\nthen push that ref to a remote or to bundle it for easy transport, and\nthen fetch it on the receiving side.  It also permits saving the index\nand even untracked files and syncing them across machines, unlike\ntemporary commits.\n\nWe intentionally attempt to exactly round-trip commits between stashes,\nalthough we don't do so for the exported data due to the base commit not\nhaving identical timestamps.  Preserving the commits exactly lets us\nmore efficiently test our code and it also permits users to more easily\ndetermine if they have the same data.\n\nThe tooling here is intentionally plumbing.  It's designed to be simple\nand functional and get the basic job done.  If we want additional\nfeatures, we can add them in the future, but this should be a simple,\nbasic feature set that can support additional uses.\n\n[0] For example, the present author has 124 stash entries in his\nrepository for this project.\n\nbrian m. carlson (6):\n  builtin/stash: factor out generic function to look up stash info\n  builtin/stash: fill in all commit data\n  object-name: make get_oid quietly return an error\n  builtin/stash: provide a way to export stashes to a ref\n  builtin/stash: provide a way to import stashes from a ref\n  doc: add stash export and import to docs\n\n Documentation/git-stash.txt |  27 +++\n builtin/stash.c             | 359 +++++++++++++++++++++++++++++++++---\n cache.h                     |  21 ++-\n object-name.c               |   6 +-\n t/t3903-stash.sh            |  52 ++++++\n 5 files changed, 431 insertions(+), 34 deletions(-)\n\n"},{"id":"451043","messageId":"20220310173236.4165310-3-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH 2/6] builtin/stash: fill in all commit data","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T17:32:32Z","receivedAt":"2022-03-10T17:35:27Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"get_stash_info doesn't ensure that all entries are filled in in all\ncases.  However, we'll want to use this information to write new\ncommits, and when we do so, we'll need all the information to be\npresent.  Fill in all the commit information whenever we call this\nfunction.\n\nNote that the behavior of info->has_u doesn't change here.  If we\npreviously read a tree a refs/stash^3:, then refs/stash^3 must be a\ntreeish.  We already here assume that the other parents specifically\ncommits, so it should be safe to do so here as well.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n builtin/stash.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 2aa06cc91d..128f0a01ef 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -124,6 +124,7 @@ static void free_stash_info(struct stash_info *info)\n static void assert_stash_like(struct stash_info *info, const char *revision)\n {\n \tif (get_oidf(&info->b_commit, \"%s^1\", revision) ||\n+\t    get_oidf(&info->i_commit, \"%s^2\", revision) ||\n \t    get_oidf(&info->w_tree, \"%s:\", revision) ||\n \t    get_oidf(&info->b_tree, \"%s^1:\", revision) ||\n \t    get_oidf(&info->i_tree, \"%s^2:\", revision))\n@@ -166,7 +167,8 @@ static int get_stash_info_1(struct stash_info *info, const char *commit, int qui\n \n \tassert_stash_like(info, revision);\n \n-\tinfo->has_u = !get_oidf(&info->u_tree, \"%s^3:\", revision);\n+\tinfo->has_u = !get_oidf(&info->u_commit, \"%s^3\", revision) &&\n+\t\t      !get_oidf(&info->u_tree, \"%s^3:\", revision);\n \n \tend_of_rev = strchrnul(revision, '@');\n \tstrbuf_add(&symbolic, revision, end_of_rev - revision);\n"},{"id":"451044","messageId":"20220310173236.4165310-2-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH 1/6] builtin/stash: factor out generic function to look up stash info","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T17:32:31Z","receivedAt":"2022-03-10T17:35:28Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"We have a function called get_stash_info that looks up this data based\non a set of command-line arguments and produces diagnostics to the user\non failure.  While this is helpful in the existing use cases, we'd like\nto make use of this logic in a more programmatic way in the future.\n\nSplit out much of the function into a function which can be used\ninternally and which knows how to suppress these error messages with a\nquiet parameter.  Wire up the rest of the function to call this internal\nfunction to preserve the existing behavior.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n builtin/stash.c | 50 ++++++++++++++++++++++++++++---------------------\n 1 file changed, 29 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 5897febfbe..2aa06cc91d 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -130,38 +130,21 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n \t\tdie(_(\"'%s' is not a stash-like commit\"), revision);\n }\n \n-static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n+static int get_stash_info_1(struct stash_info *info, const char *commit, int quiet)\n {\n \tint ret;\n \tchar *end_of_rev;\n \tchar *expanded_ref;\n \tconst char *revision;\n-\tconst char *commit = NULL;\n \tstruct object_id dummy;\n \tstruct strbuf symbolic = STRBUF_INIT;\n \n-\tif (argc > 1) {\n-\t\tint i;\n-\t\tstruct strbuf refs_msg = STRBUF_INIT;\n-\n-\t\tfor (i = 0; i < argc; i++)\n-\t\t\tstrbuf_addf(&refs_msg, \" '%s'\", argv[i]);\n-\n-\t\tfprintf_ln(stderr, _(\"Too many revisions specified:%s\"),\n-\t\t\t   refs_msg.buf);\n-\t\tstrbuf_release(&refs_msg);\n-\n-\t\treturn -1;\n-\t}\n-\n-\tif (argc == 1)\n-\t\tcommit = argv[0];\n-\n \tstrbuf_init(&info->revision, 0);\n \tif (!commit) {\n \t\tif (!ref_exists(ref_stash)) {\n \t\t\tfree_stash_info(info);\n-\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n+\t\t\tif (!quiet)\n+\t\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n \t\t\treturn -1;\n \t\t}\n \n@@ -175,7 +158,8 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n \trevision = info->revision.buf;\n \n \tif (get_oid(revision, &info->w_commit)) {\n-\t\terror(_(\"%s is not a valid reference\"), revision);\n+\t\tif (!quiet)\n+\t\t\terror(_(\"%s is not a valid reference\"), revision);\n \t\tfree_stash_info(info);\n \t\treturn -1;\n \t}\n@@ -204,6 +188,30 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n \treturn !(ret == 0 || ret == 1);\n }\n \n+static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n+{\n+\tconst char *commit = NULL;\n+\n+\tif (argc > 1) {\n+\t\tint i;\n+\t\tstruct strbuf refs_msg = STRBUF_INIT;\n+\n+\t\tfor (i = 0; i < argc; i++)\n+\t\t\tstrbuf_addf(&refs_msg, \" '%s'\", argv[i]);\n+\n+\t\tfprintf_ln(stderr, _(\"Too many revisions specified:%s\"),\n+\t\t\t   refs_msg.buf);\n+\t\tstrbuf_release(&refs_msg);\n+\n+\t\treturn -1;\n+\t}\n+\n+\tif (argc == 1)\n+\t\tcommit = argv[0];\n+\n+\treturn get_stash_info_1(info, commit, 0);\n+}\n+\n static int do_clear_stash(void)\n {\n \tstruct object_id obj;\n"},{"id":"451045","messageId":"20220310173236.4165310-4-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH 3/6] object-name: make get_oid quietly return an error","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T17:32:33Z","receivedAt":"2022-03-10T17:35:30Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A reasonable person looking at the signature and usage of get_oid and\nfriends might conclude that in the event of an error, it always returns\n-1.  However, this is not the case.  Instead, get_oid_basic dies if we\ngo too far back into the history of a reflog (or, when quiet, simply\nexits).\n\nThis is not especially useful, since in many cases, we might want to\nhandle this error differently.  Let's add a flag here to make it just\nreturn -1 like elsewhere in these code paths.\n\nNote that we cannot make this behavior the default, since we have many\nother codepaths that rely on the existing behavior, including in tests.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n cache.h       | 21 +++++++++++----------\n object-name.c |  6 +++++-\n 2 files changed, 16 insertions(+), 11 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 825ec17198..416a9d9983 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1366,16 +1366,17 @@ struct object_context {\n \tchar *path;\n };\n \n-#define GET_OID_QUIETLY           01\n-#define GET_OID_COMMIT            02\n-#define GET_OID_COMMITTISH        04\n-#define GET_OID_TREE             010\n-#define GET_OID_TREEISH          020\n-#define GET_OID_BLOB             040\n-#define GET_OID_FOLLOW_SYMLINKS 0100\n-#define GET_OID_RECORD_PATH     0200\n-#define GET_OID_ONLY_TO_DIE    04000\n-#define GET_OID_REQUIRE_PATH  010000\n+#define GET_OID_QUIETLY            01\n+#define GET_OID_COMMIT             02\n+#define GET_OID_COMMITTISH         04\n+#define GET_OID_TREE              010\n+#define GET_OID_TREEISH           020\n+#define GET_OID_BLOB              040\n+#define GET_OID_FOLLOW_SYMLINKS  0100\n+#define GET_OID_RECORD_PATH      0200\n+#define GET_OID_ONLY_TO_DIE     04000\n+#define GET_OID_REQUIRE_PATH   010000\n+#define GET_OID_RETURN_FAILURE 020000\n \n #define GET_OID_DISAMBIGUATORS \\\n \t(GET_OID_COMMIT | GET_OID_COMMITTISH | \\\ndiff --git a/object-name.c b/object-name.c\nindex 92862eeb1a..daa3ef77ef 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -911,13 +911,17 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\t\tlen, str,\n \t\t\t\t\t\tshow_date(co_time, co_tz, DATE_MODE(RFC2822)));\n \t\t\t\t}\n-\t\t\t} else {\n+\t\t\t} else if (!(flags & GET_OID_RETURN_FAILURE)) {\n \t\t\t\tif (flags & GET_OID_QUIETLY) {\n \t\t\t\t\texit(128);\n \t\t\t\t}\n \t\t\t\tdie(_(\"log for '%.*s' only has %d entries\"),\n \t\t\t\t    len, str, co_cnt);\n \t\t\t}\n+\t\t\tif (flags & GET_OID_RETURN_FAILURE) {\n+\t\t\t\tfree(real_ref);\n+\t\t\t\treturn -1;\n+\t\t\t}\n \t\t}\n \t}\n \n"},{"id":"451046","messageId":"20220310173236.4165310-5-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH 4/6] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T17:32:34Z","receivedAt":"2022-03-10T17:35:31Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A common user problem is how to sync in-progress work to another\nmachine.  Users currently must use some sort of transfer of the working\ntree, which poses security risks and also necessarily causes the index\nto become dirty.  The experience is suboptimal and frustrating for\nusers.\n\nA reasonable idea is to use the stash for this purpose, but the stash is\nstored in the reflog, not in a ref, and as such it cannot be pushed or\npulled.  This also means that it cannot be saved into a bundle or\npreserved elsewhere, which is a problem when using throwaway development\nenvironments.\n\nLet's solve this problem by allowing the user to export the stash to a\nref (or, to just write it into the repository and print the hash, à la\ngit commit-tree).  Introduce git stash export, which writes a chain of\ncommits identical to the ones used for reflog-based stashes, except that\nthe first parent is always a chain to the previous stash, or to a\nsingle, empty commit (for the final item).\n\nIterate over each stash from topmost to bottomost, looking up the data\nfor each one, and then create the chain from the single empty commit\nback up in reverse order.  Preserve the author and committer\ninformation, as well as the commit message, so we produce an identical\nstash when importing later.\n\nIf the user has specified specific stashes they'd like to export\ninstead, use those instead of iterating over all of the stashes.\n\nAs part of this, specifically request quiet behavior when looking up the\nOID for a revision because we will eventually hit a revision that\ndoesn't exist and we don't want to die when that occurs.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n builtin/stash.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 179 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 128f0a01ef..582a04dbab 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -33,6 +33,7 @@ static const char * const git_stash_usage[] = {\n \t   \"          [--] [<pathspec>...]]\"),\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stashes>]\"),\n \tNULL\n };\n \n@@ -89,6 +90,12 @@ static const char * const git_stash_save_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_export_usage[] = {\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stashes>]\"),\n+\tNULL\n+};\n+\n+\n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n \n@@ -139,6 +146,7 @@ static int get_stash_info_1(struct stash_info *info, const char *commit, int qui\n \tconst char *revision;\n \tstruct object_id dummy;\n \tstruct strbuf symbolic = STRBUF_INIT;\n+\tstruct object_context unused;\n \n \tstrbuf_init(&info->revision, 0);\n \tif (!commit) {\n@@ -158,7 +166,9 @@ static int get_stash_info_1(struct stash_info *info, const char *commit, int qui\n \n \trevision = info->revision.buf;\n \n-\tif (get_oid(revision, &info->w_commit)) {\n+\tif (get_oid_with_context(the_repository, revision,\n+\t\t\t\t GET_OID_QUIETLY | GET_OID_RETURN_FAILURE,\n+\t\t\t\t &info->w_commit, &unused)) {\n \t\tif (!quiet)\n \t\t\terror(_(\"%s is not a valid reference\"), revision);\n \t\tfree_stash_info(info);\n@@ -1775,6 +1785,172 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \treturn ret;\n }\n \n+static char *write_commit_with_parents(struct object_id *out, const struct stash_info *info, struct commit_list *parents)\n+{\n+\tsize_t author_len, committer_len;\n+\tstruct commit *this = NULL;\n+\tconst char *orig_author = NULL, *orig_committer = NULL;\n+\tchar *author = NULL, *committer = NULL;\n+\tconst char *buffer = NULL;\n+\tunsigned long bufsize;\n+\tconst char *p;\n+\tchar *msg = NULL;\n+\n+\tthis = lookup_commit_reference(the_repository, &info->w_commit);\n+\tbuffer = get_commit_buffer(this, &bufsize);\n+\torig_author = find_commit_header(buffer, \"author\", &author_len);\n+\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n+\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n+\n+\tif (!orig_author || !orig_committer || !p) {\n+\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(&info->w_commit));\n+\t\tgoto out;\n+\t}\n+\t/* Jump to message. */\n+\tp += 2;\n+\n+\tauthor = xmemdupz(orig_author, author_len);\n+\tcommitter = xmemdupz(orig_committer, committer_len);\n+\n+\tif (commit_tree_extended(p, bufsize - (p - buffer),\n+\t\t\t\t &info->w_tree, parents,\n+\t\t\t\t out, author, committer,\n+\t\t\t\t NULL, NULL)) {\n+\t\terror(_(\"could not write commit\"));\n+\t\tgoto out;\n+\t}\n+\tmsg = xmemdupz(p, bufsize - (p - buffer));\n+out:\n+\tunuse_commit_buffer(this, buffer);\n+\tfree(author);\n+\tfree(committer);\n+\treturn msg;\n+}\n+\n+static int do_export_stash(const char *ref, size_t argc, const char **argv)\n+{\n+\tstruct object_id base;\n+\tstruct commit *prev;\n+\tstruct stash_info *items = NULL;\n+\tsize_t nitems = 0, nalloc = 0;\n+\tint res = 0;\n+\n+\tprepare_fallback_ident(\"git stash\", \"git@stash\");\n+\n+\t/* First, we create a single empty commit. */\n+\tif (commit_tree(NULL, 0, the_hash_algo->empty_tree, NULL, &base, NULL, NULL))\n+\t\treturn error(_(\"unable to write base commit\"));\n+\n+\tprev = lookup_commit_reference(the_repository, &base);\n+\n+\n+\tif (argc) {\n+\t\t/*\n+\t\t * Find each specified stash, and load data into the array.\n+\t\t */\n+\t\tfor (size_t i = 0; i < argc; i++, nitems++) {\n+\t\t\tint ret;\n+\n+\t\t\tif (nalloc <= i) {\n+\t\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n+\t\t\t\titems = xrealloc(items, new * sizeof(*items));\n+\t\t\t\tnalloc = new;\n+\t\t\t}\n+\t\t\tmemset(&items[i], 0, sizeof(*items));\n+\t\t\t/* We want this to be quiet because it might not exist. */\n+\t\t\tret = get_stash_info_1(&items[i], argv[i], 1);\n+\t\t\tif (ret)\n+\t\t\t\treturn error(_(\"unable to find stash entry %s\"), argv[i]);\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * Walk the reflog, finding each stash entry, and load data into the\n+\t\t * array.\n+\t\t */\n+\t\tfor (size_t i = 0;; i++, nitems++) {\n+\t\t\tchar buf[32];\n+\t\t\tint ret;\n+\n+\t\t\tif (nalloc <= i) {\n+\t\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n+\t\t\t\titems = xrealloc(items, new * sizeof(*items));\n+\t\t\t\tnalloc = new;\n+\t\t\t}\n+\t\t\tsnprintf(buf, sizeof(buf), \"%zu\", i);\n+\t\t\tmemset(&items[i], 0, sizeof(*items));\n+\t\t\t/* We want this to be quiet because it might not exist. */\n+\t\t\tret = get_stash_info_1(&items[i], buf, 1);\n+\t\t\tif (ret)\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Now, create a set of commits identical to the regular stash commits,\n+\t * but where their first parents form a chain to our original empty\n+\t * base commit.\n+\t */\n+\tfor (ssize_t i = nitems - 1; i >= 0; i--) {\n+\t\tstruct commit_list *parents = NULL;\n+\t\tstruct commit_list **next = &parents;\n+\t\tstruct object_id out;\n+\t\tchar *msg;\n+\n+\t\tnext = commit_list_append(prev, next);\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i].b_commit), next);\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i].i_commit), next);\n+\t\tif (items[i].has_u)\n+\t\t\tnext = commit_list_append(lookup_commit_reference(the_repository,\n+\t\t\t\t\t\t\t\t\t  &items[i].u_commit),\n+\t\t\t\t\t\t  next);\n+\n+\t\tmsg = write_commit_with_parents(&out, &items[i], parents);\n+\t\tif (!msg) {\n+\t\t\tres = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tfree(msg);\n+\t\tprev = lookup_commit_reference(the_repository, &out);\n+\t}\n+\tif (ref) {\n+\t\tupdate_ref(NULL, ref, &prev->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n+\t} else {\n+\t\tputs(oid_to_hex(&prev->object.oid));\n+\t}\n+out:\n+\tfor (size_t i = 0; i < nitems; i++) {\n+\t\tfree_stash_info(&items[i]);\n+\t}\n+\tfree(items);\n+\n+\treturn res;\n+}\n+\n+static int export_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tint ret = 0;\n+\tint print = 0;\n+\tconst char *ref = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"print\", &print,\n+\t\t\t N_(\"print the object ID instead of writing it to a ref\")),\n+\t\tOPT_STRING(0, \"to-ref\", &ref, \"refname\",\n+\t\t\t   N_(\"save the data to the given ref\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_export_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (!(!!ref ^ print))\n+\t\treturn error(\"exactly one of --print or --to-ref is required\");\n+\n+\n+\tret = do_export_stash(ref, argc, argv);\n+\treturn ret;\n+}\n+\n int cmd_stash(int argc, const char **argv, const char *prefix)\n {\n \tpid_t pid = getpid();\n@@ -1818,6 +1994,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!push_stash(argc, argv, prefix, 0);\n \telse if (!strcmp(argv[0], \"save\"))\n \t\treturn !!save_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"export\"))\n+\t\treturn !!export_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\n"},{"id":"451047","messageId":"20220310173236.4165310-7-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH 6/6] doc: add stash export and import to docs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T17:32:36Z","receivedAt":"2022-03-10T17:35:33Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Now that we have an easy way for users to import and export their\nstashes, let's document this in manual page so users will know how to\nuse it.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-stash.txt | 27 +++++++++++++++++++++++++++\n 1 file changed, 27 insertions(+)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 6e15f47525..283677314a 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -20,6 +20,8 @@ SYNOPSIS\n 'git stash' clear\n 'git stash' create [<message>]\n 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n+'git stash' export ( --print | --to-ref <ref> ) [<stashes>]\n+'git stash' import <commit>\n \n DESCRIPTION\n -----------\n@@ -151,6 +153,18 @@ store::\n \treflog.  This is intended to be useful for scripts.  It is\n \tprobably not the command you want to use; see \"push\" above.\n \n+export ( --print | --to-ref <ref> ) [<stashes>]::\n+\n+\tExport the specified stashes, or all of them if none are specified, to\n+\ta chain of commits which can be transferred using the normal fetch and\n+\tpush mechanisms, then imported using the `import` subcommand.\n+\n+import <commit>::\n+\n+\tImport the specified stashes from the specified commit, which must have been\n+\tcreated by `export`, and add them to the list of stashes.  To replace the\n+\texisting stashes, use `clear` first.\n+\n OPTIONS\n -------\n -a::\n@@ -239,6 +253,19 @@ literally (including newlines and quotes).\n +\n Quiet, suppress feedback messages.\n \n+--print::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes without\n+storing it anywhere in the ref namespace and print the object ID to\n+standard output.  This is designed for scripts.\n+\n+--to-ref::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes and store\n+it to the specified ref.\n+\n \\--::\n \tThis option is only valid for `push` command.\n +\n"},{"id":"451048","messageId":"20220310173236.4165310-6-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH 5/6] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T17:32:35Z","receivedAt":"2022-03-10T17:35:34Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Now that we have a way to export stashes to a ref, let's provide a way\nto import them from such a ref back to the stash.  This works much the\nway the export code does, except that we strip off the first parent\nchain commit and then store each resulting commit back to the stash.\n\nWe don't clear the stash first and instead add the specified stashes to\nthe top of the stash.  This is because users may want to export just a\nfew stashes, such as to share a small amount of work in progress with a\ncolleague, and it would be undesirable for the receiving user to lose\nall of their data.  For users who do want to replace the stash, it's\neasy to do to: simply run \"git stash clear\" first.\n\nWe specifically rely on the fact that we'll produce identical stash\ncommits on both sides in our tests.  This provides a cheap,\nstraightforward check for our tests and also makes it easy for users to\nsee if they already have the same data in both repositories.  Note that\nthe exported commits don't because we don't write a predictable base\ncommit, however.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n builtin/stash.c  | 125 +++++++++++++++++++++++++++++++++++++++++++++++\n t/t3903-stash.sh |  52 ++++++++++++++++++++\n 2 files changed, 177 insertions(+)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 582a04dbab..626e7b8531 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -34,6 +34,7 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tN_(\"git stash export (--print | --to-ref <ref>) [<stashes>]\"),\n+\tN_(\"git stash import <commit>\"),\n \tNULL\n };\n \n@@ -95,6 +96,10 @@ static const char * const git_stash_export_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_import_usage[] = {\n+\tN_(\"git stash import <commit>\"),\n+\tNULL\n+};\n \n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n@@ -104,6 +109,7 @@ static struct strbuf stash_index_path = STRBUF_INIT;\n  * b_commit is set to the base commit\n  * i_commit is set to the commit containing the index tree\n  * u_commit is set to the commit containing the untracked files tree\n+ * c_commit is set to the first parent (chain commit) when importing and is otherwise unset\n  * w_tree is set to the working tree\n  * b_tree is set to the base tree\n  * i_tree is set to the index tree\n@@ -114,6 +120,7 @@ struct stash_info {\n \tstruct object_id b_commit;\n \tstruct object_id i_commit;\n \tstruct object_id u_commit;\n+\tstruct object_id c_commit;\n \tstruct object_id w_tree;\n \tstruct object_id b_tree;\n \tstruct object_id i_tree;\n@@ -138,6 +145,32 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n \t\tdie(_(\"'%s' is not a stash-like commit\"), revision);\n }\n \n+static int get_stash_info_for_import(struct stash_info *info, const struct object_id *oid)\n+{\n+\tint has_parents;\n+\tchar hexoid[GIT_MAX_HEXSZ + 1];\n+\toid_to_hex_r(hexoid, oid);\n+\n+\toidcpy(&info->w_commit, oid);\n+\tif (get_oidf(&info->w_tree, \"%s:\", hexoid))\n+\t\treturn -1;\n+\n+\thas_parents = !get_oidf(&info->c_commit, \"%s^1\", hexoid);\n+\n+\t/* If this tree is the empty one and we have no parents, we've reached the end. */\n+\tif (oideq(&info->w_tree, the_hash_algo->empty_tree) && !has_parents)\n+\t\treturn 1;\n+\tif (get_oidf(&info->b_commit, \"%s^2\", hexoid) ||\n+\t    get_oidf(&info->i_commit, \"%s^3\", hexoid) ||\n+\t    get_oidf(&info->b_tree, \"%s^2:\", hexoid) ||\n+\t    get_oidf(&info->i_tree, \"%s^3:\", hexoid))\n+\t\treturn -1;\n+\n+\tinfo->has_u = !get_oidf(&info->u_commit, \"%s^4\", hexoid) &&\n+\t\t      !get_oidf(&info->u_tree, \"%s^4:\", hexoid);\n+\treturn 0;\n+}\n+\n static int get_stash_info_1(struct stash_info *info, const char *commit, int quiet)\n {\n \tint ret;\n@@ -1827,6 +1860,96 @@ static char *write_commit_with_parents(struct object_id *out, const struct stash\n \treturn msg;\n }\n \n+static int do_import_stash(const char *rev)\n+{\n+\tstruct object_id oid;\n+\tsize_t nitems = 0, nalloc = 0;\n+\tstruct stash_info *items = NULL;\n+\tint res = 0;\n+\n+\tif (get_oid(rev, &oid))\n+\t\treturn error(_(\"not a valid revision: %s\"), rev);\n+\n+\t/*\n+\t * Walk the commit history, finding each stash entry, and load data into\n+\t * the array.\n+\t */\n+\tfor (size_t i = 0;; i++, nitems++) {\n+\t\tint ret;\n+\n+\t\tif (nalloc <= i) {\n+\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n+\t\t\titems = xrealloc(items, new * sizeof(*items));\n+\t\t\tnalloc = new;\n+\t\t}\n+\t\tmemset(&items[i], 0, sizeof(*items));\n+\t\t/* We want this to be quiet because it might not exist. */\n+\t\tret = get_stash_info_for_import(&items[i], &oid);\n+\t\tif (ret < 0)\n+\t\t\treturn error(_(\"%s is not a valid exported stash commit\"), oid_to_hex(&oid));\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t\toidcpy(&oid, &items[i].c_commit);\n+\t}\n+\n+\t/*\n+\t * Now, walk each entry, adding it to the stash as a normal stash\n+\t * commit.\n+\t */\n+\tfor (ssize_t i = nitems - 1; i >= 0; i--) {\n+\t\tstruct commit_list *parents = NULL;\n+\t\tstruct commit_list **next = &parents;\n+\t\tstruct object_id out;\n+\t\tchar *msg;\n+\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i].b_commit), next);\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i].i_commit), next);\n+\t\tif (items[i].has_u)\n+\t\t\tnext = commit_list_append(lookup_commit_reference(the_repository,\n+\t\t\t\t\t\t\t\t   &items[i].u_commit),\n+\t\t\t\t\t\t  next);\n+\n+\t\tmsg = write_commit_with_parents(&out, &items[i], parents);\n+\t\tif (!msg) {\n+\t\t\tres = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (do_store_stash(&out, msg, 1)) {\n+\t\t\tfree(msg);\n+\t\t\tres = -1;\n+\t\t\terror(_(\"Cannot save the current status\"));\n+\t\t\tgoto out;\n+\t\t}\n+\t\tfree(msg);\n+\t}\n+out:\n+\tfor (size_t i = 0; i < nitems; i++) {\n+\t\tfree_stash_info(&items[i]);\n+\t}\n+\tfree(items);\n+\n+\treturn res;\n+}\n+\n+static int import_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tint ret = 0;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_import_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (argc != 1)\n+\t\treturn error(_(\"a revision to import from is required\"));\n+\n+\n+\tret = do_import_stash(argv[0]);\n+\treturn ret;\n+}\n+\n static int do_export_stash(const char *ref, size_t argc, const char **argv)\n {\n \tstruct object_id base;\n@@ -1996,6 +2119,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!save_stash(argc, argv, prefix);\n \telse if (!strcmp(argv[0], \"export\"))\n \t\treturn !!export_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"import\"))\n+\t\treturn !!import_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex b149e2af44..d2ddede9be 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1295,6 +1295,58 @@ test_expect_success 'stash --keep-index with file deleted in index does not resu\n \ttest_path_is_missing to-remove\n '\n \n+test_expect_success 'stash export and import round-trip stashes' '\n+\tgit reset &&\n+\t>untracked &&\n+\t>tracked1 &&\n+\t>tracked2 &&\n+\tgit add tracked* &&\n+\tgit stash -- &&\n+\t>subdir/untracked &&\n+\t>subdir/tracked1 &&\n+\t>subdir/tracked2 &&\n+\tgit add subdir/tracked* &&\n+\tgit stash -- subdir/ &&\n+\tstash0=$(git rev-parse --verify stash@{0}) &&\n+\tstash1=$(git rev-parse --verify stash@{1}) &&\n+\tsimple=$(git stash export --print) &&\n+\tgit stash clear &&\n+\tgit stash import \"$simple\" &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported0\" = \"$stash0\" &&\n+\ttest \"$imported1\" = \"$stash1\" &&\n+\tgit stash export --to-ref refs/heads/foo &&\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported0\" = \"$stash0\" &&\n+\ttest \"$imported1\" = \"$stash1\"\n+'\n+\n+test_expect_success 'stash import appends commits' '\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\techo $(cat actual | wc -l) >count &&\n+\tgit stash import refs/heads/foo &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = $(($(cat count) * 2)) actual\n+'\n+\n+test_expect_success 'stash export can accept specified stashes' '\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\tgit stash export --to-ref bar stash@{1} stash@{0} &&\n+\tgit stash clear &&\n+\tgit stash import bar &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported1\" = \"$stash0\" &&\n+\ttest \"$imported0\" = \"$stash1\" &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = 2 actual\n+'\n+\n test_expect_success 'stash apply should succeed with unmodified file' '\n \techo base >file &&\n \tgit add file &&\n"},{"id":"451069","messageId":"xmqq35jpei18.fsf@gitster.g","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH 0/6] Importing and exporting stashes to refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-10T19:14:59Z","receivedAt":"2022-03-10T19:15:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> ...  The commits used in a stash export are nearly\n> identical to those used in the stashes, with one notable change: the\n> first parent of a stash is a pointer to the previous stash, or an empty\n> commit if there is no previous stash.  All of the other parents used in\n> the stash commit are present following it in their normal order.\n> ...\n> We intentionally attempt to exactly round-trip commits between stashes,\n> although we don't do so for the exported data due to the base commit not\n> having identical timestamps.  Preserving the commits exactly lets us\n> more efficiently test our code and it also permits users to more easily\n> determine if they have the same data.\n\nHmph, out of reflog entries stash@{0}, stash@{1}, stash@{3}, if we\ncreate a chain of commits A, B, C such that\n\n\tA^2 = B, A^1 = stash@{0}\n\tB^2 = C, B^1 = stash@{1}\n\t         C^1 = stash@{2}\n\nthen the original stash entry commits can be recreated identically,\nand after you export the stash as \"A\", you can \"import\" from it\nwithout creating any new commit to represent the stash entries, no?\n\nWhen we create A, if we use a predictable commit log message and\nthe same author/committer ident as A^1 (i.e. stash@{0}), and do it\nthe same for B and C, then no matter who exports the stash and at\nwhich time, we'd get an identical result, I would presume.\n\n> The tooling here is intentionally plumbing.  It's designed to be simple\n> and functional and get the basic job done.  If we want additional\n> features, we can add them in the future, but this should be a simple,\n> basic feature set that can support additional uses.\n\nSounds sensible.\n"},{"id":"451077","messageId":"Yipn+UiRw/GL6u2Y@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"xmqq35jpei18.fsf@gitster.g","subject":"Re: [PATCH 0/6] Importing and exporting stashes to refs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T21:04:57Z","receivedAt":"2022-03-10T21:05:05Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-10 at 19:14:59, Junio C Hamano wrote:\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> \n> > ...  The commits used in a stash export are nearly\n> > identical to those used in the stashes, with one notable change: the\n> > first parent of a stash is a pointer to the previous stash, or an empty\n> > commit if there is no previous stash.  All of the other parents used in\n> > the stash commit are present following it in their normal order.\n> > ...\n> > We intentionally attempt to exactly round-trip commits between stashes,\n> > although we don't do so for the exported data due to the base commit not\n> > having identical timestamps.  Preserving the commits exactly lets us\n> > more efficiently test our code and it also permits users to more easily\n> > determine if they have the same data.\n> \n> Hmph, out of reflog entries stash@{0}, stash@{1}, stash@{3}, if we\n> create a chain of commits A, B, C such that\n> \n> \tA^2 = B, A^1 = stash@{0}\n> \tB^2 = C, B^1 = stash@{1}\n> \t         C^1 = stash@{2}\n> \n> then the original stash entry commits can be recreated identically,\n> and after you export the stash as \"A\", you can \"import\" from it\n> without creating any new commit to represent the stash entries, no?\n\nTrue, that's an alternative approach.  Mine has the nice ability that\nyou can see the items in the stash with log --first-parent, which I\nfound to be useful in my testing.  We could of course change yours\nto have that property as well by reversing the order, but then the last\nitem in the chain would have a base commit or a different pattern.\n\nYours does have the nice ability that we can see the actual original\nstash commits as well.\n\n> When we create A, if we use a predictable commit log message and\n> the same author/committer ident as A^1 (i.e. stash@{0}), and do it\n> the same for B and C, then no matter who exports the stash and at\n> which time, we'd get an identical result, I would presume.\n\nTrue.\n\nI do want to preserve my nice --first-parent property.  What I propose\nto do is this: I'll take your approach and reverse the parents to\npreserve the --first-parent chain and synthesize a predictable root\ncommit based on the fake ID information we use for stashes when nobody's\nprovided any.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"451081","messageId":"xmqqbkydcwt9.fsf@gitster.g","threadId":"57531","inReplyTo":"Yipn+UiRw/GL6u2Y@camp.crustytoothpaste.net","subject":"Re: [PATCH 0/6] Importing and exporting stashes to refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-10T21:38:42Z","receivedAt":"2022-03-10T21:39:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> I do want to preserve my nice --first-parent property.  What I propose\n> to do is this: I'll take your approach and reverse the parents to\n> preserve the --first-parent chain and synthesize a predictable root\n> commit based on the fake ID information we use for stashes when nobody's\n> provided any.\n\nI am wondering if this can be made not an export format but a new\nmechanism to store stashes that we use without having to export and\nimport.  Capping the series of \"stash entry\" commits with an extra\ncommit that is continuously amended, and recording which stash entry\nhas already been used (and not to be shown) etc., in the log message\npart of that commit, would give us \"stash drop\" without rewriting\nall the history and would easily bring us to feature parity with the\nreflog based implementation, I would hope?\n\n"},{"id":"451087","messageId":"Yip+zMYXVfcNJ03F@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"xmqqbkydcwt9.fsf@gitster.g","subject":"Re: [PATCH 0/6] Importing and exporting stashes to refs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-10T22:42:20Z","receivedAt":"2022-03-10T22:42:25Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-10 at 21:38:42, Junio C Hamano wrote:\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> \n> > I do want to preserve my nice --first-parent property.  What I propose\n> > to do is this: I'll take your approach and reverse the parents to\n> > preserve the --first-parent chain and synthesize a predictable root\n> > commit based on the fake ID information we use for stashes when nobody's\n> > provided any.\n> \n> I am wondering if this can be made not an export format but a new\n> mechanism to store stashes that we use without having to export and\n> import.  Capping the series of \"stash entry\" commits with an extra\n> commit that is continuously amended, and recording which stash entry\n> has already been used (and not to be shown) etc., in the log message\n> part of that commit, would give us \"stash drop\" without rewriting\n> all the history and would easily bring us to feature parity with the\n> reflog based implementation, I would hope?\n\nI had thought of providing a different stash format via\nextensions.stashFormat or something instead of this.  The problem becomes\nbackward compatibility: we essentially can't use stashes with older\nversions, and a very common use case in development is to work inside a\nDocker container or such where the version of Git is whatever the OS\nhappened to ship, so such a change can be painful.  It also impedes\nworking with the repository using libgit2, which is heavily used in some\nenvironments (e.g., the Rust toolchain), until someone ports that change\nthere.\n\nI'm not opposed to seeing such a format change where refs/stash itself\nbecomes pushable via a format change, but for my present use case, I\ndon't want to do that right now.  It's possible that I might write such\na series in the future, or someone else could write it, but I won't be\nwriting it at the present moment.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"451107","messageId":"220311.86bkydi65v.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220310173236.4165310-5-sandals@crustytoothpaste.net","subject":"Re: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-11T02:08:04Z","receivedAt":"2022-03-11T02:17:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Mar 10 2022, brian m. carlson wrote:\n\n> +\tsize_t author_len, committer_len;\n> +\tstruct commit *this = NULL;\n> +\tconst char *orig_author = NULL, *orig_committer = NULL;\n> +\tchar *author = NULL, *committer = NULL;\n> +\tconst char *buffer = NULL;\n> +\tunsigned long bufsize;\n> +\tconst char *p;\n> +\tchar *msg = NULL;\n\nThese shouldn't be initialized unless they really need to..\n\n> +\tthis = lookup_commit_reference(the_repository, &info->w_commit);\n\n..and some are clobbered right away here, so all of these should not be initializzed.\n\n> +\tbuffer = get_commit_buffer(this, &bufsize);\n> +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n> +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n> +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n\n...since by doing so we hide genuine \"uninitialized\"\nwarnings. E.g. \"author_len\" here isn't initialized, but is set by\nfind_commit_header(), but if that line was removed we'd warn below, but\nnot if it's initialized when the variables are declared..\n\n> +\t\tfor (size_t i = 0;; i++, nitems++) {\n> +\t\t\tchar buf[32];\n> +\t\t\tint ret;\n> +\n> +\t\t\tif (nalloc <= i) {\n> +\t\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n> +\t\t\t\titems = xrealloc(items, new * sizeof(*items));\n> +\t\t\t\tnalloc = new;\n\nCan't we just use the usual ALLOC_GROW() pattern here?\n\n> +\t\t\t}\n> +\t\t\tsnprintf(buf, sizeof(buf), \"%zu\", i);\n\nAren't the %z formats unportable (even with our newly found reliance on\nmore C99)? I vaguely recall trying them recently and the windows CI jobs\nerroring...\n\n> +\tfor (ssize_t i = nitems - 1; i >= 0; i--) {\n\nThe ssize_t type can be really small (it's not a signed size_t), so this\nis unportable, but in practice maybe it's OK...\n\nIn this case if you're just wanting to count down in a list maybe you\ncan use the pattern I used in 99d60545f87 (string-list API: change \"nr\"\nand \"alloc\" to \"size_t\", 2022-03-07)?\n\n> +\tif (ref) {\n> +\t\tupdate_ref(NULL, ref, &prev->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n> +\t} else {\n> +\t\tputs(oid_to_hex(&prev->object.oid));\n> +\t}\n\nNit: braces can be gone.\n\n> +out:\n> +\tfor (size_t i = 0; i < nitems; i++) {\n> +\t\tfree_stash_info(&items[i]);\n> +\t}\n\nDitto.\n\n> +static int export_stash(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint ret = 0;\n> +\tint print = 0;\n> +\tconst char *ref = NULL;\n> +\tstruct option options[] = {\n> +\t\tOPT_BOOL(0, \"print\", &print,\n> +\t\t\t N_(\"print the object ID instead of writing it to a ref\")),\n> +\t\tOPT_STRING(0, \"to-ref\", &ref, \"refname\",\n\nNeeds _(\"refname\")\n\n> +\t\t\t   N_(\"save the data to the given ref\")),\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\targc = parse_options(argc, argv, prefix, options,\n> +\t\t\t     git_stash_export_usage,\n> +\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n> +\n> +\tif (!(!!ref ^ print))\n> +\t\treturn error(\"exactly one of --print or --to-ref is required\");\n\nCute, but maybe we can just use OPT_CMDMODE(), and if it's \"to-ref\"\nshift one off argv afterwards (which'll be your &ref).\n\nI.e. it'll give you the option incompatibility check, and without a new\ntranslaed string.\n\nWhich, if we're keeping this version should use _().\n\n"},{"id":"451346","messageId":"2422376f-79aa-2d35-2646-c3611e2ef8d6@gmail.com","threadId":"57531","inReplyTo":"220311.86bkydi65v.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-03-14T21:19:10Z","receivedAt":"2022-03-14T21:19:19Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Brian and Ævar\n\nFirstly I think this is a useful feature to add to git stash, thanks for \nworking on it Brian\n\nOn 11/03/2022 02:08, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Thu, Mar 10 2022, brian m. carlson wrote:\n> \n>> +\tsize_t author_len, committer_len;\n>> +\tstruct commit *this = NULL;\n>> +\tconst char *orig_author = NULL, *orig_committer = NULL;\n>> +\tchar *author = NULL, *committer = NULL;\n>> +\tconst char *buffer = NULL;\n>> +\tunsigned long bufsize;\n>> +\tconst char *p;\n>> +\tchar *msg = NULL;\n> \n> These shouldn't be initialized unless they really need to..\n> \n>> +\tthis = lookup_commit_reference(the_repository, &info->w_commit);\n> \n> ..and some are clobbered right away here, so all of these should not be initializzed.\n> \n>> +\tbuffer = get_commit_buffer(this, &bufsize);\n>> +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n>> +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n>> +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n\nYou could start searching from orig_committer rather than buffer but I'm \nsure it doesn't make any real difference. The sequencer does something \nsimilar to this to replay commits when rebasing - is there any scope for \nsharing code between the two?\n\n> ...since by doing so we hide genuine \"uninitialized\"\n> warnings. E.g. \"author_len\" here isn't initialized, but is set by\n> find_commit_header(), but if that line was removed we'd warn below, but\n> not if it's initialized when the variables are declared..\n> \n>> +\t\tfor (size_t i = 0;; i++, nitems++) {\n\nDo we need i and nitems?\n\n>> +\t\t\tchar buf[32];\n>> +\t\t\tint ret;\n>> +\n>> +\t\t\tif (nalloc <= i) {\n>> +\t\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n>> +\t\t\t\titems = xrealloc(items, new * sizeof(*items));\n>> +\t\t\t\tnalloc = new;\n> \n> Can't we just use the usual ALLOC_GROW() pattern here?\nALLOC_GROW_BY() zeros out the memory which would mean we could remove \nthe memset() calls in the loops. I noticed in some other loops we know \nthe size in advance and could use CALLOC_ARRAY().\n\n>> +\t\t\t}\n>> +\t\t\tsnprintf(buf, sizeof(buf), \"%zu\", i);\n> \n> Aren't the %z formats unportable (even with our newly found reliance on\n> more C99)? I vaguely recall trying them recently and the windows CI jobs\n> erroring...\n\nAccording to [1] it has been available since at least 2015. It is \ncertainly much nicer than casting every size_t to uintmax_t and having \nto use PRIuMAX.\n\n>> +\tfor (ssize_t i = nitems - 1; i >= 0; i--) {\n> \n> The ssize_t type can be really small (it's not a signed size_t), so this\n> is unportable, but in practice maybe it's OK...\n\nI'm not really convinced by this ssize_t can be small argument[2], do \nyou know of any platforms where it is true?\n\nBest Wishes\n\nPhillip\n\n[1] \nhttps://docs.microsoft.com/en-us/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions?view=msvc-140#size\n\n[2] \nhttps://lore.kernel.org/git/e881a455-88b5-9c87-03a8-caaee68bb344@gmail.com/\n\n"},{"id":"451383","messageId":"2e5b6c09-b0eb-4e97-0eea-a150e30ad493@gmail.com","threadId":"57531","inReplyTo":"2422376f-79aa-2d35-2646-c3611e2ef8d6@gmail.com","subject":"Re: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-03-15T10:50:52Z","receivedAt":"2022-03-15T10:52:43Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 14/03/2022 21:19, Phillip Wood wrote:\n> Hi Brian and Ævar\n\nSorry brian, I forgot you prefer a lowercase \"b\"\n\n> Firstly I think this is a useful feature to add to git stash, thanks for \n> working on it Brian\n> \n> On 11/03/2022 02:08, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Thu, Mar 10 2022, brian m. carlson wrote:\n>>\n>>> +    size_t author_len, committer_len;\n>>> +    struct commit *this = NULL;\n>>> +    const char *orig_author = NULL, *orig_committer = NULL;\n>>> +    char *author = NULL, *committer = NULL;\n>>> +    const char *buffer = NULL;\n>>> +    unsigned long bufsize;\n>>> +    const char *p;\n>>> +    char *msg = NULL;\n>>\n>> These shouldn't be initialized unless they really need to..\n>>\n>>> +    this = lookup_commit_reference(the_repository, &info->w_commit);\n>>\n>> ..and some are clobbered right away here, so all of these should not \n>> be initializzed.\n>>\n>>> +    buffer = get_commit_buffer(this, &bufsize);\n>>> +    orig_author = find_commit_header(buffer, \"author\", &author_len);\n>>> +    orig_committer = find_commit_header(buffer, \"committer\", \n>>> &committer_len);\n>>> +    p = memmem(buffer, bufsize, \"\\n\\n\", 2);\n> \n> You could start searching from orig_committer rather than buffer but I'm \n> sure it doesn't make any real difference. The sequencer does something \n> similar to this to replay commits when rebasing - is there any scope for \n> sharing code between the two?\n\nI had a look at sequencer.c this morning and I think the answer is \"not \neasily\". In principle it would be nice to have a split_commit_header() \nfunction that filled a struct of pointer, length pairs for the author, \ncommitter, message and optionally the subject that could be used here, \nin the sequencer and in fast-export but I think that is more work than \nis reasonable for this series.\n\nBest Wishes\n\nPhillip\n"},{"id":"451483","messageId":"xmqqv8wdn8nx.fsf@gitster.g","threadId":"57531","inReplyTo":"20220310173236.4165310-3-sandals@crustytoothpaste.net","subject":"Re: [PATCH 2/6] builtin/stash: fill in all commit data","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-16T16:50:42Z","receivedAt":"2022-03-16T16:50:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> get_stash_info doesn't ensure that all entries are filled in in all\n> cases.  However, we'll want to use this information to write new\n> commits, and when we do so, we'll need all the information to be\n> present.  Fill in all the commit information whenever we call this\n> function.\n>\n> Note that the behavior of info->has_u doesn't change here.  If we\n> previously read a tree a refs/stash^3:, then refs/stash^3 must be a\n> treeish.  We already here assume that the other parents specifically\n> commits, so it should be safe to do so here as well.\n>\n> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n>  builtin/stash.c | 4 +++-\n>  1 file changed, 3 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 2aa06cc91d..128f0a01ef 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -124,6 +124,7 @@ static void free_stash_info(struct stash_info *info)\n\nBefore this function ...\n\n>  static void assert_stash_like(struct stash_info *info, const char *revision)\n>  {\n>  \tif (get_oidf(&info->b_commit, \"%s^1\", revision) ||\n> +\t    get_oidf(&info->i_commit, \"%s^2\", revision) ||\n>  \t    get_oidf(&info->w_tree, \"%s:\", revision) ||\n>  \t    get_oidf(&info->b_tree, \"%s^1:\", revision) ||\n>  \t    get_oidf(&info->i_tree, \"%s^2:\", revision))\n\n... there is this comment.\n\n/*\n * w_commit is set to the commit containing the working tree\n * b_commit is set to the base commit\n * i_commit is set to the commit containing the index tree\n * u_commit is set to the commit containing the untracked files tree\n * w_tree is set to the working tree\n * b_tree is set to the base tree\n * i_tree is set to the index tree\n * u_tree is set to the untracked files tree\n */\n\nWe probably would want to comment that u_commit (hence u_tree) is\noptional.  That is why assert_stash_like() does not say anything\nabout u_commit or u_tree, right?\n\nIt is curious why the function does not learn to check w_commit,\neven though the proposed commit log message claims that this is\nabout filling all commit data.\n\n> @@ -166,7 +167,8 @@ static int get_stash_info_1(struct stash_info *info, const char *commit, int qui\n>  \n>  \tassert_stash_like(info, revision);\n>  \n> -\tinfo->has_u = !get_oidf(&info->u_tree, \"%s^3:\", revision);\n> +\tinfo->has_u = !get_oidf(&info->u_commit, \"%s^3\", revision) &&\n> +\t\t      !get_oidf(&info->u_tree, \"%s^3:\", revision);\n>  \n>  \tend_of_rev = strchrnul(revision, '@');\n>  \tstrbuf_add(&symbolic, revision, end_of_rev - revision);\n"},{"id":"451484","messageId":"xmqqo825n8eh.fsf@gitster.g","threadId":"57531","inReplyTo":"20220310173236.4165310-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH 3/6] object-name: make get_oid quietly return an error","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-16T16:56:22Z","receivedAt":"2022-03-16T16:56:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> A reasonable person looking at the signature and usage of get_oid and\n> friends might conclude that in the event of an error, it always returns\n> -1.  However, this is not the case.  Instead, get_oid_basic dies if we\n> go too far back into the history of a reflog (or, when quiet, simply\n> exits).\n>\n> This is not especially useful, since in many cases, we might want to\n> handle this error differently.  Let's add a flag here to make it just\n> return -1 like elsewhere in these code paths.\n>\n> Note that we cannot make this behavior the default, since we have many\n> other codepaths that rely on the existing behavior, including in tests.\n>\n> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n>  cache.h       | 21 +++++++++++----------\n>  object-name.c |  6 +++++-\n>  2 files changed, 16 insertions(+), 11 deletions(-)\n>\n> diff --git a/cache.h b/cache.h\n> index 825ec17198..416a9d9983 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -1366,16 +1366,17 @@ struct object_context {\n>  \tchar *path;\n>  };\n>  \n> -#define GET_OID_QUIETLY           01\n> -#define GET_OID_COMMIT            02\n> -#define GET_OID_COMMITTISH        04\n> -#define GET_OID_TREE             010\n> -#define GET_OID_TREEISH          020\n> -#define GET_OID_BLOB             040\n> -#define GET_OID_FOLLOW_SYMLINKS 0100\n> -#define GET_OID_RECORD_PATH     0200\n> -#define GET_OID_ONLY_TO_DIE    04000\n> -#define GET_OID_REQUIRE_PATH  010000\n> +#define GET_OID_QUIETLY            01\n> +#define GET_OID_COMMIT             02\n> +#define GET_OID_COMMITTISH         04\n> +#define GET_OID_TREE              010\n> +#define GET_OID_TREEISH           020\n> +#define GET_OID_BLOB              040\n> +#define GET_OID_FOLLOW_SYMLINKS  0100\n> +#define GET_OID_RECORD_PATH      0200\n> +#define GET_OID_ONLY_TO_DIE     04000\n> +#define GET_OID_REQUIRE_PATH   010000\n> +#define GET_OID_RETURN_FAILURE 020000\n\nI do not think we want this change.  The next time somebody adds an\noverly long symbol, we reformat all the lines, making it hard to\nspot that the change is only adding a single new symbol?\n\nI think we'd rather go the other way not to tempt people into\nright-aligning these constants, either by rewriting them into\n\n\t#define GET_OID_QUIETLY<TAB>(1U << 1)\n\t#define GET_OID_COMMIT<TAB>(1U << 2)\n\t#define GET_OID_COMMITTISH<TAB>(1U << 3)\n\t...\n\t\nin a separate preliminary patch without adding a new symbol, or\nadding the new symbol unaligned and without touching existing lines.\n\n> diff --git a/object-name.c b/object-name.c\n> index 92862eeb1a..daa3ef77ef 100644\n> --- a/object-name.c\n> +++ b/object-name.c\n> @@ -911,13 +911,17 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n>  \t\t\t\t\t\tlen, str,\n>  \t\t\t\t\t\tshow_date(co_time, co_tz, DATE_MODE(RFC2822)));\n>  \t\t\t\t}\n> -\t\t\t} else {\n> +\t\t\t} else if (!(flags & GET_OID_RETURN_FAILURE)) {\n>  \t\t\t\tif (flags & GET_OID_QUIETLY) {\n>  \t\t\t\t\texit(128);\n>  \t\t\t\t}\n>  \t\t\t\tdie(_(\"log for '%.*s' only has %d entries\"),\n>  \t\t\t\t    len, str, co_cnt);\n>  \t\t\t}\n> +\t\t\tif (flags & GET_OID_RETURN_FAILURE) {\n> +\t\t\t\tfree(real_ref);\n> +\t\t\t\treturn -1;\n> +\t\t\t}\n>  \t\t}\n\nSo, without the new bit, we used to die loudly or quietly.  The new\nbit allows us to return an error to the caller without dying\nourselves.\n\nYou can call the bit _RETURN_ERROR and not to worry about the\nright-alignment above ;-), but better yet, how about calling it\n_GENTLY, which is how we call such a variant of behaviour?\n\n"},{"id":"451485","messageId":"67325D49-FE1F-4C70-B4F8-80665C20BF3E@gmail.com","threadId":"57531","inReplyTo":"xmqqo825n8eh.fsf@gitster.g","subject":"Re: [PATCH 3/6] object-name: make get_oid quietly return an error","fromName":"Drew Stolee","fromEmail":"dstolee@gmail.com","sentAt":"2022-03-16T17:01:00Z","receivedAt":"2022-03-16T17:01:10Z","isPatch":true,"sender":{"key":"dstolee@gmail.com","avatar":null},"body":"Hey guys - I’d been ignoring this thread as spam but just opened it and see you meant to include Derrick Stolee. I’m Drew Stolee, likely related but who knows how. dstolee@gmail.com is not the developer you’re looking for. \n\nBest,\nDrew\n\nSent from my iPhone\n\n> On Mar 16, 2022, at 10:56 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> \n>> A reasonable person looking at the signature and usage of get_oid and\n>> friends might conclude that in the event of an error, it always returns\n>> -1.  However, this is not the case.  Instead, get_oid_basic dies if we\n>> go too far back into the history of a reflog (or, when quiet, simply\n>> exits).\n>> \n>> This is not especially useful, since in many cases, we might want to\n>> handle this error differently.  Let's add a flag here to make it just\n>> return -1 like elsewhere in these code paths.\n>> \n>> Note that we cannot make this behavior the default, since we have many\n>> other codepaths that rely on the existing behavior, including in tests.\n>> \n>> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n>> ---\n>> cache.h       | 21 +++++++++++----------\n>> object-name.c |  6 +++++-\n>> 2 files changed, 16 insertions(+), 11 deletions(-)\n>> \n>> diff --git a/cache.h b/cache.h\n>> index 825ec17198..416a9d9983 100644\n>> --- a/cache.h\n>> +++ b/cache.h\n>> @@ -1366,16 +1366,17 @@ struct object_context {\n>>    char *path;\n>> };\n>> \n>> -#define GET_OID_QUIETLY           01\n>> -#define GET_OID_COMMIT            02\n>> -#define GET_OID_COMMITTISH        04\n>> -#define GET_OID_TREE             010\n>> -#define GET_OID_TREEISH          020\n>> -#define GET_OID_BLOB             040\n>> -#define GET_OID_FOLLOW_SYMLINKS 0100\n>> -#define GET_OID_RECORD_PATH     0200\n>> -#define GET_OID_ONLY_TO_DIE    04000\n>> -#define GET_OID_REQUIRE_PATH  010000\n>> +#define GET_OID_QUIETLY            01\n>> +#define GET_OID_COMMIT             02\n>> +#define GET_OID_COMMITTISH         04\n>> +#define GET_OID_TREE              010\n>> +#define GET_OID_TREEISH           020\n>> +#define GET_OID_BLOB              040\n>> +#define GET_OID_FOLLOW_SYMLINKS  0100\n>> +#define GET_OID_RECORD_PATH      0200\n>> +#define GET_OID_ONLY_TO_DIE     04000\n>> +#define GET_OID_REQUIRE_PATH   010000\n>> +#define GET_OID_RETURN_FAILURE 020000\n> \n> I do not think we want this change.  The next time somebody adds an\n> overly long symbol, we reformat all the lines, making it hard to\n> spot that the change is only adding a single new symbol?\n> \n> I think we'd rather go the other way not to tempt people into\n> right-aligning these constants, either by rewriting them into\n> \n>    #define GET_OID_QUIETLY<TAB>(1U << 1)\n>    #define GET_OID_COMMIT<TAB>(1U << 2)\n>    #define GET_OID_COMMITTISH<TAB>(1U << 3)\n>    ...\n>    \n> in a separate preliminary patch without adding a new symbol, or\n> adding the new symbol unaligned and without touching existing lines.\n> \n>> diff --git a/object-name.c b/object-name.c\n>> index 92862eeb1a..daa3ef77ef 100644\n>> --- a/object-name.c\n>> +++ b/object-name.c\n>> @@ -911,13 +911,17 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n>>                        len, str,\n>>                        show_date(co_time, co_tz, DATE_MODE(RFC2822)));\n>>                }\n>> -            } else {\n>> +            } else if (!(flags & GET_OID_RETURN_FAILURE)) {\n>>                if (flags & GET_OID_QUIETLY) {\n>>                    exit(128);\n>>                }\n>>                die(_(\"log for '%.*s' only has %d entries\"),\n>>                    len, str, co_cnt);\n>>            }\n>> +            if (flags & GET_OID_RETURN_FAILURE) {\n>> +                free(real_ref);\n>> +                return -1;\n>> +            }\n>>        }\n> \n> So, without the new bit, we used to die loudly or quietly.  The new\n> bit allows us to return an error to the caller without dying\n> ourselves.\n> \n> You can call the bit _RETURN_ERROR and not to worry about the\n> right-alignment above ;-), but better yet, how about calling it\n> _GENTLY, which is how we call such a variant of behaviour?\n> \n"},{"id":"451486","messageId":"xmqqk0ctn7yv.fsf@gitster.g","threadId":"57531","inReplyTo":"20220310173236.4165310-5-sandals@crustytoothpaste.net","subject":"Re: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-16T17:05:44Z","receivedAt":"2022-03-16T17:05:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 128f0a01ef..582a04dbab 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -33,6 +33,7 @@ static const char * const git_stash_usage[] = {\n>  \t   \"          [--] [<pathspec>...]]\"),\n>  \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n>  \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n> +\tN_(\"git stash export (--print | --to-ref <ref>) [<stashes>]\"),\n\nIf you mean we can take zero or more stash entries, the way to write\nit I think is \n\n\t[<stash>...]\n\ncf. a few lines above about <pathspec>.\n\n> +static const char * const git_stash_export_usage[] = {\n> +\tN_(\"git stash export (--print | --to-ref <ref>) [<stashes>]\"),\n> +\tNULL\n> +};\n\nLikewise.\n\nI think we agreed that it is a better design to reuse the actual\nstash entries as one of the parents of the exported chain element\n(while using another parent to string the entries together), so I\nwon't comment on the actual implementation that recreates multi-way\nmerge commits with no relation to the stash entries, which we won't\nsee in the next iteration.\n\nThanks.\n\n\n\n\n\n"},{"id":"451487","messageId":"xmqqa6dpn70b.fsf@gitster.g","threadId":"57531","inReplyTo":"20220310173236.4165310-6-sandals@crustytoothpaste.net","subject":"Re: [PATCH 5/6] builtin/stash: provide a way to import stashes from a ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-16T17:26:28Z","receivedAt":"2022-03-16T17:26:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> @@ -104,6 +109,7 @@ static struct strbuf stash_index_path = STRBUF_INIT;\n>   * b_commit is set to the base commit\n>   * i_commit is set to the commit containing the index tree\n>   * u_commit is set to the commit containing the untracked files tree\n> + * c_commit is set to the first parent (chain commit) when importing and is otherwise unset\n>   * w_tree is set to the working tree\n>   * b_tree is set to the base tree\n>   * i_tree is set to the index tree\n> @@ -114,6 +120,7 @@ struct stash_info {\n>  \tstruct object_id b_commit;\n>  \tstruct object_id i_commit;\n>  \tstruct object_id u_commit;\n> +\tstruct object_id c_commit;\n>  \tstruct object_id w_tree;\n>  \tstruct object_id b_tree;\n>  \tstruct object_id i_tree;\n\nWith the redesign that an exported chain is a series of two-parent\nmerges, where the first parent is used to string them together in a\nsingle strand of pearls and the second parent is the stash entry,\nthe above change becomes totally unnecessary, right?  The import\nside will be doing a first-parent walk of the export, pushing the\nsecond parent into reflog of refs/stash---we may want sanity check\nthese second parents with assert_stash_like(), but there is no need\nto re-synthesize the stash entries anymore, which would simplify the\nimplementation quite a bit, right?\n\t\t\nNamely:\n\n> +static int do_import_stash(const char *rev)\n> +{\n> +\tstruct object_id oid;\n> +\tsize_t nitems = 0, nalloc = 0;\n> +\tstruct stash_info *items = NULL;\n> +\tint res = 0;\n> +\n> +\tif (get_oid(rev, &oid))\n> +\t\treturn error(_(\"not a valid revision: %s\"), rev);\n> +\n> +\t/*\n> +\t * Walk the commit history, finding each stash entry, and load data into\n> +\t * the array.\n> +\t */\n> +\tfor (size_t i = 0;; i++, nitems++) {\n> +\t\tint ret;\n> +\n> +\t\tif (nalloc <= i) {\n> +\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n> +\t\t\titems = xrealloc(items, new * sizeof(*items));\n> +\t\t\tnalloc = new;\n> +\t\t}\n> +\t\tmemset(&items[i], 0, sizeof(*items));\n> +\t\t/* We want this to be quiet because it might not exist. */\n> +\t\tret = get_stash_info_for_import(&items[i], &oid);\n\nThe new helper function is not necessary; we can use vanilla\nget_stash_info() on the second parent to get the same information,\nand we do not really need to keep it in core.  We can sanity check\nthe shape of the imported stash entry right away and discard\neverything except for the commit object name.\n\n> +\t/*\n> +\t * Now, walk each entry, adding it to the stash as a normal stash\n> +\t * commit.\n> +\t */\n> +\tfor (ssize_t i = nitems - 1; i >= 0; i--) {\n> +\t\tstruct commit_list *parents = NULL;\n> +\t\tstruct commit_list **next = &parents;\n> +\t\tstruct object_id out;\n> +\t\tchar *msg;\n> +\n> +\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i].b_commit), next);\n> +\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i].i_commit), next);\n> +\t\tif (items[i].has_u)\n> +\t\t\tnext = commit_list_append(lookup_commit_reference(the_repository,\n> +\t\t\t\t\t\t\t\t   &items[i].u_commit),\n> +\t\t\t\t\t\t  next);\n> +\n> +\t\tmsg = write_commit_with_parents(&out, &items[i], parents);\n\nAnd this part becomes completely unnecessary if we reuse what the\norigin repository already had, which directly can be ...\n\n> +\t\tif (!msg) {\n> +\t\t\tres = -1;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\t\tif (do_store_stash(&out, msg, 1)) {\n\n... fed to this call.\n\n"},{"id":"451488","messageId":"xmqq35jhn6ni.fsf@gitster.g","threadId":"57531","inReplyTo":"20220310173236.4165310-7-sandals@crustytoothpaste.net","subject":"Re: [PATCH 6/6] doc: add stash export and import to docs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-16T17:34:09Z","receivedAt":"2022-03-16T17:34:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> Now that we have an easy way for users to import and export their\n> stashes, let's document this in manual page so users will know how to\n> use it.\n>\n> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n>  Documentation/git-stash.txt | 27 +++++++++++++++++++++++++++\n>  1 file changed, 27 insertions(+)\n>\n> diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\n> index 6e15f47525..283677314a 100644\n> --- a/Documentation/git-stash.txt\n> +++ b/Documentation/git-stash.txt\n> @@ -20,6 +20,8 @@ SYNOPSIS\n>  'git stash' clear\n>  'git stash' create [<message>]\n>  'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n> +'git stash' export ( --print | --to-ref <ref> ) [<stashes>]\n\nAgain, \"[<stash>...]\", I think.  Do we want SP inside () but no SP\ninside []?  That looks inconsistent.\n\nI.e.\n\n    'git stash' export (--print|--to-ref <ref>) [<stash>...]\n\n\n> +export ( --print | --to-ref <ref> ) [<stashes>]::\n> +\n> +\tExport the specified stashes, or all of them if none are specified, to\n> +\ta chain of commits which can be transferred using the normal fetch and\n> +\tpush mechanisms, then imported using the `import` subcommand.\n\nDitto.\n\n> +import <commit>::\n> +\n> +\tImport the specified stashes from the specified commit, which must have been\n> +\tcreated by `export`, and add them to the list of stashes.  To replace the\n> +\texisting stashes, use `clear` first.\n\nA tangent.\n\nI personally prefer the style to have a blank line after the\n\"<dt>::\" in asciidoc's way of doing description list used here, both\nin these two new entries and many existing ones, by the way.\n\nWe probably should do that consistently, though.\n\n>  OPTIONS\n>  -------\n>  -a::\n> @@ -239,6 +253,19 @@ literally (including newlines and quotes).\n>  +\n>  Quiet, suppress feedback messages.\n>  \n> +--print::\n> +\tThis option is only valid for `export`.\n> ++\n> +Create the chain of commits representing the exported stashes without\n> +storing it anywhere in the ref namespace and print the object ID to\n> +standard output.  This is designed for scripts.\n> +\n> +--to-ref::\n> +\tThis option is only valid for `export`.\n> ++\n> +Create the chain of commits representing the exported stashes and store\n> +it to the specified ref.\n> +\n>  \\--::\n>  \tThis option is only valid for `push` command.\n>  +\n\nThere is one more necessary update after this, no?\n\ndiff --git i/Documentation/git-stash.txt w/Documentation/git-stash.txt\nindex 283677314a..acce92b5d9 100644\n--- i/Documentation/git-stash.txt\n+++ w/Documentation/git-stash.txt\n@@ -283,7 +283,7 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n \n <stash>::\n \tThis option is only valid for `apply`, `branch`, `drop`, `pop`,\n-\t`show` commands.\n+\t`show`, and `export` commands.\n +\n A reference of the form `stash@{<revision>}`. When no `<stash>` is\n given, the latest stash is assumed (that is, `stash@{0}`).\n"},{"id":"451522","messageId":"YjJZPsxP1OwKtzXj@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"xmqqo825n8eh.fsf@gitster.g","subject":"Re: [PATCH 3/6] object-name: make get_oid quietly return an error","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-16T21:40:14Z","receivedAt":"2022-03-16T21:40:22Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-16 at 16:56:22, Junio C Hamano wrote:\n> > diff --git a/object-name.c b/object-name.c\n> > index 92862eeb1a..daa3ef77ef 100644\n> > --- a/object-name.c\n> > +++ b/object-name.c\n> > @@ -911,13 +911,17 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n> >  \t\t\t\t\t\tlen, str,\n> >  \t\t\t\t\t\tshow_date(co_time, co_tz, DATE_MODE(RFC2822)));\n> >  \t\t\t\t}\n> > -\t\t\t} else {\n> > +\t\t\t} else if (!(flags & GET_OID_RETURN_FAILURE)) {\n> >  \t\t\t\tif (flags & GET_OID_QUIETLY) {\n> >  \t\t\t\t\texit(128);\n> >  \t\t\t\t}\n> >  \t\t\t\tdie(_(\"log for '%.*s' only has %d entries\"),\n> >  \t\t\t\t    len, str, co_cnt);\n> >  \t\t\t}\n> > +\t\t\tif (flags & GET_OID_RETURN_FAILURE) {\n> > +\t\t\t\tfree(real_ref);\n> > +\t\t\t\treturn -1;\n> > +\t\t\t}\n> >  \t\t}\n> \n> So, without the new bit, we used to die loudly or quietly.  The new\n> bit allows us to return an error to the caller without dying\n> ourselves.\n> \n> You can call the bit _RETURN_ERROR and not to worry about the\n> right-alignment above ;-), but better yet, how about calling it\n> _GENTLY, which is how we call such a variant of behaviour?\n\nSure, that works.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"451523","messageId":"xmqqr171k1ww.fsf@gitster.g","threadId":"57531","inReplyTo":"xmqq35jhn6ni.fsf@gitster.g","subject":"Re: [PATCH 6/6] doc: add stash export and import to docs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-16T21:44:47Z","receivedAt":"2022-03-16T21:44:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n>\n>> Now that we have an easy way for users to import and export their\n>> stashes, let's document this in manual page so users will know how to\n>> use it.\n\nIt was raised during the review club meeting that it would be easier\nto read if these documentation updates are made in the same commit as\nthe ones that added the feature.\n"},{"id":"451524","messageId":"YjJbJ9ZXlUAd2evC@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"2422376f-79aa-2d35-2646-c3611e2ef8d6@gmail.com","subject":"Re: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-16T21:48:23Z","receivedAt":"2022-03-16T21:48:34Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-14 at 21:19:10, Phillip Wood wrote:\n> Hi Brian and Ævar\n> \n> Firstly I think this is a useful feature to add to git stash, thanks for\n> working on it Brian\n\nThanks.  I'm glad folks other than me will find it useful.\n\n> On 11/03/2022 02:08, Ævar Arnfjörð Bjarmason wrote:\n> > \n> > On Thu, Mar 10 2022, brian m. carlson wrote:\n> > \n> > > +\tsize_t author_len, committer_len;\n> > > +\tstruct commit *this = NULL;\n> > > +\tconst char *orig_author = NULL, *orig_committer = NULL;\n> > > +\tchar *author = NULL, *committer = NULL;\n> > > +\tconst char *buffer = NULL;\n> > > +\tunsigned long bufsize;\n> > > +\tconst char *p;\n> > > +\tchar *msg = NULL;\n> > \n> > These shouldn't be initialized unless they really need to..\n> > \n> > > +\tthis = lookup_commit_reference(the_repository, &info->w_commit);\n> > \n> > ..and some are clobbered right away here, so all of these should not be initializzed.\n\nThis function got hoisted out of what would otherwise be duplicated\ncode, and that's why they're all initialized (because we would otherwise\nhave called free on an uninitialized value).  I can remove the ones that\naren't strictly needed.\n\n> > > +\tbuffer = get_commit_buffer(this, &bufsize);\n> > > +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n> > > +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n> > > +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n> \n> You could start searching from orig_committer rather than buffer but I'm\n> sure it doesn't make any real difference. The sequencer does something\n> similar to this to replay commits when rebasing - is there any scope for\n> sharing code between the two?\n\nI can look into it.  The amount of code that would be duplicated here is\nvery minimal, so I'm okay with just adding a few lines here.\n\n> > ...since by doing so we hide genuine \"uninitialized\"\n> > warnings. E.g. \"author_len\" here isn't initialized, but is set by\n> > find_commit_header(), but if that line was removed we'd warn below, but\n> > not if it's initialized when the variables are declared..\n> > \n> > > +\t\tfor (size_t i = 0;; i++, nitems++) {\n> \n> Do we need i and nitems?\n\nI can look into removing them.\n\n> > > +\t\t\tchar buf[32];\n> > > +\t\t\tint ret;\n> > > +\n> > > +\t\t\tif (nalloc <= i) {\n> > > +\t\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n> > > +\t\t\t\titems = xrealloc(items, new * sizeof(*items));\n> > > +\t\t\t\tnalloc = new;\n> > \n> > Can't we just use the usual ALLOC_GROW() pattern here?\n> ALLOC_GROW_BY() zeros out the memory which would mean we could remove the\n> memset() calls in the loops. I noticed in some other loops we know the size\n> in advance and could use CALLOC_ARRAY().\n\nYeah, I can switch to that.  I was looking for that, but I was thinking\nof a function and not a macro, so I missed it.\n\n> > > +\t\t\t}\n> > > +\t\t\tsnprintf(buf, sizeof(buf), \"%zu\", i);\n> > \n> > Aren't the %z formats unportable (even with our newly found reliance on\n> > more C99)? I vaguely recall trying them recently and the windows CI jobs\n> > erroring...\n> \n> According to [1] it has been available since at least 2015. It is certainly\n> much nicer than casting every size_t to uintmax_t and having to use PRIuMAX.\n\nIf we're relying on a new enough MSVC for C11, then it's much newer than\n2015, so we should be fine.  It's mandatory on POSIX systems.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"451525","messageId":"YjJbsowcAqMhIgDr@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"xmqqa6dpn70b.fsf@gitster.g","subject":"Re: [PATCH 5/6] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-16T21:50:42Z","receivedAt":"2022-03-16T21:50:50Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-16 at 17:26:28, Junio C Hamano wrote:\n> > +static int do_import_stash(const char *rev)\n> > +{\n> > +\tstruct object_id oid;\n> > +\tsize_t nitems = 0, nalloc = 0;\n> > +\tstruct stash_info *items = NULL;\n> > +\tint res = 0;\n> > +\n> > +\tif (get_oid(rev, &oid))\n> > +\t\treturn error(_(\"not a valid revision: %s\"), rev);\n> > +\n> > +\t/*\n> > +\t * Walk the commit history, finding each stash entry, and load data into\n> > +\t * the array.\n> > +\t */\n> > +\tfor (size_t i = 0;; i++, nitems++) {\n> > +\t\tint ret;\n> > +\n> > +\t\tif (nalloc <= i) {\n> > +\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n> > +\t\t\titems = xrealloc(items, new * sizeof(*items));\n> > +\t\t\tnalloc = new;\n> > +\t\t}\n> > +\t\tmemset(&items[i], 0, sizeof(*items));\n> > +\t\t/* We want this to be quiet because it might not exist. */\n> > +\t\tret = get_stash_info_for_import(&items[i], &oid);\n> \n> The new helper function is not necessary; we can use vanilla\n> get_stash_info() on the second parent to get the same information,\n> and we do not really need to keep it in core.  We can sanity check\n> the shape of the imported stash entry right away and discard\n> everything except for the commit object name.\n\nYes, in the new approach, I think we can do that.  We may still need the\nbehavior which doesn't die on error, but I think we can centralize it.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"451622","messageId":"220318.86bky3cr8j.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"YjJbJ9ZXlUAd2evC@camp.crustytoothpaste.net","subject":"C99 %zu support (on MSVC) (was: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref)","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-18T13:34:35Z","receivedAt":"2022-03-18T13:41:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 16 2022, brian m. carlson wrote:\n\n> [[PGP Signed Part:Undecided]]\n> On 2022-03-14 at 21:19:10, Phillip Wood wrote:\n>> Hi Brian and Ævar\n>> \n>> Firstly I think this is a useful feature to add to git stash, thanks for\n>> working on it Brian\n>\n> Thanks.  I'm glad folks other than me will find it useful.\n>\n>> On 11/03/2022 02:08, Ævar Arnfjörð Bjarmason wrote:\n>> > \n>> > On Thu, Mar 10 2022, brian m. carlson wrote:\n>> > \n>> > > +\tsize_t author_len, committer_len;\n>> > > +\tstruct commit *this = NULL;\n>> > > +\tconst char *orig_author = NULL, *orig_committer = NULL;\n>> > > +\tchar *author = NULL, *committer = NULL;\n>> > > +\tconst char *buffer = NULL;\n>> > > +\tunsigned long bufsize;\n>> > > +\tconst char *p;\n>> > > +\tchar *msg = NULL;\n>> > \n>> > These shouldn't be initialized unless they really need to..\n>> > \n>> > > +\tthis = lookup_commit_reference(the_repository, &info->w_commit);\n>> > \n>> > ..and some are clobbered right away here, so all of these should not be initializzed.\n>\n> This function got hoisted out of what would otherwise be duplicated\n> code, and that's why they're all initialized (because we would otherwise\n> have called free on an uninitialized value).  I can remove the ones that\n> aren't strictly needed.\n>\n>> > > +\tbuffer = get_commit_buffer(this, &bufsize);\n>> > > +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n>> > > +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n>> > > +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n>> \n>> You could start searching from orig_committer rather than buffer but I'm\n>> sure it doesn't make any real difference. The sequencer does something\n>> similar to this to replay commits when rebasing - is there any scope for\n>> sharing code between the two?\n>\n> I can look into it.  The amount of code that would be duplicated here is\n> very minimal, so I'm okay with just adding a few lines here.\n>\n>> > ...since by doing so we hide genuine \"uninitialized\"\n>> > warnings. E.g. \"author_len\" here isn't initialized, but is set by\n>> > find_commit_header(), but if that line was removed we'd warn below, but\n>> > not if it's initialized when the variables are declared..\n>> > \n>> > > +\t\tfor (size_t i = 0;; i++, nitems++) {\n>> \n>> Do we need i and nitems?\n>\n> I can look into removing them.\n>\n>> > > +\t\t\tchar buf[32];\n>> > > +\t\t\tint ret;\n>> > > +\n>> > > +\t\t\tif (nalloc <= i) {\n>> > > +\t\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n>> > > +\t\t\t\titems = xrealloc(items, new * sizeof(*items));\n>> > > +\t\t\t\tnalloc = new;\n>> > \n>> > Can't we just use the usual ALLOC_GROW() pattern here?\n>> ALLOC_GROW_BY() zeros out the memory which would mean we could remove the\n>> memset() calls in the loops. I noticed in some other loops we know the size\n>> in advance and could use CALLOC_ARRAY().\n>\n> Yeah, I can switch to that.  I was looking for that, but I was thinking\n> of a function and not a macro, so I missed it.\n>\n>> > > +\t\t\t}\n>> > > +\t\t\tsnprintf(buf, sizeof(buf), \"%zu\", i);\n>> > \n>> > Aren't the %z formats unportable (even with our newly found reliance on\n>> > more C99)? I vaguely recall trying them recently and the windows CI jobs\n>> > erroring...\n>> \n>> According to [1] it has been available since at least 2015. It is certainly\n>> much nicer than casting every size_t to uintmax_t and having to use PRIuMAX.\n>\n> If we're relying on a new enough MSVC for C11, then it's much newer than\n> 2015, so we should be fine.  It's mandatory on POSIX systems.\n\nFWIW I dug into my logs and I ran into it with %zu (not %z), but that's\nwhat you're using.\n\nSorry about being inaccurate, it seems %z's portability isn't the same\nas %z.\n\nI ran into it in mid-2021 in the GitHub CI, but those logs are deleted\nnow (and I didn't re-push that specific OID):\n\n    https://github.com/avar/git/runs/2298653913\n\nWhere it would emit output like:\n\n    builtin/log.c: In function 'gen_message_id':\n    311\n    builtin/log.c:1047:29: error: unknown conversion type character 'z' in format [-Werror=format=]\n    312\n     1047 |  strbuf_addf(&fmt, \"%%s-%%0%zud.%%0%zud-%%s-%%s-%%s\", tmp.len, tmp.len);\n\nThis SO post, whose accuracy I can't verify, claims it is supported in\nVS 2013 or later, and that the way to check for it is with \"_MSC_VER >=\n1800\":\n\n    https://stackoverflow.com/questions/44382862/how-to-printf-a-size-t-without-warning-in-mingw-w64-gcc-7-1\n\nSo if we are going to use it and that's true (which would be great!) it\nwould IMO make sense to introduce it in some prep commit where we delete\ne.g. \"_MSC_VER>=1310\" and other things you'll see in-tree if you look\nthrough:\n\n    git grep _MSC_VER\n\nI.e. to push out some canary commit for using that specific feature, and\nalong with it delete the old MSVC compatibility shims (which presumably\nwe can't use if we're going to hard depend on %zu).\n"},{"id":"451626","messageId":"220318.867d8rcq34.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"2422376f-79aa-2d35-2646-c3611e2ef8d6@gmail.com","subject":"ssize_t portability (was: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref)","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-18T13:41:57Z","receivedAt":"2022-03-18T14:06:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Mar 14 2022, Phillip Wood wrote:\n\n> Hi Brian and Ævar\n> [...]\n\nHi, sorry about the late reply, just on this point (which I see is stil\nunaddressed), and changing $subject for the generic question:\n\n>>> +\tfor (ssize_t i = nitems - 1; i >= 0; i--) {\n>> The ssize_t type can be really small (it's not a signed size_t), so\n>> this\n>> is unportable, but in practice maybe it's OK...\n>\n> I'm not really convinced by this ssize_t can be small argument[2], do\n> you know of any platforms where it is true?\n\nWhere we'd overflow in this code as written? Yes, every platform we\nbuild on, since e.g. on Linux it's got half the unsigned size of\nSIZE_MAX, on my 64 bit box:\n\n    SIZE_MAX  = 18446744073709551615\n    SSIZE_MAX = 9223372036854775807\n\nOf course exceeding 2^63-1 or even 2^31-1 number of stashes seems\nunlikely in practice.\n\nIf you meant are there platforms where ssize_t is as small as you can\npedintically make it, i.e. not just half the signed range of size_t, but\nsomething much smaller?\n\nPossibly, but I think that's unlikely in practice given the\nhomogenization in computing. Even C just mandated 2's compliment!\n\nAlthough I think it's still imporant to understand that in the specific\ncase of ssize_t, unlike other \"signed version of type X\" the *intent* of\nthe standard is clearly to *not* mandate that it's a signed version of\nsize_t. I.e. it's intended for functions like:\n\n     ssize_t read(int fildes, void *buf, size_t nbyte);\n\nWhich both per the standard and in practice might have limits that are\nmuch smaller than their respective types. E.g. on my Linux box read(2)\nsays:\n\n    On Linux, read() (and similar system calls) will transfer at most\n    0x7ffff000 (2,147,479,552) bytes, returning the number of bytes\n    actually transferred.  (This is true on both 32-bit and 64-bit\n    systems.)\n\nI can see how *some* platform might take those liberties in\npractice. I.e. 1/2 of your addressable memory/offset != number of bytes\nyou'd want to return or consider at once for I/O purposes.\n\nBut in any case, we do have existing unportable code in-tree, but\ngenerally with C it's good practice to avoid unportable code if it's\neasy, you never know what platform will be released tomorrow that you\nmight have to scramble to support.\n\nAs I noted in this and other similar cases it's trivial to just use\nsize_t here. It's just a matter of changing (leaving the C99-specifics\nhere aside):\n\n\tfor (i = nitems - 1; i >= 0; i--) {\n\t\titem = ary[i];\n                [...];\n\nTo, as I did in 99d60545f87 (string-list API: change \"nr\" and \"alloc\" to\n\"size_t\", 2022-03-07):\n\n\tfor (i = nitems; i >= 1; i--) {\n\t\titem = ary[i - 1];\n                [...];\n\nOr even, if repeating \"i - 1\" is tedious:\n\n\tfor (cnt = nitems; cnt >= 1; cnt--) {\n\t\tsize_t i = cnt - 1;\n\n\t\titem = ary[i];\n                [...];\n\nPortability concerns aside I think such code is much clearer and easier\nto reason about, since you no longer have to carefully squint to see if\nwe're doing the right thing with the two types in play.\n"},{"id":"451630","messageId":"a67e0fd8-4a14-16c9-9b57-3430440ef93c@gmail.com","threadId":"57531","inReplyTo":"220318.86bky3cr8j.gmgdl@evledraar.gmail.com","subject":"Re: C99 %zu support (on MSVC) (was: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref)","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-03-18T16:26:40Z","receivedAt":"2022-03-18T16:28:39Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 18/03/2022 13:34, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Wed, Mar 16 2022, brian m. carlson wrote:\n> \n>> [[PGP Signed Part:Undecided]]\n>> On 2022-03-14 at 21:19:10, Phillip Wood wrote:\n>>> Hi Brian and Ævar\n>>>\n>>> Firstly I think this is a useful feature to add to git stash, thanks for\n>>> working on it Brian\n>>\n>> Thanks.  I'm glad folks other than me will find it useful.\n>>\n>>> On 11/03/2022 02:08, Ævar Arnfjörð Bjarmason wrote:\n>>>>\n>>>> On Thu, Mar 10 2022, brian m. carlson wrote:\n>>>>\n>>>>> +\tsize_t author_len, committer_len;\n>>>>> +\tstruct commit *this = NULL;\n>>>>> +\tconst char *orig_author = NULL, *orig_committer = NULL;\n>>>>> +\tchar *author = NULL, *committer = NULL;\n>>>>> +\tconst char *buffer = NULL;\n>>>>> +\tunsigned long bufsize;\n>>>>> +\tconst char *p;\n>>>>> +\tchar *msg = NULL;\n>>>>\n>>>> These shouldn't be initialized unless they really need to..\n>>>>\n>>>>> +\tthis = lookup_commit_reference(the_repository, &info->w_commit);\n>>>>\n>>>> ..and some are clobbered right away here, so all of these should not be initializzed.\n>>\n>> This function got hoisted out of what would otherwise be duplicated\n>> code, and that's why they're all initialized (because we would otherwise\n>> have called free on an uninitialized value).  I can remove the ones that\n>> aren't strictly needed.\n>>\n>>>>> +\tbuffer = get_commit_buffer(this, &bufsize);\n>>>>> +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n>>>>> +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n>>>>> +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n>>>\n>>> You could start searching from orig_committer rather than buffer but I'm\n>>> sure it doesn't make any real difference. The sequencer does something\n>>> similar to this to replay commits when rebasing - is there any scope for\n>>> sharing code between the two?\n>>\n>> I can look into it.  The amount of code that would be duplicated here is\n>> very minimal, so I'm okay with just adding a few lines here.\n>>\n>>>> ...since by doing so we hide genuine \"uninitialized\"\n>>>> warnings. E.g. \"author_len\" here isn't initialized, but is set by\n>>>> find_commit_header(), but if that line was removed we'd warn below, but\n>>>> not if it's initialized when the variables are declared..\n>>>>\n>>>>> +\t\tfor (size_t i = 0;; i++, nitems++) {\n>>>\n>>> Do we need i and nitems?\n>>\n>> I can look into removing them.\n>>\n>>>>> +\t\t\tchar buf[32];\n>>>>> +\t\t\tint ret;\n>>>>> +\n>>>>> +\t\t\tif (nalloc <= i) {\n>>>>> +\t\t\t\tsize_t new = nalloc * 3 / 2 + 5;\n>>>>> +\t\t\t\titems = xrealloc(items, new * sizeof(*items));\n>>>>> +\t\t\t\tnalloc = new;\n>>>>\n>>>> Can't we just use the usual ALLOC_GROW() pattern here?\n>>> ALLOC_GROW_BY() zeros out the memory which would mean we could remove the\n>>> memset() calls in the loops. I noticed in some other loops we know the size\n>>> in advance and could use CALLOC_ARRAY().\n>>\n>> Yeah, I can switch to that.  I was looking for that, but I was thinking\n>> of a function and not a macro, so I missed it.\n>>\n>>>>> +\t\t\t}\n>>>>> +\t\t\tsnprintf(buf, sizeof(buf), \"%zu\", i);\n>>>>\n>>>> Aren't the %z formats unportable (even with our newly found reliance on\n>>>> more C99)? I vaguely recall trying them recently and the windows CI jobs\n>>>> erroring...\n>>>\n>>> According to [1] it has been available since at least 2015. It is certainly\n>>> much nicer than casting every size_t to uintmax_t and having to use PRIuMAX.\n>>\n>> If we're relying on a new enough MSVC for C11, then it's much newer than\n>> 2015, so we should be fine.  It's mandatory on POSIX systems.\n> \n> FWIW I dug into my logs and I ran into it with %zu (not %z), but that's\n> what you're using.\n> \n> Sorry about being inaccurate, it seems %z's portability isn't the same\n> as %z.\n> \n> I ran into it in mid-2021 in the GitHub CI, but those logs are deleted\n> now (and I didn't re-push that specific OID):\n> \n>      https://github.com/avar/git/runs/2298653913\n> \n> Where it would emit output like:\n> \n>      builtin/log.c: In function 'gen_message_id':\n>      311\n>      builtin/log.c:1047:29: error: unknown conversion type character 'z' in format [-Werror=format=]\n>      312\n>       1047 |  strbuf_addf(&fmt, \"%%s-%%0%zud.%%0%zud-%%s-%%s-%%s\", tmp.len, tmp.len);\n> \n> This SO post, whose accuracy I can't verify, claims it is supported in\n> VS 2013 or later, and that the way to check for it is with \"_MSC_VER >=\n> 1800\":\n> \n>      https://stackoverflow.com/questions/44382862/how-to-printf-a-size-t-without-warning-in-mingw-w64-gcc-7-1\n\nOh, so it is mingw that is the problem, not MSVC. Indeed using \"%zu\" \n(see the diff below) fails for the \"win build\" job[1] but the \"win+VS \nbuild\" succeeds[2]. mingw config.mak.uname to uses \n-D__MINGW_USE_ANSI_STDIO=0. Even if we could change that to =1 it would \nbe insufficient as it does not affect the format specifiers allowed by \n__attribute__((format (printf, 3, 4))). I think to do that we would have \nto change \"printf\" to \"__MINGW_PRINTF_FORMAT\" for each attribute \ndeclaration[3].\n\nIn short unfortunately I don't think we can easily use \"%zu\"\n\nBest wishes\n\nPhillip\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex e1ab39cce3..1790ad6359 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -193,9 +193,8 @@ static ssize_t find_unique(const char *string, \nstruct prefix_item_list *list)\n         struct string_list_item *item;\n\n         if (list->items.nr != list->sorted.nr)\n-               BUG(\"prefix_item_list in inconsistent state (%\"PRIuMAX\n-                   \" vs %\"PRIuMAX\")\",\n-                   (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);\n+               BUG(\"prefix_item_list in inconsistent state (%zu vs %zu)\",\n+                   list->items.nr, list->sorted.nr);\n\n         if (index < 0)\n                 item = list->sorted.items[-1 - index].util;\n\n[1] \nhttps://github.com/phillipwood/git/runs/5601840748?check_suite_focus=true\n[2] \nhttps://github.com/phillipwood/git/runs/5601840528?check_suite_focus=true\n[3] https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf\n\n> So if we are going to use it and that's true (which would be great!) it\n> would IMO make sense to introduce it in some prep commit where we delete\n> e.g. \"_MSC_VER>=1310\" and other things you'll see in-tree if you look\n> through:\n> \n>      git grep _MSC_VER\n> \n> I.e. to push out some canary commit for using that specific feature, and\n> along with it delete the old MSVC compatibility shims (which presumably\n> we can't use if we're going to hard depend on %zu).\n"},{"id":"452117","messageId":"nycvar.QRO.7.76.6.2203241456250.388@tvgsbejvaqbjf.bet","threadId":"57531","inReplyTo":"YjJbJ9ZXlUAd2evC@camp.crustytoothpaste.net","subject":"Re: [PATCH 4/6] builtin/stash: provide a way to export stashes to a ref","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-03-24T14:02:39Z","receivedAt":"2022-03-24T14:03:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi brian,\n\nOn Wed, 16 Mar 2022, brian m. carlson wrote:\n\n> On 2022-03-14 at 21:19:10, Phillip Wood wrote:\n>\n> > On 11/03/2022 02:08, Ævar Arnfjörð Bjarmason wrote:\n> > >\n> > > On Thu, Mar 10 2022, brian m. carlson wrote:\n> > >\n> > > > +\t\t\t}\n> > > > +\t\t\tsnprintf(buf, sizeof(buf), \"%zu\", i);\n> > >\n> > > Aren't the %z formats unportable (even with our newly found reliance on\n> > > more C99)? I vaguely recall trying them recently and the windows CI jobs\n> > > erroring...\n> >\n> > According to [1] it has been available since at least 2015. It is certainly\n> > much nicer than casting every size_t to uintmax_t and having to use PRIuMAX.\n>\n> If we're relying on a new enough MSVC for C11, then it's much newer than\n> 2015, so we should be fine.  It's mandatory on POSIX systems.\n\nThe MSVCRT we're using in GCC is much, much older, and that won't change\nanytime soon, I don't think.\n\nYou _might_ get the code to compile a `%zu` format by playing some macro\ntricks, but executing the result would still not work.\n\nIt sure would be nice if we could use all that POSIX promises... but we\ncan't. Sorry...\n\nCiao,\nDscho\n"},{"id":"452609","messageId":"20220329214941.2018609-3-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220329214941.2018609-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 2/4] builtin/stash: factor out revision parsing into a function","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-29T21:49:39Z","receivedAt":"2022-03-29T21:49:56Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"We allow several special forms of stash names in this code.  In the\nfuture, we'll want to allow these same forms without parsing a stash\ncommit, so let's refactor this code out into a function for reuse.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n builtin/stash.c | 34 +++++++++++++++++++++-------------\n 1 file changed, 21 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 5897febfbe..4c281a5781 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -130,6 +130,24 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n \t\tdie(_(\"'%s' is not a stash-like commit\"), revision);\n }\n \n+static int parse_revision(struct strbuf *revision, const char *commit, int quiet)\n+{\n+\tstrbuf_init(revision, 0);\n+\tif (!commit) {\n+\t\tif (!ref_exists(ref_stash)) {\n+\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tstrbuf_addf(revision, \"%s@{0}\", ref_stash);\n+\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n+\t\tstrbuf_addf(revision, \"%s@{%s}\", ref_stash, commit);\n+\t} else {\n+\t\tstrbuf_addstr(revision, commit);\n+\t}\n+\treturn 0;\n+}\n+\n static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n {\n \tint ret;\n@@ -157,19 +175,9 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n \tif (argc == 1)\n \t\tcommit = argv[0];\n \n-\tstrbuf_init(&info->revision, 0);\n-\tif (!commit) {\n-\t\tif (!ref_exists(ref_stash)) {\n-\t\t\tfree_stash_info(info);\n-\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\tstrbuf_addf(&info->revision, \"%s@{0}\", ref_stash);\n-\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n-\t\tstrbuf_addf(&info->revision, \"%s@{%s}\", ref_stash, commit);\n-\t} else {\n-\t\tstrbuf_addstr(&info->revision, commit);\n+\tif (parse_revision(&info->revision, commit, 0)) {\n+\t\tfree_stash_info(info);\n+\t\treturn -1;\n \t}\n \n \trevision = info->revision.buf;\n"},{"id":"452610","messageId":"20220329214941.2018609-2-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220329214941.2018609-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 1/4] object-name: make get_oid quietly return an error","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-29T21:49:38Z","receivedAt":"2022-03-29T21:49:58Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A reasonable person looking at the signature and usage of get_oid and\nfriends might conclude that in the event of an error, it always returns\n-1.  However, this is not the case.  Instead, get_oid_basic dies if we\ngo too far back into the history of a reflog (or, when quiet, simply\nexits).\n\nThis is not especially useful, since in many cases, we might want to\nhandle this error differently.  Let's add a flag here to make it just\nreturn -1 like elsewhere in these code paths.\n\nNote that we cannot make this behavior the default, since we have many\nother codepaths that rely on the existing behavior, including in tests.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n cache.h       | 1 +\n object-name.c | 6 +++++-\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/cache.h b/cache.h\nindex 825ec17198..657e3ff17f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1376,6 +1376,7 @@ struct object_context {\n #define GET_OID_RECORD_PATH     0200\n #define GET_OID_ONLY_TO_DIE    04000\n #define GET_OID_REQUIRE_PATH  010000\n+#define GET_OID_GENTLY        020000\n \n #define GET_OID_DISAMBIGUATORS \\\n \t(GET_OID_COMMIT | GET_OID_COMMITTISH | \\\ndiff --git a/object-name.c b/object-name.c\nindex 92862eeb1a..46dbfe36a6 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -911,13 +911,17 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\t\tlen, str,\n \t\t\t\t\t\tshow_date(co_time, co_tz, DATE_MODE(RFC2822)));\n \t\t\t\t}\n-\t\t\t} else {\n+\t\t\t} else if (!(flags & GET_OID_GENTLY)) {\n \t\t\t\tif (flags & GET_OID_QUIETLY) {\n \t\t\t\t\texit(128);\n \t\t\t\t}\n \t\t\t\tdie(_(\"log for '%.*s' only has %d entries\"),\n \t\t\t\t    len, str, co_cnt);\n \t\t\t}\n+\t\t\tif (flags & GET_OID_GENTLY) {\n+\t\t\t\tfree(real_ref);\n+\t\t\t\treturn -1;\n+\t\t\t}\n \t\t}\n \t}\n \n"},{"id":"452611","messageId":"20220329214941.2018609-1-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 0/4] Importing and exporting stashes to refs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-29T21:49:37Z","receivedAt":"2022-03-29T21:50:00Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Stashes are currently stored using the reflog in a given repository.\nThis is an interesting and novel way to handle them, but there is no way\nto easily move a stash across machines.  For example, stashes cannot be\nbundled, pushed, or fetched.\n\nLet's solve this problem by allowing users to import and export stashes\nto a chain of commits.  The commits used in a stash export contain two\nparents: one which is the pointer to the next exported stash (or to an\nempty commit with no parents if there are no more) and the second is the\nstash commit that would normally be stored in the reflog.\n\nWe now produce completely reproducible exports by creating the base\ncommit with predictable author and committer names and timestamps.\nThe timestamp is arbitrary but picked to be the same as the one used by\nformat-patch.\n\nWe also now avoid size_t.  Because of the constraints of portability, we\ncan't have nice things like %zu, so now we use int.  Parties wanting to\nimport or export more than 2 billion stashes are welcome to send a patch\nimproving the situation.  While I have many stashes, I'm at least a\nbillion or so away from that point, so I'm going to punt that to a\nfuture series.\n\nChanges from v1:\n* Change storage format as suggested by Junio.\n* Rename to GIT_OID_GENTLY.\n* Remove unnecessary initializations.\n* Use ALLOC_GROW_BY.\n* Ensure completely reproducible exports.\n* Avoid size_t.\n* Various other code cleanups.\n\nbrian m. carlson (4):\n  object-name: make get_oid quietly return an error\n  builtin/stash: factor out revision parsing into a function\n  builtin/stash: provide a way to export stashes to a ref\n  builtin/stash: provide a way to import stashes from a ref\n\n Documentation/git-stash.txt |  29 +++-\n builtin/stash.c             | 324 ++++++++++++++++++++++++++++++++++--\n cache.h                     |   1 +\n object-name.c               |   6 +-\n t/t3903-stash.sh            |  52 ++++++\n 5 files changed, 397 insertions(+), 15 deletions(-)\n\n"},{"id":"452612","messageId":"20220329214941.2018609-5-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220329214941.2018609-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-29T21:49:41Z","receivedAt":"2022-03-29T21:50:02Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Now that we have a way to export stashes to a ref, let's provide a way\nto import them from such a ref back to the stash.  This works much the\nway the export code does, except that we strip off the first parent\nchain commit and then store each resulting commit back to the stash.\n\nWe don't clear the stash first and instead add the specified stashes to\nthe top of the stash.  This is because users may want to export just a\nfew stashes, such as to share a small amount of work in progress with a\ncolleague, and it would be undesirable for the receiving user to lose\nall of their data.  For users who do want to replace the stash, it's\neasy to do to: simply run \"git stash clear\" first.\n\nWe specifically rely on the fact that we'll produce identical stash\ncommits on both sides in our tests.  This provides a cheap,\nstraightforward check for our tests and also makes it easy for users to\nsee if they already have the same data in both repositories.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-stash.txt |   7 +++\n builtin/stash.c             | 107 ++++++++++++++++++++++++++++++++++++\n t/t3903-stash.sh            |  52 ++++++++++++++++++\n 3 files changed, 166 insertions(+)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 162110314e..28eb9cab0c 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -21,6 +21,7 @@ SYNOPSIS\n 'git stash' create [<message>]\n 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n 'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n+'git stash' import <commit>\n \n DESCRIPTION\n -----------\n@@ -158,6 +159,12 @@ export ( --print | --to-ref <ref> ) [<stash>...]::\n \ta chain of commits which can be transferred using the normal fetch and\n \tpush mechanisms, then imported using the `import` subcommand.\n \n+import <commit>::\n+\n+\tImport the specified stashes from the specified commit, which must have been\n+\tcreated by `export`, and add them to the list of stashes.  To replace the\n+\texisting stashes, use `clear` first.\n+\n OPTIONS\n -------\n -a::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 6f1fa19172..4b198f1ab7 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -34,6 +34,7 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n+\tN_(\"git stash import <commit>\"),\n \tNULL\n };\n \n@@ -95,6 +96,10 @@ static const char * const git_stash_export_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_import_usage[] = {\n+\tN_(\"git stash import <commit>\"),\n+\tNULL\n+};\n \n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n@@ -104,6 +109,7 @@ static struct strbuf stash_index_path = STRBUF_INIT;\n  * b_commit is set to the base commit\n  * i_commit is set to the commit containing the index tree\n  * u_commit is set to the commit containing the untracked files tree\n+ * c_commit is set to the first parent (chain commit) when importing and is otherwise unset\n  * w_tree is set to the working tree\n  * b_tree is set to the base tree\n  * i_tree is set to the index tree\n@@ -114,6 +120,7 @@ struct stash_info {\n \tstruct object_id b_commit;\n \tstruct object_id i_commit;\n \tstruct object_id u_commit;\n+\tstruct object_id c_commit;\n \tstruct object_id w_tree;\n \tstruct object_id b_tree;\n \tstruct object_id i_tree;\n@@ -1826,6 +1833,104 @@ static int write_commit_with_parents(struct object_id *out, const struct object_\n \treturn ret;\n }\n \n+static int do_import_stash(const char *rev)\n+{\n+\tstruct object_id chain;\n+\tsize_t nalloc = 0;\n+\tstruct object_id *items = NULL;\n+\tint nitems = 0;\n+\tint res = 0;\n+\tconst char *buffer = NULL;\n+\tstruct commit *this = NULL;\n+\tchar *msg = NULL;\n+\n+\tif (get_oid(rev, &chain))\n+\t\treturn error(_(\"not a valid revision: %s\"), rev);\n+\n+\t/*\n+\t * Walk the commit history, finding each stash entry, and load data into\n+\t * the array.\n+\t */\n+\tfor (int i = 0;; i++) {\n+\t\tstruct object_id tree, oid;\n+\t\tchar revision[GIT_MAX_HEXSZ + 1];\n+\n+\t\toid_to_hex_r(revision, &chain);\n+\n+\t\tif (get_oidf(&tree, \"%s:\", revision) ||\n+\t\t    !oideq(&tree, the_hash_algo->empty_tree)) {\n+\t\t\treturn error(_(\"%s is not a valid exported stash commit\"), revision);\n+\t\t}\n+\t\tif (get_oidf(&chain, \"%s^1\", revision) ||\n+\t\t    get_oidf(&oid, \"%s^2\", revision))\n+\t\t\tbreak;\n+\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\toidcpy(&items[i], &oid);\n+\t}\n+\n+\t/*\n+\t * Now, walk each entry, adding it to the stash as a normal stash\n+\t * commit.\n+\t */\n+\tfor (int i = nitems - 1; i >= 0; i--) {\n+\t\tunsigned long bufsize;\n+\t\tconst char *p;\n+\n+\t\tthis = lookup_commit_reference(the_repository, &items[i]);\n+\t\tbuffer = get_commit_buffer(this, &bufsize);\n+\t\tif (!buffer) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot read commit buffer for %s\"), oid_to_hex(&items[i]));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n+\t\tif (!p) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(&items[i]));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tp += 2;\n+\t\tmsg = xmemdupz(p, bufsize - (p - buffer));\n+\t\tunuse_commit_buffer(this, buffer);\n+\t\tbuffer = NULL;\n+\n+\t\tif (do_store_stash(&items[i], msg, 1)) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot save the stash for %s\"), oid_to_hex(&items[i]));\n+\t\t\tgoto out;\n+\t\t}\n+\t\tFREE_AND_NULL(msg);\n+\t}\n+out:\n+\tif (this && buffer)\n+\t\tunuse_commit_buffer(this, buffer);\n+\tfree(items);\n+\tfree(msg);\n+\n+\treturn res;\n+}\n+\n+static int import_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tint ret = 0;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_import_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (argc != 1)\n+\t\treturn error(_(\"a revision to import from is required\"));\n+\n+\n+\tret = do_import_stash(argv[0]);\n+\treturn ret;\n+}\n+\n static int do_export_stash(const char *ref, size_t argc, const char **argv)\n {\n \tstruct object_id base;\n@@ -1999,6 +2104,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!save_stash(argc, argv, prefix);\n \telse if (!strcmp(argv[0], \"export\"))\n \t\treturn !!export_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"import\"))\n+\t\treturn !!import_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex b149e2af44..d2ddede9be 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1295,6 +1295,58 @@ test_expect_success 'stash --keep-index with file deleted in index does not resu\n \ttest_path_is_missing to-remove\n '\n \n+test_expect_success 'stash export and import round-trip stashes' '\n+\tgit reset &&\n+\t>untracked &&\n+\t>tracked1 &&\n+\t>tracked2 &&\n+\tgit add tracked* &&\n+\tgit stash -- &&\n+\t>subdir/untracked &&\n+\t>subdir/tracked1 &&\n+\t>subdir/tracked2 &&\n+\tgit add subdir/tracked* &&\n+\tgit stash -- subdir/ &&\n+\tstash0=$(git rev-parse --verify stash@{0}) &&\n+\tstash1=$(git rev-parse --verify stash@{1}) &&\n+\tsimple=$(git stash export --print) &&\n+\tgit stash clear &&\n+\tgit stash import \"$simple\" &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported0\" = \"$stash0\" &&\n+\ttest \"$imported1\" = \"$stash1\" &&\n+\tgit stash export --to-ref refs/heads/foo &&\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported0\" = \"$stash0\" &&\n+\ttest \"$imported1\" = \"$stash1\"\n+'\n+\n+test_expect_success 'stash import appends commits' '\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\techo $(cat actual | wc -l) >count &&\n+\tgit stash import refs/heads/foo &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = $(($(cat count) * 2)) actual\n+'\n+\n+test_expect_success 'stash export can accept specified stashes' '\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\tgit stash export --to-ref bar stash@{1} stash@{0} &&\n+\tgit stash clear &&\n+\tgit stash import bar &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported1\" = \"$stash0\" &&\n+\ttest \"$imported0\" = \"$stash1\" &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = 2 actual\n+'\n+\n test_expect_success 'stash apply should succeed with unmodified file' '\n \techo base >file &&\n \tgit add file &&\n"},{"id":"452613","messageId":"20220329214941.2018609-4-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220329214941.2018609-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-29T21:49:40Z","receivedAt":"2022-03-29T21:50:04Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A common user problem is how to sync in-progress work to another\nmachine.  Users currently must use some sort of transfer of the working\ntree, which poses security risks and also necessarily causes the index\nto become dirty.  The experience is suboptimal and frustrating for\nusers.\n\nA reasonable idea is to use the stash for this purpose, but the stash is\nstored in the reflog, not in a ref, and as such it cannot be pushed or\npulled.  This also means that it cannot be saved into a bundle or\npreserved elsewhere, which is a problem when using throwaway development\nenvironments.\n\nLet's solve this problem by allowing the user to export the stash to a\nref (or, to just write it into the repository and print the hash, à la\ngit commit-tree).  Introduce git stash export, which writes a chain of\ncommits where the first parent is always a chain to the previous stash,\nor to a single, empty commit (for the final item) and the second is the\nstash commit normally written to the reflog.\n\nIterate over each stash from topmost to bottomost, looking up the data\nfor each one, and then create the chain from the single empty commit\nback up in reverse order.  Generate a predictable empty commit so our\nbehavior is reproducible.  Create a useful commit message, preserving\nthe author and committer information, to help users identify stash\ncommits when viewing them as normal commits.\n\nIf the user has specified specific stashes they'd like to export\ninstead, use those instead of iterating over all of the stashes.\n\nAs part of this, specifically request quiet behavior when looking up the\nOID for a revision because we will eventually hit a revision that\ndoesn't exist and we don't want to die when that occurs.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-stash.txt |  22 ++++-\n builtin/stash.c             | 183 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 204 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 6e15f47525..162110314e 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -20,6 +20,7 @@ SYNOPSIS\n 'git stash' clear\n 'git stash' create [<message>]\n 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n+'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n \n DESCRIPTION\n -----------\n@@ -151,6 +152,12 @@ store::\n \treflog.  This is intended to be useful for scripts.  It is\n \tprobably not the command you want to use; see \"push\" above.\n \n+export ( --print | --to-ref <ref> ) [<stash>...]::\n+\n+\tExport the specified stashes, or all of them if none are specified, to\n+\ta chain of commits which can be transferred using the normal fetch and\n+\tpush mechanisms, then imported using the `import` subcommand.\n+\n OPTIONS\n -------\n -a::\n@@ -239,6 +246,19 @@ literally (including newlines and quotes).\n +\n Quiet, suppress feedback messages.\n \n+--print::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes without\n+storing it anywhere in the ref namespace and print the object ID to\n+standard output.  This is designed for scripts.\n+\n+--to-ref::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes and store\n+it to the specified ref.\n+\n \\--::\n \tThis option is only valid for `push` command.\n +\n@@ -256,7 +276,7 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n \n <stash>::\n \tThis option is only valid for `apply`, `branch`, `drop`, `pop`,\n-\t`show` commands.\n+\t`show`, and `export` commands.\n +\n A reference of the form `stash@{<revision>}`. When no `<stash>` is\n given, the latest stash is assumed (that is, `stash@{0}`).\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 4c281a5781..6f1fa19172 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -33,6 +33,7 @@ static const char * const git_stash_usage[] = {\n \t   \"          [--] [<pathspec>...]]\"),\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n \tNULL\n };\n \n@@ -89,6 +90,12 @@ static const char * const git_stash_save_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_export_usage[] = {\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n+\tNULL\n+};\n+\n+\n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n \n@@ -1773,6 +1780,180 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \treturn ret;\n }\n \n+static int write_commit_with_parents(struct object_id *out, const struct object_id *oid, struct commit_list *parents)\n+{\n+\tsize_t author_len, committer_len;\n+\tstruct commit *this;\n+\tconst char *orig_author, *orig_committer;\n+\tchar *author = NULL, *committer = NULL;\n+\tconst char *buffer;\n+\tunsigned long bufsize;\n+\tconst char *p;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tthis = lookup_commit_reference(the_repository, oid);\n+\tbuffer = get_commit_buffer(this, &bufsize);\n+\torig_author = find_commit_header(buffer, \"author\", &author_len);\n+\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n+\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n+\n+\tif (!orig_author || !orig_committer || !p) {\n+\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n+\t\tgoto out;\n+\t}\n+\t/* Jump to message. */\n+\tp += 2;\n+\tstrbuf_addstr(&msg, \"git stash: \");\n+\tstrbuf_add(&msg, p, bufsize - (p - buffer));\n+\n+\tauthor = xmemdupz(orig_author, author_len);\n+\tcommitter = xmemdupz(orig_committer, committer_len);\n+\n+\tif (commit_tree_extended(msg.buf, msg.len,\n+\t\t\t\t the_hash_algo->empty_tree, parents,\n+\t\t\t\t out, author, committer,\n+\t\t\t\t NULL, NULL)) {\n+\t\tret = -1;\n+\t\terror(_(\"could not write commit\"));\n+\t\tgoto out;\n+\t}\n+out:\n+\tstrbuf_reset(&msg);\n+\tunuse_commit_buffer(this, buffer);\n+\tfree(author);\n+\tfree(committer);\n+\treturn ret;\n+}\n+\n+static int do_export_stash(const char *ref, size_t argc, const char **argv)\n+{\n+\tstruct object_id base;\n+\tstruct object_context unused;\n+\tstruct commit *prev;\n+\tstruct object_id *items = NULL;\n+\tint nitems = 0, nalloc = 0;\n+\tint res = 0;\n+\tstruct strbuf revision;\n+\tconst char *author, *committer;\n+\n+\t/*\n+\t * This is an arbitrary, fixed date, specifically the one used by git\n+\t * format-patch.  The goal is merely to produce reproducible output.\n+\t */\n+\tprepare_fallback_ident(\"git stash\", \"git@stash\");\n+\tauthor = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n+\tcommitter = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n+\n+\t/* First, we create a single empty commit. */\n+\tif (commit_tree_extended(NULL, 0, the_hash_algo->empty_tree, NULL, &base, author, committer, NULL, NULL))\n+\t\treturn error(_(\"unable to write base commit\"));\n+\n+\tprev = lookup_commit_reference(the_repository, &base);\n+\n+\tif (argc) {\n+\t\t/*\n+\t\t * Find each specified stash, and load data into the array.\n+\t\t */\n+\t\tfor (int i = 0; i < argc; i++) {\n+\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\t\tif (parse_revision(&revision, argv[i], 0) ||\n+\t\t\t    get_oid_with_context(the_repository, revision.buf,\n+\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n+\t\t\t\t\t\t &items[i], &unused)) {\n+\t\t\t\terror(_(\"unable to find stash entry %s\"), argv[i]);\n+\t\t\t\tres = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * Walk the reflog, finding each stash entry, and load data into the\n+\t\t * array.\n+\t\t */\n+\t\tfor (int i = 0;; i++) {\n+\t\t\tchar buf[32];\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tsnprintf(buf, sizeof(buf), \"%d\", i);\n+\t\t\tif (parse_revision(&revision, buf, 1) ||\n+\t\t\t    get_oid_with_context(the_repository, revision.buf,\n+\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n+\t\t\t\t\t\t &oid, &unused))\n+\t\t\t\tbreak;\n+\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\t\toidcpy(&items[i], &oid);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Now, create a set of commits identical to the regular stash commits,\n+\t * but where their first parents form a chain to our original empty\n+\t * base commit.\n+\t */\n+\tfor (int i = nitems - 1; i >= 0; i--) {\n+\t\tstruct commit_list *parents = NULL;\n+\t\tstruct commit_list **next = &parents;\n+\t\tstruct object_id out;\n+\n+\t\tnext = commit_list_append(prev, next);\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i]), next);\n+\t\tif (write_commit_with_parents(&out, &items[i], parents)) {\n+\t\t\tres = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tprev = lookup_commit_reference(the_repository, &out);\n+\t}\n+\tif (ref)\n+\t\tupdate_ref(NULL, ref, &prev->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n+\telse\n+\t\tputs(oid_to_hex(&prev->object.oid));\n+out:\n+\tfree(items);\n+\n+\treturn res;\n+}\n+\n+enum export_action {\n+\tACTION_NONE,\n+\tACTION_PRINT,\n+\tACTION_TO_REF,\n+};\n+\n+static int export_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tint ret = 0;\n+\tconst char *ref = NULL;\n+\tenum export_action action = ACTION_NONE;\n+\tstruct option options[] = {\n+\t\tOPT_CMDMODE(0, \"print\", &action,\n+\t\t\t    N_(\"print the object ID instead of writing it to a ref\"),\n+\t\t\t    ACTION_PRINT),\n+\t\tOPT_CMDMODE(0, \"to-ref\", &action,\n+\t\t\t    N_(\"save the data to the given ref\"),\n+\t\t\t    ACTION_TO_REF),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_export_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (action == ACTION_NONE) {\n+\t\treturn error(_(\"exactly one of --print and --to-ref is required\"));\n+\t} else if (action == ACTION_TO_REF) {\n+\t\tif (!argc)\n+\t\t\treturn error(_(\"--to-ref requires an argument\"));\n+\t\tref = argv[0];\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n+\n+\tret = do_export_stash(ref, argc, argv);\n+\treturn ret;\n+}\n+\n int cmd_stash(int argc, const char **argv, const char *prefix)\n {\n \tpid_t pid = getpid();\n@@ -1816,6 +1997,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!push_stash(argc, argv, prefix, 0);\n \telse if (!strcmp(argv[0], \"save\"))\n \t\treturn !!save_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"export\"))\n+\t\treturn !!export_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\n"},{"id":"452721","messageId":"xmqqtubf59dz.fsf@gitster.g","threadId":"57531","inReplyTo":"20220329214941.2018609-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-30T23:05:28Z","receivedAt":"2022-03-30T23:05:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> +static int do_export_stash(const char *ref, size_t argc, const char **argv)\n> +{\n> +\tstruct object_id base;\n> +\tstruct object_context unused;\n> +\tstruct commit *prev;\n> +\tstruct object_id *items = NULL;\n> +\tint nitems = 0, nalloc = 0;\n> +\tint res = 0;\n> +\tstruct strbuf revision;\n> +\tconst char *author, *committer;\n> +\n> +\t/*\n> +\t * This is an arbitrary, fixed date, specifically the one used by git\n> +\t * format-patch.  The goal is merely to produce reproducible output.\n> +\t */\n> +\tprepare_fallback_ident(\"git stash\", \"git@stash\");\n> +\tauthor = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n> +\tcommitter = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n> +\n> +\t/* First, we create a single empty commit. */\n> +\tif (commit_tree_extended(NULL, 0, the_hash_algo->empty_tree, NULL, &base, author, committer, NULL, NULL))\n> +\t\treturn error(_(\"unable to write base commit\"));\n> +\n> +\tprev = lookup_commit_reference(the_repository, &base);\n> +\n> +\tif (argc) {\n> +\t\t/*\n> +\t\t * Find each specified stash, and load data into the array.\n> +\t\t */\n> +\t\tfor (int i = 0; i < argc; i++) {\n> +\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n\nDocumentation/CodingGuidelines still says this\n\n - Declaring a variable in the for loop \"for (int i = 0; i < 10; i++)\"\n   is still not allowed in this codebase.\n\nWe have been experimenting since we merged 44ba10d6 (revision: use\nC99 declaration of variable in for() loop, 2021-11-14) at 5a4069a1\n(Merge branch 'jc/c99-var-decl-in-for-loop', 2021-12-21), which is\nshipped as a part of v2.35.0 just a few months ago.\n\nLet's not add more until we are sure that we do not have to revert\n44ba10d6 (revision: use C99 declaration of variable in for() loop,\n2021-11-14) to limit the potential damage, especially when it is so\neasy to do so.\n\nJust declare \"int i\" at the beginning of the funcion and keep\nreusing it would be fine here.\n"},{"id":"452724","messageId":"YkTrWPmIaBzl5SA5@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"xmqqtubf59dz.fsf@gitster.g","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-03-30T23:44:24Z","receivedAt":"2022-03-30T23:44:29Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-30 at 23:05:28, Junio C Hamano wrote:\n> Documentation/CodingGuidelines still says this\n> \n>  - Declaring a variable in the for loop \"for (int i = 0; i < 10; i++)\"\n>    is still not allowed in this codebase.\n> \n> We have been experimenting since we merged 44ba10d6 (revision: use\n> C99 declaration of variable in for() loop, 2021-11-14) at 5a4069a1\n> (Merge branch 'jc/c99-var-decl-in-for-loop', 2021-12-21), which is\n> shipped as a part of v2.35.0 just a few months ago.\n> \n> Let's not add more until we are sure that we do not have to revert\n> 44ba10d6 (revision: use C99 declaration of variable in for() loop,\n> 2021-11-14) to limit the potential damage, especially when it is so\n> easy to do so.\n> \n> Just declare \"int i\" at the beginning of the funcion and keep\n> reusing it would be fine here.\n\nOkay, I can reroll with that.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"452762","messageId":"xmqqo81m51ux.fsf@gitster.g","threadId":"57531","inReplyTo":"20220329214941.2018609-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 0/4] Importing and exporting stashes to refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-31T01:48:06Z","receivedAt":"2022-03-31T01:48:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> brian m. carlson (4):\n>   object-name: make get_oid quietly return an error\n>   builtin/stash: factor out revision parsing into a function\n>   builtin/stash: provide a way to export stashes to a ref\n>   builtin/stash: provide a way to import stashes from a ref\n>\n>  Documentation/git-stash.txt |  29 +++-\n>  builtin/stash.c             | 324 ++++++++++++++++++++++++++++++++++--\n>  cache.h                     |   1 +\n>  object-name.c               |   6 +-\n>  t/t3903-stash.sh            |  52 ++++++\n>  5 files changed, 397 insertions(+), 15 deletions(-)\n\nMerged to 'seen', t3903 seems to have a handful of breakages.\ne.g. https://github.com/git/git/runs/5763421702?check_suite_focus=true\n\nThanks.\n"},{"id":"452764","messageId":"220331.86pmm2swm9.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220329214941.2018609-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-31T01:56:01Z","receivedAt":"2022-03-31T02:07:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 29 2022, brian m. carlson wrote:\n\n> A common user problem is how to sync in-progress work to another\n> machine.  Users currently must use some sort of transfer of the working\n> tree, which poses security risks and also necessarily causes the index\n> to become dirty.  The experience is suboptimal and frustrating for\n> users.\n>\n> A reasonable idea is to use the stash for this purpose, but the stash is\n> stored in the reflog, not in a ref, and as such it cannot be pushed or\n> pulled.  This also means that it cannot be saved into a bundle or\n> preserved elsewhere, which is a problem when using throwaway development\n> environments.\n>\n> Let's solve this problem by allowing the user to export the stash to a\n> ref (or, to just write it into the repository and print the hash, à la\n> git commit-tree).  Introduce git stash export, which writes a chain of\n> commits where the first parent is always a chain to the previous stash,\n> or to a single, empty commit (for the final item) and the second is the\n> stash commit normally written to the reflog.\n>\n> Iterate over each stash from topmost to bottomost, looking up the data\n> for each one, and then create the chain from the single empty commit\n> back up in reverse order.  Generate a predictable empty commit so our\n> behavior is reproducible.  Create a useful commit message, preserving\n> the author and committer information, to help users identify stash\n> commits when viewing them as normal commits.\n>\n> If the user has specified specific stashes they'd like to export\n> instead, use those instead of iterating over all of the stashes.\n>\n> As part of this, specifically request quiet behavior when looking up the\n> OID for a revision because we will eventually hit a revision that\n> doesn't exist and we don't want to die when that occurs.\n>\n> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n>  Documentation/git-stash.txt |  22 ++++-\n>  builtin/stash.c             | 183 ++++++++++++++++++++++++++++++++++++\n>  2 files changed, 204 insertions(+), 1 deletion(-)\n>\n> diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\n> index 6e15f47525..162110314e 100644\n> --- a/Documentation/git-stash.txt\n> +++ b/Documentation/git-stash.txt\n> @@ -20,6 +20,7 @@ SYNOPSIS\n>  'git stash' clear\n>  'git stash' create [<message>]\n>  'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n> +'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -151,6 +152,12 @@ store::\n>  \treflog.  This is intended to be useful for scripts.  It is\n>  \tprobably not the command you want to use; see \"push\" above.\n>  \n> +export ( --print | --to-ref <ref> ) [<stash>...]::\n> +\n\nI think this extra \\n here isn't needed.\n\n> +static const char * const git_stash_export_usage[] = {\n> +\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n> +\tNULL\n> +};\n> +\n> +\n\nStray too-much-whitespace.\n\n>  static const char ref_stash[] = \"refs/stash\";\n>  static struct strbuf stash_index_path = STRBUF_INIT;\n>  \n> @@ -1773,6 +1780,180 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n>  \treturn ret;\n>  }\n>  \n> +static int write_commit_with_parents(struct object_id *out, const struct object_id *oid, struct commit_list *parents)\n> +{\n> +\tsize_t author_len, committer_len;\n> +\tstruct commit *this;\n> +\tconst char *orig_author, *orig_committer;\n> +\tchar *author = NULL, *committer = NULL;\n> +\tconst char *buffer;\n> +\tunsigned long bufsize;\n> +\tconst char *p;\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\tint ret = 0;\n\nWith this...\n\n> +\tthis = lookup_commit_reference(the_repository, oid);\n> +\tbuffer = get_commit_buffer(this, &bufsize);\n> +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n> +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n> +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n> +\n> +\tif (!orig_author || !orig_committer || !p) {\n> +\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n> +\t\tgoto out;\n\n..isn't this a logic error, shouldn't we return -1 here?\n\n> +\t}\n> +\t/* Jump to message. */\n> +\tp += 2;\n> +\tstrbuf_addstr(&msg, \"git stash: \");\n> +\tstrbuf_add(&msg, p, bufsize - (p - buffer));\n> +\n> +\tauthor = xmemdupz(orig_author, author_len);\n> +\tcommitter = xmemdupz(orig_committer, committer_len);\n> +\n> +\tif (commit_tree_extended(msg.buf, msg.len,\n> +\t\t\t\t the_hash_algo->empty_tree, parents,\n> +\t\t\t\t out, author, committer,\n> +\t\t\t\t NULL, NULL)) {\n> +\t\tret = -1;\n> +\t\terror(_(\"could not write commit\"));\n\nbetter as \"ret = error(...\"?\n\n> +\t\tgoto out;\n> +\t}\n> +out:\n> +\tstrbuf_reset(&msg);\n> +\tunuse_commit_buffer(this, buffer);\n> +\tfree(author);\n> +\tfree(committer);\n> +\treturn ret;\n> +}\n> +\n> +static int do_export_stash(const char *ref, size_t argc, const char **argv)\n> +{\n> +\tstruct object_id base;\n> +\tstruct object_context unused;\n> +\tstruct commit *prev;\n> +\tstruct object_id *items = NULL;\n> +\tint nitems = 0, nalloc = 0;\n\nCan nalloc be moved into the if=else scopes?\n\nAlso shouldn't these be size_t...?\n\n> +\tint res = 0;\n> +\tstruct strbuf revision;\n> +\tconst char *author, *committer;\n> +\n> +\t/*\n> +\t * This is an arbitrary, fixed date, specifically the one used by git\n> +\t * format-patch.  The goal is merely to produce reproducible output.\n> +\t */\n> +\tprepare_fallback_ident(\"git stash\", \"git@stash\");\n> +\tauthor = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n> +\tcommitter = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n> +\n> +\t/* First, we create a single empty commit. */\n> +\tif (commit_tree_extended(NULL, 0, the_hash_algo->empty_tree, NULL, &base, author, committer, NULL, NULL))\n\nSome very long lines here.\n\n> +\t\treturn error(_(\"unable to write base commit\"));\n> +\n> +\tprev = lookup_commit_reference(the_repository, &base);\n> +\n> +\tif (argc) {\n> +\t\t/*\n> +\t\t * Find each specified stash, and load data into the array.\n> +\t\t */\n> +\t\tfor (int i = 0; i < argc; i++) {\n\n...as this is size_t, not int.\n\n> +\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n> +\t\t\tif (parse_revision(&revision, argv[i], 0) ||\n> +\t\t\t    get_oid_with_context(the_repository, revision.buf,\n> +\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n> +\t\t\t\t\t\t &items[i], &unused)) {\n> +\t\t\t\terror(_(\"unable to find stash entry %s\"), argv[i]);\n> +\t\t\t\tres = -1;\n\nditto \"ret = error(...\"\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t}\n> +\t} else {\n> +\t\t/*\n> +\t\t * Walk the reflog, finding each stash entry, and load data into the\n> +\t\t * array.\n> +\t\t */\n> +\t\tfor (int i = 0;; i++) {\n\nAside from the C99 dependency Junio mentioned, this should also be size_t.\n\n> +\t/*\n> +\t * Now, create a set of commits identical to the regular stash commits,\n> +\t * but where their first parents form a chain to our original empty\n> +\t * base commit.\n> +\t */\n> +\tfor (int i = nitems - 1; i >= 0; i--) {\n\nDid you spot my \"count down\" suggestion in\nhttps://lore.kernel.org/git/220311.86bkydi65v.gmgdl@evledraar.gmail.com/\non the v1?\n\n> +\t\tstruct commit_list *parents = NULL;\n> +\t\tstruct commit_list **next = &parents;\n> +\t\tstruct object_id out;\n> +\n> +\t\tnext = commit_list_append(prev, next);\n> +\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i]), next);\n> +\t\tif (write_commit_with_parents(&out, &items[i], parents)) {\n\nHere we returned -1 from this earlier, I think this would be more\nstraightforward as:\n\t\n\tres = write_commit_with_parents(...)\n\tif (res < 0)\n\t\tgoto out;\n\t\n\n> +\t\t\tres = -1;\n> +\t\t\tgoto out;\n\nSo one doesn't have to wonder why we're ignoring the error value, and\nusing -1, but then treating all non-zero as errors.\n\n> +\t\t}\n> +\t\tprev = lookup_commit_reference(the_repository, &out);\n> +\t}\n> +\tif (ref)\n> +\t\tupdate_ref(NULL, ref, &prev->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n> +\telse\n> +\t\tputs(oid_to_hex(&prev->object.oid));\n> +out:\n> +\tfree(items);\n> +\n> +\treturn res;\n> +}\n> +\n> +enum export_action {\n> +\tACTION_NONE,\n> +\tACTION_PRINT,\n> +\tACTION_TO_REF,\n> +};\n> +\n> +static int export_stash(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint ret = 0;\n\nIt looks like we don't need to initialize this.\n\n> +\tconst char *ref = NULL;\n> +\tenum export_action action = ACTION_NONE;\n> +\tstruct option options[] = {\n> +\t\tOPT_CMDMODE(0, \"print\", &action,\n> +\t\t\t    N_(\"print the object ID instead of writing it to a ref\"),\n> +\t\t\t    ACTION_PRINT),\n> +\t\tOPT_CMDMODE(0, \"to-ref\", &action,\n> +\t\t\t    N_(\"save the data to the given ref\"),\n> +\t\t\t    ACTION_TO_REF),\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\targc = parse_options(argc, argv, prefix, options,\n> +\t\t\t     git_stash_export_usage,\n> +\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n> +\n> +\tif (action == ACTION_NONE) {\n> +\t\treturn error(_(\"exactly one of --print and --to-ref is required\"));\n> +\t} else if (action == ACTION_TO_REF) {\n> +\t\tif (!argc)\n> +\t\t\treturn error(_(\"--to-ref requires an argument\"));\n> +\t\tref = argv[0];\n> +\t\targc--;\n> +\t\targv++;\n> +\t}\n> +\n> +\n\nnit: Too much whitespace\n\n> +\tret = do_export_stash(ref, argc, argv);\n> +\treturn ret;\n\nAside from the \"ret\" case above, maybe this would be better if the\n\"action\" check became a swith, then the compiler would help check it\nagainst the enum, and this wouldn't implicitly be both ACTION_PRINT and\nACTION_TO_REF, but could be done via a fall-through.\n\n"},{"id":"452765","messageId":"220331.86lewqswes.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220329214941.2018609-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-31T02:09:21Z","receivedAt":"2022-03-31T02:12:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 29 2022, brian m. carlson wrote:\n\n> +\tif (argc) {\n> +\t\t/*\n> +\t\t * Find each specified stash, and load data into the array.\n> +\t\t */\n> +\t\tfor (int i = 0; i < argc; i++) {\n> +\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n> +\t\t\tif (parse_revision(&revision, argv[i], 0) ||\n> +\t\t\t    get_oid_with_context(the_repository, revision.buf,\n> +\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n> +\t\t\t\t\t\t &items[i], &unused)) {\n> +\t\t\t\terror(_(\"unable to find stash entry %s\"), argv[i]);\n> +\t\t\t\tres = -1;\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t}\n\nOne thing I missed on the first read-through, in the earlier commit you\nfactored out parse_revision(), which now contains this code (which was\nhere before this series):\n\t\n\t+\tif (!commit) {\n\t+\t\tif (!ref_exists(ref_stash)) {\n\t+\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n\t+\t\t\treturn -1;\n\t+\t\t}\n\nAren't we going to print both \"No stash entries\" and \"unable to find\nstash entry %s\" in those cases?\n"},{"id":"452767","messageId":"220331.86h77esw12.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"xmqqo81m51ux.fsf@gitster.g","subject":"Re: [PATCH v2 0/4] Importing and exporting stashes to refs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-03-31T02:18:44Z","receivedAt":"2022-03-31T02:20:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 30 2022, Junio C Hamano wrote:\n\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n>\n>> brian m. carlson (4):\n>>   object-name: make get_oid quietly return an error\n>>   builtin/stash: factor out revision parsing into a function\n>>   builtin/stash: provide a way to export stashes to a ref\n>>   builtin/stash: provide a way to import stashes from a ref\n>>\n>>  Documentation/git-stash.txt |  29 +++-\n>>  builtin/stash.c             | 324 ++++++++++++++++++++++++++++++++++--\n>>  cache.h                     |   1 +\n>>  object-name.c               |   6 +-\n>>  t/t3903-stash.sh            |  52 ++++++\n>>  5 files changed, 397 insertions(+), 15 deletions(-)\n>\n> Merged to 'seen', t3903 seems to have a handful of breakages.\n> e.g. https://github.com/git/git/runs/5763421702?check_suite_focus=true\n\nOne is a segfault because of a missing STRBUF_INIT:\n\t\n\tdiff --git a/builtin/stash.c b/builtin/stash.c\n\tindex 7cab57bbe4b..92a1682941c 100644\n\t--- a/builtin/stash.c\n\t+++ b/builtin/stash.c\n\t@@ -1931,7 +1931,7 @@ static int do_export_stash(const char *ref, size_t argc, const char **argv)\n\t \tstruct object_id *items = NULL;\n\t \tint nitems = 0, nalloc = 0;\n\t \tint res = 0;\n\t-\tstruct strbuf revision;\n\t+\tstruct strbuf revision = STRBUF_INIT;\n\t \tconst char *author, *committer;\n\t \n\t \t/*\n\t\n"},{"id":"452810","messageId":"xmqq7d8a3tnb.fsf@gitster.g","threadId":"57531","inReplyTo":"220331.86pmm2swm9.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-03-31T17:43:04Z","receivedAt":"2022-03-31T17:43:12Z","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>> @@ -151,6 +152,12 @@ store::\n>>  \treflog.  This is intended to be useful for scripts.  It is\n>>  \tprobably not the command you want to use; see \"push\" above.\n>>  \n>> +export ( --print | --to-ref <ref> ) [<stash>...]::\n>> +\n>\n> I think this extra \\n here isn't needed.\n\nI wrote the same in my review on one of the previous rounds, but\nremoved it before sending it out, because this is better with than\nwithout.  The blank line isn't necessary but does not hurt.  And\nother existing entries in this file seem to have them consistently.\n\nThanks for carefully reading the posted patch.\n"},{"id":"452999","messageId":"20220403182250.904933-1-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH v3 0/4] Importing and exporting stashes to refs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-03T18:22:46Z","receivedAt":"2022-04-03T18:22:58Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Stashes are currently stored using the reflog in a given repository.\nThis is an interesting and novel way to handle them, but there is no way\nto easily move a stash across machines.  For example, stashes cannot be\nbundled, pushed, or fetched.\n\nLet's solve this problem by allowing users to import and export stashes\nto a chain of commits.  The commits used in a stash export contain two\nparents: one which is the pointer to the next exported stash (or to an\nempty commit with no parents if there are no more) and the second is the\nstash commit that would normally be stored in the reflog.\n\nChanges from v2:\n* Fix uninitialized strbuf.\n* Avoid C99-style initializations.\n\nChanges from v1:\n* Change storage format as suggested by Junio.\n* Rename to GIT_OID_GENTLY.\n* Remove unnecessary initializations.\n* Use ALLOC_GROW_BY.\n* Ensure completely reproducible exports.\n* Avoid size_t.\n* Various other code cleanups.\n\nbrian m. carlson (4):\n  object-name: make get_oid quietly return an error\n  builtin/stash: factor out revision parsing into a function\n  builtin/stash: provide a way to export stashes to a ref\n  builtin/stash: provide a way to import stashes from a ref\n\n Documentation/git-stash.txt |  29 +++-\n builtin/stash.c             | 326 ++++++++++++++++++++++++++++++++++--\n cache.h                     |   1 +\n object-name.c               |   6 +-\n t/t3903-stash.sh            |  52 ++++++\n 5 files changed, 399 insertions(+), 15 deletions(-)\n\n"},{"id":"453000","messageId":"20220403182250.904933-2-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220403182250.904933-1-sandals@crustytoothpaste.net","subject":"[PATCH v3 1/4] object-name: make get_oid quietly return an error","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-03T18:22:47Z","receivedAt":"2022-04-03T18:23:00Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A reasonable person looking at the signature and usage of get_oid and\nfriends might conclude that in the event of an error, it always returns\n-1.  However, this is not the case.  Instead, get_oid_basic dies if we\ngo too far back into the history of a reflog (or, when quiet, simply\nexits).\n\nThis is not especially useful, since in many cases, we might want to\nhandle this error differently.  Let's add a flag here to make it just\nreturn -1 like elsewhere in these code paths.\n\nNote that we cannot make this behavior the default, since we have many\nother codepaths that rely on the existing behavior, including in tests.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n cache.h       | 1 +\n object-name.c | 6 +++++-\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/cache.h b/cache.h\nindex 825ec17198..657e3ff17f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1376,6 +1376,7 @@ struct object_context {\n #define GET_OID_RECORD_PATH     0200\n #define GET_OID_ONLY_TO_DIE    04000\n #define GET_OID_REQUIRE_PATH  010000\n+#define GET_OID_GENTLY        020000\n \n #define GET_OID_DISAMBIGUATORS \\\n \t(GET_OID_COMMIT | GET_OID_COMMITTISH | \\\ndiff --git a/object-name.c b/object-name.c\nindex 92862eeb1a..46dbfe36a6 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -911,13 +911,17 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\t\tlen, str,\n \t\t\t\t\t\tshow_date(co_time, co_tz, DATE_MODE(RFC2822)));\n \t\t\t\t}\n-\t\t\t} else {\n+\t\t\t} else if (!(flags & GET_OID_GENTLY)) {\n \t\t\t\tif (flags & GET_OID_QUIETLY) {\n \t\t\t\t\texit(128);\n \t\t\t\t}\n \t\t\t\tdie(_(\"log for '%.*s' only has %d entries\"),\n \t\t\t\t    len, str, co_cnt);\n \t\t\t}\n+\t\t\tif (flags & GET_OID_GENTLY) {\n+\t\t\t\tfree(real_ref);\n+\t\t\t\treturn -1;\n+\t\t\t}\n \t\t}\n \t}\n \n"},{"id":"453001","messageId":"20220403182250.904933-3-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220403182250.904933-1-sandals@crustytoothpaste.net","subject":"[PATCH v3 2/4] builtin/stash: factor out revision parsing into a function","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-03T18:22:48Z","receivedAt":"2022-04-03T18:23:03Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"We allow several special forms of stash names in this code.  In the\nfuture, we'll want to allow these same forms without parsing a stash\ncommit, so let's refactor this code out into a function for reuse.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n builtin/stash.c | 34 +++++++++++++++++++++-------------\n 1 file changed, 21 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 5897febfbe..4c281a5781 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -130,6 +130,24 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n \t\tdie(_(\"'%s' is not a stash-like commit\"), revision);\n }\n \n+static int parse_revision(struct strbuf *revision, const char *commit, int quiet)\n+{\n+\tstrbuf_init(revision, 0);\n+\tif (!commit) {\n+\t\tif (!ref_exists(ref_stash)) {\n+\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tstrbuf_addf(revision, \"%s@{0}\", ref_stash);\n+\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n+\t\tstrbuf_addf(revision, \"%s@{%s}\", ref_stash, commit);\n+\t} else {\n+\t\tstrbuf_addstr(revision, commit);\n+\t}\n+\treturn 0;\n+}\n+\n static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n {\n \tint ret;\n@@ -157,19 +175,9 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n \tif (argc == 1)\n \t\tcommit = argv[0];\n \n-\tstrbuf_init(&info->revision, 0);\n-\tif (!commit) {\n-\t\tif (!ref_exists(ref_stash)) {\n-\t\t\tfree_stash_info(info);\n-\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\tstrbuf_addf(&info->revision, \"%s@{0}\", ref_stash);\n-\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n-\t\tstrbuf_addf(&info->revision, \"%s@{%s}\", ref_stash, commit);\n-\t} else {\n-\t\tstrbuf_addstr(&info->revision, commit);\n+\tif (parse_revision(&info->revision, commit, 0)) {\n+\t\tfree_stash_info(info);\n+\t\treturn -1;\n \t}\n \n \trevision = info->revision.buf;\n"},{"id":"453002","messageId":"20220403182250.904933-5-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220403182250.904933-1-sandals@crustytoothpaste.net","subject":"[PATCH v3 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-03T18:22:50Z","receivedAt":"2022-04-03T18:23:05Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Now that we have a way to export stashes to a ref, let's provide a way\nto import them from such a ref back to the stash.  This works much the\nway the export code does, except that we strip off the first parent\nchain commit and then store each resulting commit back to the stash.\n\nWe don't clear the stash first and instead add the specified stashes to\nthe top of the stash.  This is because users may want to export just a\nfew stashes, such as to share a small amount of work in progress with a\ncolleague, and it would be undesirable for the receiving user to lose\nall of their data.  For users who do want to replace the stash, it's\neasy to do to: simply run \"git stash clear\" first.\n\nWe specifically rely on the fact that we'll produce identical stash\ncommits on both sides in our tests.  This provides a cheap,\nstraightforward check for our tests and also makes it easy for users to\nsee if they already have the same data in both repositories.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-stash.txt |   7 +++\n builtin/stash.c             | 108 ++++++++++++++++++++++++++++++++++++\n t/t3903-stash.sh            |  52 +++++++++++++++++\n 3 files changed, 167 insertions(+)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 162110314e..28eb9cab0c 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -21,6 +21,7 @@ SYNOPSIS\n 'git stash' create [<message>]\n 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n 'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n+'git stash' import <commit>\n \n DESCRIPTION\n -----------\n@@ -158,6 +159,12 @@ export ( --print | --to-ref <ref> ) [<stash>...]::\n \ta chain of commits which can be transferred using the normal fetch and\n \tpush mechanisms, then imported using the `import` subcommand.\n \n+import <commit>::\n+\n+\tImport the specified stashes from the specified commit, which must have been\n+\tcreated by `export`, and add them to the list of stashes.  To replace the\n+\texisting stashes, use `clear` first.\n+\n OPTIONS\n -------\n -a::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 89e22d0cdd..93b1a996c4 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -34,6 +34,7 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n+\tN_(\"git stash import <commit>\"),\n \tNULL\n };\n \n@@ -95,6 +96,10 @@ static const char * const git_stash_export_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_import_usage[] = {\n+\tN_(\"git stash import <commit>\"),\n+\tNULL\n+};\n \n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n@@ -104,6 +109,7 @@ static struct strbuf stash_index_path = STRBUF_INIT;\n  * b_commit is set to the base commit\n  * i_commit is set to the commit containing the index tree\n  * u_commit is set to the commit containing the untracked files tree\n+ * c_commit is set to the first parent (chain commit) when importing and is otherwise unset\n  * w_tree is set to the working tree\n  * b_tree is set to the base tree\n  * i_tree is set to the index tree\n@@ -114,6 +120,7 @@ struct stash_info {\n \tstruct object_id b_commit;\n \tstruct object_id i_commit;\n \tstruct object_id u_commit;\n+\tstruct object_id c_commit;\n \tstruct object_id w_tree;\n \tstruct object_id b_tree;\n \tstruct object_id i_tree;\n@@ -1826,6 +1833,105 @@ static int write_commit_with_parents(struct object_id *out, const struct object_\n \treturn ret;\n }\n \n+static int do_import_stash(const char *rev)\n+{\n+\tstruct object_id chain;\n+\tsize_t nalloc = 0;\n+\tstruct object_id *items = NULL;\n+\tint nitems = 0;\n+\tint res = 0;\n+\tint i;\n+\tconst char *buffer = NULL;\n+\tstruct commit *this = NULL;\n+\tchar *msg = NULL;\n+\n+\tif (get_oid(rev, &chain))\n+\t\treturn error(_(\"not a valid revision: %s\"), rev);\n+\n+\t/*\n+\t * Walk the commit history, finding each stash entry, and load data into\n+\t * the array.\n+\t */\n+\tfor (i = 0;; i++) {\n+\t\tstruct object_id tree, oid;\n+\t\tchar revision[GIT_MAX_HEXSZ + 1];\n+\n+\t\toid_to_hex_r(revision, &chain);\n+\n+\t\tif (get_oidf(&tree, \"%s:\", revision) ||\n+\t\t    !oideq(&tree, the_hash_algo->empty_tree)) {\n+\t\t\treturn error(_(\"%s is not a valid exported stash commit\"), revision);\n+\t\t}\n+\t\tif (get_oidf(&chain, \"%s^1\", revision) ||\n+\t\t    get_oidf(&oid, \"%s^2\", revision))\n+\t\t\tbreak;\n+\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\toidcpy(&items[i], &oid);\n+\t}\n+\n+\t/*\n+\t * Now, walk each entry, adding it to the stash as a normal stash\n+\t * commit.\n+\t */\n+\tfor (i = nitems - 1; i >= 0; i--) {\n+\t\tunsigned long bufsize;\n+\t\tconst char *p;\n+\n+\t\tthis = lookup_commit_reference(the_repository, &items[i]);\n+\t\tbuffer = get_commit_buffer(this, &bufsize);\n+\t\tif (!buffer) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot read commit buffer for %s\"), oid_to_hex(&items[i]));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n+\t\tif (!p) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(&items[i]));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tp += 2;\n+\t\tmsg = xmemdupz(p, bufsize - (p - buffer));\n+\t\tunuse_commit_buffer(this, buffer);\n+\t\tbuffer = NULL;\n+\n+\t\tif (do_store_stash(&items[i], msg, 1)) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot save the stash for %s\"), oid_to_hex(&items[i]));\n+\t\t\tgoto out;\n+\t\t}\n+\t\tFREE_AND_NULL(msg);\n+\t}\n+out:\n+\tif (this && buffer)\n+\t\tunuse_commit_buffer(this, buffer);\n+\tfree(items);\n+\tfree(msg);\n+\n+\treturn res;\n+}\n+\n+static int import_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tint ret = 0;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_import_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (argc != 1)\n+\t\treturn error(_(\"a revision to import from is required\"));\n+\n+\n+\tret = do_import_stash(argv[0]);\n+\treturn ret;\n+}\n+\n static int do_export_stash(const char *ref, size_t argc, const char **argv)\n {\n \tstruct object_id base;\n@@ -2000,6 +2106,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!save_stash(argc, argv, prefix);\n \telse if (!strcmp(argv[0], \"export\"))\n \t\treturn !!export_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"import\"))\n+\t\treturn !!import_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex b149e2af44..d2ddede9be 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1295,6 +1295,58 @@ test_expect_success 'stash --keep-index with file deleted in index does not resu\n \ttest_path_is_missing to-remove\n '\n \n+test_expect_success 'stash export and import round-trip stashes' '\n+\tgit reset &&\n+\t>untracked &&\n+\t>tracked1 &&\n+\t>tracked2 &&\n+\tgit add tracked* &&\n+\tgit stash -- &&\n+\t>subdir/untracked &&\n+\t>subdir/tracked1 &&\n+\t>subdir/tracked2 &&\n+\tgit add subdir/tracked* &&\n+\tgit stash -- subdir/ &&\n+\tstash0=$(git rev-parse --verify stash@{0}) &&\n+\tstash1=$(git rev-parse --verify stash@{1}) &&\n+\tsimple=$(git stash export --print) &&\n+\tgit stash clear &&\n+\tgit stash import \"$simple\" &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported0\" = \"$stash0\" &&\n+\ttest \"$imported1\" = \"$stash1\" &&\n+\tgit stash export --to-ref refs/heads/foo &&\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported0\" = \"$stash0\" &&\n+\ttest \"$imported1\" = \"$stash1\"\n+'\n+\n+test_expect_success 'stash import appends commits' '\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\techo $(cat actual | wc -l) >count &&\n+\tgit stash import refs/heads/foo &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = $(($(cat count) * 2)) actual\n+'\n+\n+test_expect_success 'stash export can accept specified stashes' '\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\tgit stash export --to-ref bar stash@{1} stash@{0} &&\n+\tgit stash clear &&\n+\tgit stash import bar &&\n+\timported0=$(git rev-parse --verify stash@{0}) &&\n+\timported1=$(git rev-parse --verify stash@{1}) &&\n+\ttest \"$imported1\" = \"$stash0\" &&\n+\ttest \"$imported0\" = \"$stash1\" &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = 2 actual\n+'\n+\n test_expect_success 'stash apply should succeed with unmodified file' '\n \techo base >file &&\n \tgit add file &&\n"},{"id":"453003","messageId":"20220403182250.904933-4-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220403182250.904933-1-sandals@crustytoothpaste.net","subject":"[PATCH v3 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-03T18:22:49Z","receivedAt":"2022-04-03T18:23:06Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A common user problem is how to sync in-progress work to another\nmachine.  Users currently must use some sort of transfer of the working\ntree, which poses security risks and also necessarily causes the index\nto become dirty.  The experience is suboptimal and frustrating for\nusers.\n\nA reasonable idea is to use the stash for this purpose, but the stash is\nstored in the reflog, not in a ref, and as such it cannot be pushed or\npulled.  This also means that it cannot be saved into a bundle or\npreserved elsewhere, which is a problem when using throwaway development\nenvironments.\n\nLet's solve this problem by allowing the user to export the stash to a\nref (or, to just write it into the repository and print the hash, à la\ngit commit-tree).  Introduce git stash export, which writes a chain of\ncommits where the first parent is always a chain to the previous stash,\nor to a single, empty commit (for the final item) and the second is the\nstash commit normally written to the reflog.\n\nIterate over each stash from topmost to bottomost, looking up the data\nfor each one, and then create the chain from the single empty commit\nback up in reverse order.  Generate a predictable empty commit so our\nbehavior is reproducible.  Create a useful commit message, preserving\nthe author and committer information, to help users identify stash\ncommits when viewing them as normal commits.\n\nIf the user has specified specific stashes they'd like to export\ninstead, use those instead of iterating over all of the stashes.\n\nAs part of this, specifically request quiet behavior when looking up the\nOID for a revision because we will eventually hit a revision that\ndoesn't exist and we don't want to die when that occurs.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-stash.txt |  22 ++++-\n builtin/stash.c             | 184 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 205 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 6e15f47525..162110314e 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -20,6 +20,7 @@ SYNOPSIS\n 'git stash' clear\n 'git stash' create [<message>]\n 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n+'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n \n DESCRIPTION\n -----------\n@@ -151,6 +152,12 @@ store::\n \treflog.  This is intended to be useful for scripts.  It is\n \tprobably not the command you want to use; see \"push\" above.\n \n+export ( --print | --to-ref <ref> ) [<stash>...]::\n+\n+\tExport the specified stashes, or all of them if none are specified, to\n+\ta chain of commits which can be transferred using the normal fetch and\n+\tpush mechanisms, then imported using the `import` subcommand.\n+\n OPTIONS\n -------\n -a::\n@@ -239,6 +246,19 @@ literally (including newlines and quotes).\n +\n Quiet, suppress feedback messages.\n \n+--print::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes without\n+storing it anywhere in the ref namespace and print the object ID to\n+standard output.  This is designed for scripts.\n+\n+--to-ref::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes and store\n+it to the specified ref.\n+\n \\--::\n \tThis option is only valid for `push` command.\n +\n@@ -256,7 +276,7 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n \n <stash>::\n \tThis option is only valid for `apply`, `branch`, `drop`, `pop`,\n-\t`show` commands.\n+\t`show`, and `export` commands.\n +\n A reference of the form `stash@{<revision>}`. When no `<stash>` is\n given, the latest stash is assumed (that is, `stash@{0}`).\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 4c281a5781..89e22d0cdd 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -33,6 +33,7 @@ static const char * const git_stash_usage[] = {\n \t   \"          [--] [<pathspec>...]]\"),\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n \tNULL\n };\n \n@@ -89,6 +90,12 @@ static const char * const git_stash_save_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_export_usage[] = {\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n+\tNULL\n+};\n+\n+\n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n \n@@ -1773,6 +1780,181 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \treturn ret;\n }\n \n+static int write_commit_with_parents(struct object_id *out, const struct object_id *oid, struct commit_list *parents)\n+{\n+\tsize_t author_len, committer_len;\n+\tstruct commit *this;\n+\tconst char *orig_author, *orig_committer;\n+\tchar *author = NULL, *committer = NULL;\n+\tconst char *buffer;\n+\tunsigned long bufsize;\n+\tconst char *p;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tthis = lookup_commit_reference(the_repository, oid);\n+\tbuffer = get_commit_buffer(this, &bufsize);\n+\torig_author = find_commit_header(buffer, \"author\", &author_len);\n+\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n+\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n+\n+\tif (!orig_author || !orig_committer || !p) {\n+\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n+\t\tgoto out;\n+\t}\n+\t/* Jump to message. */\n+\tp += 2;\n+\tstrbuf_addstr(&msg, \"git stash: \");\n+\tstrbuf_add(&msg, p, bufsize - (p - buffer));\n+\n+\tauthor = xmemdupz(orig_author, author_len);\n+\tcommitter = xmemdupz(orig_committer, committer_len);\n+\n+\tif (commit_tree_extended(msg.buf, msg.len,\n+\t\t\t\t the_hash_algo->empty_tree, parents,\n+\t\t\t\t out, author, committer,\n+\t\t\t\t NULL, NULL)) {\n+\t\tret = -1;\n+\t\terror(_(\"could not write commit\"));\n+\t\tgoto out;\n+\t}\n+out:\n+\tstrbuf_reset(&msg);\n+\tunuse_commit_buffer(this, buffer);\n+\tfree(author);\n+\tfree(committer);\n+\treturn ret;\n+}\n+\n+static int do_export_stash(const char *ref, size_t argc, const char **argv)\n+{\n+\tstruct object_id base;\n+\tstruct object_context unused;\n+\tstruct commit *prev;\n+\tstruct object_id *items = NULL;\n+\tint nitems = 0, nalloc = 0;\n+\tint res = 0;\n+\tint i;\n+\tstruct strbuf revision = STRBUF_INIT;\n+\tconst char *author, *committer;\n+\n+\t/*\n+\t * This is an arbitrary, fixed date, specifically the one used by git\n+\t * format-patch.  The goal is merely to produce reproducible output.\n+\t */\n+\tprepare_fallback_ident(\"git stash\", \"git@stash\");\n+\tauthor = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n+\tcommitter = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT, \"2001-09-17T00:00:00Z\", 0);\n+\n+\t/* First, we create a single empty commit. */\n+\tif (commit_tree_extended(NULL, 0, the_hash_algo->empty_tree, NULL, &base, author, committer, NULL, NULL))\n+\t\treturn error(_(\"unable to write base commit\"));\n+\n+\tprev = lookup_commit_reference(the_repository, &base);\n+\n+\tif (argc) {\n+\t\t/*\n+\t\t * Find each specified stash, and load data into the array.\n+\t\t */\n+\t\tfor (i = 0; i < argc; i++) {\n+\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\t\tif (parse_revision(&revision, argv[i], 0) ||\n+\t\t\t    get_oid_with_context(the_repository, revision.buf,\n+\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n+\t\t\t\t\t\t &items[i], &unused)) {\n+\t\t\t\terror(_(\"unable to find stash entry %s\"), argv[i]);\n+\t\t\t\tres = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * Walk the reflog, finding each stash entry, and load data into the\n+\t\t * array.\n+\t\t */\n+\t\tfor (i = 0;; i++) {\n+\t\t\tchar buf[32];\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tsnprintf(buf, sizeof(buf), \"%d\", i);\n+\t\t\tif (parse_revision(&revision, buf, 1) ||\n+\t\t\t    get_oid_with_context(the_repository, revision.buf,\n+\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n+\t\t\t\t\t\t &oid, &unused))\n+\t\t\t\tbreak;\n+\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\t\toidcpy(&items[i], &oid);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Now, create a set of commits identical to the regular stash commits,\n+\t * but where their first parents form a chain to our original empty\n+\t * base commit.\n+\t */\n+\tfor (i = nitems - 1; i >= 0; i--) {\n+\t\tstruct commit_list *parents = NULL;\n+\t\tstruct commit_list **next = &parents;\n+\t\tstruct object_id out;\n+\n+\t\tnext = commit_list_append(prev, next);\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i]), next);\n+\t\tif (write_commit_with_parents(&out, &items[i], parents)) {\n+\t\t\tres = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tprev = lookup_commit_reference(the_repository, &out);\n+\t}\n+\tif (ref)\n+\t\tupdate_ref(NULL, ref, &prev->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n+\telse\n+\t\tputs(oid_to_hex(&prev->object.oid));\n+out:\n+\tfree(items);\n+\n+\treturn res;\n+}\n+\n+enum export_action {\n+\tACTION_NONE,\n+\tACTION_PRINT,\n+\tACTION_TO_REF,\n+};\n+\n+static int export_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tint ret = 0;\n+\tconst char *ref = NULL;\n+\tenum export_action action = ACTION_NONE;\n+\tstruct option options[] = {\n+\t\tOPT_CMDMODE(0, \"print\", &action,\n+\t\t\t    N_(\"print the object ID instead of writing it to a ref\"),\n+\t\t\t    ACTION_PRINT),\n+\t\tOPT_CMDMODE(0, \"to-ref\", &action,\n+\t\t\t    N_(\"save the data to the given ref\"),\n+\t\t\t    ACTION_TO_REF),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_export_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (action == ACTION_NONE) {\n+\t\treturn error(_(\"exactly one of --print and --to-ref is required\"));\n+\t} else if (action == ACTION_TO_REF) {\n+\t\tif (!argc)\n+\t\t\treturn error(_(\"--to-ref requires an argument\"));\n+\t\tref = argv[0];\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n+\n+\tret = do_export_stash(ref, argc, argv);\n+\treturn ret;\n+}\n+\n int cmd_stash(int argc, const char **argv, const char *prefix)\n {\n \tpid_t pid = getpid();\n@@ -1816,6 +1998,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!push_stash(argc, argv, prefix, 0);\n \telse if (!strcmp(argv[0], \"save\"))\n \t\treturn !!save_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"export\"))\n+\t\treturn !!export_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\n"},{"id":"453015","messageId":"xmqqtub9soew.fsf@gitster.g","threadId":"57531","inReplyTo":"20220403182250.904933-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH v3 0/4] Importing and exporting stashes to refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-04-04T00:05:59Z","receivedAt":"2022-04-04T00:06:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> Changes from v2:\n> * Fix uninitialized strbuf.\n> * Avoid C99-style initializations.\n\nThanks.\n\n[1] is a CI run of 'seen' without this topic, while [2] is the same\nbut with this topic.\n\nt3903.115-117 (stash export) are not very happy in the latter.\n\ne.g. https://github.com/git/git/runs/5808828105?check_suite_focus=true#step:7:6623\n\n[References]\n\n*1* https://github.com/git/git/actions/runs/2086776970 \n*2* https://github.com/git/git/actions/runs/2086887176 \n"},{"id":"453016","messageId":"xmqqpmlxsnc5.fsf@gitster.g","threadId":"57531","inReplyTo":"xmqqtub9soew.fsf@gitster.g","subject":"Re: [PATCH v3 0/4] Importing and exporting stashes to refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-04-04T00:29:14Z","receivedAt":"2022-04-04T00:29:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n>\n>> Changes from v2:\n>> * Fix uninitialized strbuf.\n>> * Avoid C99-style initializations.\n>\n> Thanks.\n>\n> [1] is a CI run of 'seen' without this topic, while [2] is the same\n> but with this topic.\n>\n> t3903.115-117 (stash export) are not very happy in the latter.\n>\n> e.g. https://github.com/git/git/runs/5808828105?check_suite_focus=true#step:7:6623\n>\n> [References]\n>\n> *1* https://github.com/git/git/actions/runs/2086776970 \n> *2* https://github.com/git/git/actions/runs/2086887176 \n\nI think this is coming from a mismerge, conflicting topic being\nÆvar's revisions leakage series.  I'll push out an updated\nresolution later today.\n\nThanks.\n"},{"id":"453024","messageId":"220404.86tub9jql5.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"xmqqpmlxsnc5.fsf@gitster.g","subject":"Re: [PATCH v3 0/4] Importing and exporting stashes to refs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-04T06:20:29Z","receivedAt":"2022-04-04T06:45:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Apr 03 2022, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n>>\n>>> Changes from v2:\n>>> * Fix uninitialized strbuf.\n>>> * Avoid C99-style initializations.\n>>\n>> Thanks.\n>>\n>> [1] is a CI run of 'seen' without this topic, while [2] is the same\n>> but with this topic.\n>>\n>> t3903.115-117 (stash export) are not very happy in the latter.\n>>\n>> e.g. https://github.com/git/git/runs/5808828105?check_suite_focus=true#step:7:6623\n>>\n>> [References]\n>>\n>> *1* https://github.com/git/git/actions/runs/2086776970 \n>> *2* https://github.com/git/git/actions/runs/2086887176 \n>\n> I think this is coming from a mismerge, conflicting topic being\n> Ævar's revisions leakage series.  I'll push out an updated\n> resolution later today.\n\nIt looks like it, I looked at your resolution. It seems that since the\nfailure you pushed out this on top:\n\t\n\tdiff --git a/builtin/stash.c b/builtin/stash.c\n\tindex 35d7c2e828b..b4da17265a1 100644\n\t--- a/builtin/stash.c\n\t+++ b/builtin/stash.c\n\t@@ -150,6 +150,7 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n\t \n\t static int parse_revision(struct strbuf *revision, const char *commit, int quiet)\n\t {\n\t+\tstrbuf_init(revision, 0);\n\t \tif (!commit) {\n\t \t\tif (!ref_exists(ref_stash)) {\n\t \t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n\t@@ -192,8 +193,10 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n\t \tif (argc == 1)\n\t \t\tcommit = argv[0];\n\t \n\t-\tif (parse_revision(&info->revision, commit, 0))\n\t+\tif (parse_revision(&info->revision, commit, 0)) {\n\t+\t\tfree_stash_info(info);\n\t \t\treturn -1;\n\t+\t}\n\t \n\t \trevision = info->revision.buf;\n\nWhich per my [1] is odd. I.e. in my topic I'd gotten rid of that\nstrbuf_init() entirely, see [2].\n\nBut looking again the odd interaction with brian's topic is that in his\n[2] and [3] he refactored the parse_revision() into existance.\n\nIn the version he based things on we always init'd the strbuf there, but\nbetween [2] and [3] that \"strbuf_init()\" was tasked with the work of a\nstrbuf_reset().\n\nAnd on \"seen\" this (new code) still leaks:\n\t\n\t$ ~/g/git/git stash export --print\n\t6b0514ae1f670e55f450518c4b4421a997c8d082\n\t\n\t=================================================================\n\t==25882==ERROR: LeakSanitizer: detected memory leaks\n\t\n\tDirect leak of 910 byte(s) in 14 object(s) allocated from:\n\t    #0 0x45658d in __interceptor_realloc (/home/avar/g/git/git+0x45658d)\n\t    #1 0x763f6d in xrealloc /home/avar/g/git/wrapper.c:136:8\n\t    #2 0x71c031 in strbuf_grow /home/avar/g/git/strbuf.c:99:2\n\t    #3 0x71d224 in strbuf_vaddf /home/avar/g/git/strbuf.c:395:3\n\t    #4 0x71d1e1 in strbuf_addf /home/avar/g/git/strbuf.c:336:2\n\t    #5 0x546f3e in parse_revision /home/avar/g/git/builtin/stash.c:162:3\n\t    #6 0x547f99 in do_export_stash /home/avar/g/git/builtin/stash.c:1987:8\n\t    #7 0x543b2b in export_stash /home/avar/g/git/builtin/stash.c:2061:8\n\t    #8 0x541846 in cmd_stash /home/avar/g/git/builtin/stash.c:2109:12\n\t    #9 0x45a38a in run_builtin /home/avar/g/git/git.c:466:11\n\t    #10 0x458e21 in handle_builtin /home/avar/g/git/git.c:720:3\n\t    #11 0x459d65 in run_argv /home/avar/g/git/git.c:787:4\n\t    #12 0x458bda in cmd_main /home/avar/g/git/git.c:920:19\n\t    #13 0x567839 in main /home/avar/g/git/common-main.c:56:11\n\t    #14 0x7fb9a380f7ec in __libc_start_main csu/../csu/libc-start.c:332:16\n\t    #15 0x431119 in _start (/home/avar/g/git/git+0x431119)\n\nWe just don't test t3903 under linux-leaks yet.\n\nAnyway. I belive that the correct resolution is (but see caveats below):\n\t\n\tdiff --git a/builtin/stash.c b/builtin/stash.c\n\tindex b4da17265a1..3b9a6c5b753 100644\n\t--- a/builtin/stash.c\n\t+++ b/builtin/stash.c\n\t@@ -150,7 +150,6 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n\t \n\t static int parse_revision(struct strbuf *revision, const char *commit, int quiet)\n\t {\n\t-\tstrbuf_init(revision, 0);\n\t \tif (!commit) {\n\t \t\tif (!ref_exists(ref_stash)) {\n\t \t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n\t@@ -193,10 +192,8 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n\t \tif (argc == 1)\n\t \t\tcommit = argv[0];\n\t \n\t-\tif (parse_revision(&info->revision, commit, 0)) {\n\t-\t\tfree_stash_info(info);\n\t+\tif (parse_revision(&info->revision, commit, 0))\n\t \t\treturn -1;\n\t-\t}\n\t \n\t \trevision = info->revision.buf;\n\t \n\t@@ -1827,7 +1824,7 @@ static int write_commit_with_parents(struct object_id *out, const struct object_\n\t \t\tgoto out;\n\t \t}\n\t out:\n\t-\tstrbuf_reset(&msg);\n\t+\tstrbuf_release(&msg);\n\t \tunuse_commit_buffer(this, buffer);\n\t \tfree(author);\n\t \tfree(committer);\n\t@@ -1965,6 +1962,7 @@ static int do_export_stash(const char *ref, size_t argc, const char **argv)\n\t \t\t */\n\t \t\tfor (i = 0; i < argc; i++) {\n\t \t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n\t+\t\t\tstrbuf_reset(&revision);\n\t \t\t\tif (parse_revision(&revision, argv[i], 0) ||\n\t \t\t\t    get_oid_with_context(the_repository, revision.buf,\n\t \t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n\t@@ -1983,6 +1981,8 @@ static int do_export_stash(const char *ref, size_t argc, const char **argv)\n\t \t\t\tchar buf[32];\n\t \t\t\tstruct object_id oid;\n\t \n\t+\t\t\tstrbuf_reset(&revision);\n\t+\n\t \t\t\tsnprintf(buf, sizeof(buf), \"%d\", i);\n\t \t\t\tif (parse_revision(&revision, buf, 1) ||\n\t \t\t\t    get_oid_with_context(the_repository, revision.buf,\n\t@@ -2018,6 +2018,7 @@ static int do_export_stash(const char *ref, size_t argc, const char **argv)\n\t \t\tputs(oid_to_hex(&prev->object.oid));\n\t out:\n\t \tfree(items);\n\t+\tstrbuf_release(&revision);\n\t \n\t \treturn res;\n\t }\n\nI.e. most of that is really feedback/a bug report on [3] and [4],and the\nthird hunk here is really an unrelated fix where [4] conflated\nstrbuf_reset() with strbuf_release().\n\n1. https://lore.kernel.org/git/cover-v2-00.27-00000000000-20220323T203149Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/patch-v5-10.27-4c5404912e9-20220402T102002Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/20220403182250.904933-3-sandals@crustytoothpaste.net/\n4. https://lore.kernel.org/git/20220403182250.904933-4-sandals@crustytoothpaste.net/\n"},{"id":"453025","messageId":"220404.86pmlxjppz.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220403182250.904933-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH v3 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-04T06:46:44Z","receivedAt":"2022-04-04T07:03:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Apr 03 2022, brian m. carlson wrote:\n\n> [...snip...]\n\nPer\nhttps://lore.kernel.org/git/220404.86tub9jql5.gmgdl@evledraar.gmail.com/\nthere's a bug here, in the preceding commit you:\n\t\n\t+static int parse_revision(struct strbuf *revision, const char *commit, int quiet)\n\t+{\n\t+\tstrbuf_init(revision, 0);\n\t[...]\n\t static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n\t {\n\t \tint ret;\n\t@@ -157,19 +175,9 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n\t \tif (argc == 1)\n\t \t\tcommit = argv[0];\n\t \n\t-\tstrbuf_init(&info->revision, 0);\n\t-\tif (!commit) {\n\t-\t\tif (!ref_exists(ref_stash)) {\n\t[...]\n\t+\tif (parse_revision(&info->revision, commit, 0)) {\n\t+\t\tfree_stash_info(info);\n\t+\t\treturn -1;\n\t \t}\n\nThat caller wasn't dealing with an init'd \"info\", so we init'd it late\nand freed it (which my revisions_release() makes a lot less icky). But\nunrelated to that:\n\t\n> +\tif (argc) {\n> +\t\t/*\n> +\t\t * Find each specified stash, and load data into the array.\n> +\t\t */\n> +\t\tfor (i = 0; i < argc; i++) {\n> +\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n> +\t\t\tif (parse_revision(&revision, argv[i], 0) ||\n\nHere...\n\n> +\t\t\t    get_oid_with_context(the_repository, revision.buf,\n> +\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n> +\t\t\t\t\t\t &items[i], &unused)) {\n> +\t\t\t\terror(_(\"unable to find stash entry %s\"), argv[i]);\n> +\t\t\t\tres = -1;\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t}\n> +\t} else {\n> +\t\t/*\n> +\t\t * Walk the reflog, finding each stash entry, and load data into the\n> +\t\t * array.\n> +\t\t */\n> +\t\tfor (i = 0;; i++) {\n> +\t\t\tchar buf[32];\n> +\t\t\tstruct object_id oid;\n> +\n> +\t\t\tsnprintf(buf, sizeof(buf), \"%d\", i);\n> +\t\t\tif (parse_revision(&revision, buf, 1) ||\n\n...and here you are leaking memory in a loop. Per the above you need a\nstrbuf_reset() for both.\n\nThen (moved from earlier):\n\n> +static int write_commit_with_parents(struct object_id *out, const struct object_id *oid, struct commit_list *parents)\n> +{\n> +\tsize_t author_len, committer_len;\n> +\tstruct commit *this;\n> +\tconst char *orig_author, *orig_committer;\n> +\tchar *author = NULL, *committer = NULL;\n> +\tconst char *buffer;\n> +\tunsigned long bufsize;\n> +\tconst char *p;\n> +\tstruct strbuf msg = STRBUF_INIT;\n\nThis is a leak, because:\n\n> +out:\n> +\tstrbuf_reset(&msg);\n\nThis should be strbuf_release(), not strbuf_reset().\n\nAnyway, even if builtin/stash.c is currently a big fail whale it's still\nuseful to test new code with SANITIZE=leak, e.g. (and most of this is a\nlot less painful on top of my in-flight topic) on top of yours you can\ntry:\n\n        printf \"leak:%s\\n\" setup_revisions add_object_array_with_path \\\n        \tadd_rev_cmdline cmd_log_init_finish strvec_push \\\n                get_untracked_files pump_io_round setup_rename_conflict_info parse_pathspec \\\n                prep_parse_options init_reflog_walk >/tmp/supp.txt;\n\tLSAN_OPTIONS=print_suppressions=0:suppressions=/tmp/supp.txt ./t3903-stash.sh  -vixd\n\nWhere that printf is just something I came up with via trial & error by\nre-running the test with -vixd and quieting existing leaks, until\ngetting to the first leak new in your topic.\n\n"},{"id":"453033","messageId":"220404.86czhxjewx.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220403182250.904933-5-sandals@crustytoothpaste.net","subject":"Re: [PATCH v3 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-04T10:38:53Z","receivedAt":"2022-04-04T10:56:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Apr 03 2022, brian m. carlson wrote:\n\n\n> +\tstruct object_id *items = NULL;\n\nIs there a reason for why this...\n\n> [...]\n> +\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n> +\t\toidcpy(&items[i], &oid);\n> [...]\n> +\tfor (i = nitems - 1; i >= 0; i--) {\n> [...]\n> +\t\tthis = lookup_commit_reference(the_repository, &items[i]);\n> +\tfree(msg);\n\n...can't just use the existing \"oid_array\" APi?\n\n> [...]\n> +static int import_stash(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint ret = 0;\n> +\tstruct option options[] = {\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\targc = parse_options(argc, argv, prefix, options,\n> +\t\t\t     git_stash_import_usage,\n> +\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n> +\n> +\tif (argc != 1)\n> +\t\treturn error(_(\"a revision to import from is required\"));\n\nI think you want to use usage_with_options() here instead.\n\nIn any case, I think you can de-init \"ret\" above I think, as the\ncompiler will then spot a future logic error if we don't reach this:\n\n> +\tret = do_import_stash(argv[0]);\n> +\treturn ret;\n> +}\n> +\n>  static int do_export_stash(const char *ref, size_t argc, const char **argv)\n>  {\n>  \tstruct object_id base;\n> @@ -2000,6 +2106,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n>  \t\treturn !!save_stash(argc, argv, prefix);\n>  \telse if (!strcmp(argv[0], \"export\"))\n>  \t\treturn !!export_stash(argc, argv, prefix);\n> +\telse if (!strcmp(argv[0], \"import\"))\n> +\t\treturn !!import_stash(argc, argv, prefix);\n>  \telse if (*argv[0] != '-')\n>  \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n>  \t\t\t       git_stash_usage, options, argv[0]);\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index b149e2af44..d2ddede9be 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -1295,6 +1295,58 @@ test_expect_success 'stash --keep-index with file deleted in index does not resu\n>  \ttest_path_is_missing to-remove\n>  '\n>  \n> +test_expect_success 'stash export and import round-trip stashes' '\n> +\tgit reset &&\n> +\t>untracked &&\n> +\t>tracked1 &&\n> +\t>tracked2 &&\n> +\tgit add tracked* &&\n> +\tgit stash -- &&\n> +\t>subdir/untracked &&\n> +\t>subdir/tracked1 &&\n> +\t>subdir/tracked2 &&\n> +\tgit add subdir/tracked* &&\n> +\tgit stash -- subdir/ &&\n> +\tstash0=$(git rev-parse --verify stash@{0}) &&\n> +\tstash1=$(git rev-parse --verify stash@{1}) &&\n> +\tsimple=$(git stash export --print) &&\n> +\tgit stash clear &&\n> +\tgit stash import \"$simple\" &&\n> +\timported0=$(git rev-parse --verify stash@{0}) &&\n> +\timported1=$(git rev-parse --verify stash@{1}) &&\n> +\ttest \"$imported0\" = \"$stash0\" &&\n> +\ttest \"$imported1\" = \"$stash1\" &&\n> +\tgit stash export --to-ref refs/heads/foo &&\n> +\tgit stash clear &&\n> +\tgit stash import foo &&\n> +\timported0=$(git rev-parse --verify stash@{0}) &&\n> +\timported1=$(git rev-parse --verify stash@{1}) &&\n> +\ttest \"$imported0\" = \"$stash0\" &&\n> +\ttest \"$imported1\" = \"$stash1\"\n\nThis whole variable assignment/test/manual rev-parse would be better as\njust feeding tags you created earlier to test_cmp_rev, should be a lot\nfewer lines that way, I.e. these last 4 lines become:\n\n\tgit tag t-stash0 # earlier\n\ttest_cmp_rev t-stash0 stash@{0} &&\n\ttest_cmp_rev t-stash stash@{1}\n\nPerhaps adding N files in one commit isn't critical, then you could even\npiggy-back on \"test_commit\"....\n\n> +test_expect_success 'stash import appends commits' '\n> +\tgit log --format=oneline -g refs/stash >actual &&\n> +\techo $(cat actual | wc -l) >count &&\n\nHrm...\n\n> +\tgit stash import refs/heads/foo &&\n> +\tgit log --format=oneline -g refs/stash >actual &&\n> +\ttest_line_count = $(($(cat count) * 2)) actual\n\nFWIW I think I'd save myself the trivial disk space here and less shell\ntrickery with:\n\n\tgit log >out &&\n\tcat out out >out2\n\nThen:\n\n\ttest_line_count = $(wc -l <out2) actual\n\nOr just:\n\n\ttest_line_count = $(cat log-out log-out | wc -l) actual\n\nI.e. parts of this are presumably working around the $(()) operation not\njiving with a whitespace-padded $count, so you're doing the echo instead\nof a more obvious redirection to avoid that.\n\nWhich, I'd think is made more obvious by just cat-ing the earlier output\ntwice. Just my 0.02...\n\n> +test_expect_success 'stash export can accept specified stashes' '\n> +\tgit stash clear &&\n\nThis looks like it belongs as a test_when_finished line of an earlier\ntest that should be cleaning up after itself.\n\n> +\tgit stash import foo &&\n> +\tgit stash export --to-ref bar stash@{1} stash@{0} &&\n> +\tgit stash clear &&\n> +\tgit stash import bar &&\n> +\timported0=$(git rev-parse --verify stash@{0}) &&\n> +\timported1=$(git rev-parse --verify stash@{1}) &&\n> +\ttest \"$imported1\" = \"$stash0\" &&\n> +\ttest \"$imported0\" = \"$stash1\" &&\n\nThis test has an implicit dependency on your earlier test and will break\nif it's not defining stash0, e.g. if you use --run=N or use other skip\ntest features of test-lib.sh.\n\nJust factor that into a setup function & have the rest call it?\n"},{"id":"453038","messageId":"e73dc9f4-ad32-d49b-9767-c23c78a7e681@gmail.com","threadId":"57531","inReplyTo":"20220403182250.904933-3-sandals@crustytoothpaste.net","subject":"Re: [PATCH v3 2/4] builtin/stash: factor out revision parsing into a function","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-04-04T15:44:03Z","receivedAt":"2022-04-04T15:44:18Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi brian\n\nOn 03/04/2022 19:22, brian m. carlson wrote:\n> We allow several special forms of stash names in this code.  In the\n> future, we'll want to allow these same forms without parsing a stash\n> commit, so let's refactor this code out into a function for reuse.\n> \n> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n>   builtin/stash.c | 34 +++++++++++++++++++++-------------\n>   1 file changed, 21 insertions(+), 13 deletions(-)\n> \n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 5897febfbe..4c281a5781 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -130,6 +130,24 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n>   \t\tdie(_(\"'%s' is not a stash-like commit\"), revision);\n>   }\n>   \n> +static int parse_revision(struct strbuf *revision, const char *commit, int quiet)\n> +{\n> +\tstrbuf_init(revision, 0);\n\nI think this should become strbuf_reset() and the caller should call \nstrbuf_init() once before calling this function (or use STASH_INFO_INIT \nfrom ab/plug-leak-in-revisions). That should fix one of the leaks Ævar \nwas talking about, otherwise we reallocate the strbuf each time this \nfunction is called and leak the previous allocation.\n\nBest Wishes\n\nPhillip\n\n> +\tif (!commit) {\n> +\t\tif (!ref_exists(ref_stash)) {\n> +\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n> +\t\t\treturn -1;\n> +\t\t}\n> +\n> +\t\tstrbuf_addf(revision, \"%s@{0}\", ref_stash);\n> +\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n> +\t\tstrbuf_addf(revision, \"%s@{%s}\", ref_stash, commit);\n> +\t} else {\n> +\t\tstrbuf_addstr(revision, commit);\n> +\t}\n> +\treturn 0;\n> +}\n> +\n>   static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n>   {\n>   \tint ret;\n> @@ -157,19 +175,9 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n>   \tif (argc == 1)\n>   \t\tcommit = argv[0];\n>   \n> -\tstrbuf_init(&info->revision, 0);\n> -\tif (!commit) {\n> -\t\tif (!ref_exists(ref_stash)) {\n> -\t\t\tfree_stash_info(info);\n> -\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n> -\t\t\treturn -1;\n> -\t\t}\n> -\n> -\t\tstrbuf_addf(&info->revision, \"%s@{0}\", ref_stash);\n> -\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n> -\t\tstrbuf_addf(&info->revision, \"%s@{%s}\", ref_stash, commit);\n> -\t} else {\n> -\t\tstrbuf_addstr(&info->revision, commit);\n> +\tif (parse_revision(&info->revision, commit, 0)) {\n> +\t\tfree_stash_info(info);\n> +\t\treturn -1;\n>   \t}\n>   \n>   \trevision = info->revision.buf;\n"},{"id":"453103","messageId":"YkwIuA5dbwf+T2n4@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"220404.86tub9jql5.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 0/4] Importing and exporting stashes to refs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-05T09:15:51Z","receivedAt":"2022-04-05T09:53:18Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-04-04 at 06:20:29, Ævar Arnfjörð Bjarmason wrote:\n> I.e. most of that is really feedback/a bug report on [3] and [4],and the\n> third hunk here is really an unrelated fix where [4] conflated\n> strbuf_reset() with strbuf_release().\n\nYour resolutions seem reasonable.  And yes, I always conflate\nstrbuf_reset and strbuf_release.\n\nI'll put out a v4 with changes to my series and responses to some other\nfeedback I've received.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"453110","messageId":"YkwYSv7EitHaCQTs@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"220331.86lewqswes.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-05T10:22:02Z","receivedAt":"2022-04-05T11:42:47Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-31 at 02:09:21, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Mar 29 2022, brian m. carlson wrote:\n> \n> > +\tif (argc) {\n> > +\t\t/*\n> > +\t\t * Find each specified stash, and load data into the array.\n> > +\t\t */\n> > +\t\tfor (int i = 0; i < argc; i++) {\n> > +\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n> > +\t\t\tif (parse_revision(&revision, argv[i], 0) ||\n> > +\t\t\t    get_oid_with_context(the_repository, revision.buf,\n> > +\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n> > +\t\t\t\t\t\t &items[i], &unused)) {\n> > +\t\t\t\terror(_(\"unable to find stash entry %s\"), argv[i]);\n> > +\t\t\t\tres = -1;\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> > +\t\t}\n> \n> One thing I missed on the first read-through, in the earlier commit you\n> factored out parse_revision(), which now contains this code (which was\n> here before this series):\n> \t\n> \t+\tif (!commit) {\n> \t+\t\tif (!ref_exists(ref_stash)) {\n> \t+\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n> \t+\t\t\treturn -1;\n> \t+\t\t}\n> \n> Aren't we going to print both \"No stash entries\" and \"unable to find\n> stash entry %s\" in those cases?\n\nNo, we're not, because commit isn't NULL.  However, I'll put this under\nthe quiet flag nevertheless for the situation in the next chunk down\n(where argc is 0) where this is a problem.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"453111","messageId":"YkwT6pwfC1d/PG4Q@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"220404.86czhxjewx.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-05T10:03:22Z","receivedAt":"2022-04-05T11:43:00Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-04-04 at 10:38:53, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Sun, Apr 03 2022, brian m. carlson wrote:\n> \n> \n> > +\tstruct object_id *items = NULL;\n> \n> Is there a reason for why this...\n> \n> ...can't just use the existing \"oid_array\" APi?\n\nNone in particular off the top of my head, except that it didn't\noriginally.\n\n> I think you want to use usage_with_options() here instead.\n> \n> In any case, I think you can de-init \"ret\" above I think, as the\n> compiler will then spot a future logic error if we don't reach this:\n\nSure, I can do that.\n\n> This whole variable assignment/test/manual rev-parse would be better as\n> just feeding tags you created earlier to test_cmp_rev, should be a lot\n> fewer lines that way, I.e. these last 4 lines become:\n> \n> \tgit tag t-stash0 # earlier\n> \ttest_cmp_rev t-stash0 stash@{0} &&\n> \ttest_cmp_rev t-stash stash@{1}\n\nYeah, I think this would be a nice improvement.\n\n> Perhaps adding N files in one commit isn't critical, then you could even\n> piggy-back on \"test_commit\"....\n\nI don't think test_commit works here because stash only operates on\nthings that are not committed.  The original goal here was to ensure\nthat we round-tripped the untracked files.  Since the design has\nchanged, that's not as critical, but I still think it's a useful thing\nto check.\n\n> FWIW I think I'd save myself the trivial disk space here and less shell\n> trickery with:\n> \n> \tgit log >out &&\n> \tcat out out >out2\n> \n> Then:\n> \n> \ttest_line_count = $(wc -l <out2) actual\n> \n> Or just:\n> \n> \ttest_line_count = $(cat log-out log-out | wc -l) actual\n> \n> I.e. parts of this are presumably working around the $(()) operation not\n> jiving with a whitespace-padded $count, so you're doing the echo instead\n> of a more obvious redirection to avoid that.\n> \n> Which, I'd think is made more obvious by just cat-ing the earlier output\n> twice. Just my 0.02...\n\nYour assumption is correct, and I can make that change.\n\n> > +test_expect_success 'stash export can accept specified stashes' '\n> > +\tgit stash clear &&\n> \n> This looks like it belongs as a test_when_finished line of an earlier\n> test that should be cleaning up after itself.\n\nNot really.  We don't clear the stashes elsewhere in the test.  In fact,\nlast I looked, we had about 25 of them by this point in the test.\n\n> > +\tgit stash import foo &&\n> > +\tgit stash export --to-ref bar stash@{1} stash@{0} &&\n> > +\tgit stash clear &&\n> > +\tgit stash import bar &&\n> > +\timported0=$(git rev-parse --verify stash@{0}) &&\n> > +\timported1=$(git rev-parse --verify stash@{1}) &&\n> > +\ttest \"$imported1\" = \"$stash0\" &&\n> > +\ttest \"$imported0\" = \"$stash1\" &&\n> \n> This test has an implicit dependency on your earlier test and will break\n> if it's not defining stash0, e.g. if you use --run=N or use other skip\n> test features of test-lib.sh.\n> \n> Just factor that into a setup function & have the rest call it?\n\nYes, most of our tests have that problem.  I don't think it's worth\nchanging the way we do things unless we plan to make a concerted effort\nto do that across the codebase and verify that in CI.\n\nIf we really want to make that change across the codebase for the\nfuture, that's fine, but I haven't seen such a discussion on the list so\nfar.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"453116","messageId":"YkwgGjj0JIcOhlMH@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"220331.86pmm2swm9.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-05T10:55:22Z","receivedAt":"2022-04-05T12:58:36Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-03-31 at 01:56:01, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Mar 29 2022, brian m. carlson wrote:\n> \n> > diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\n> > index 6e15f47525..162110314e 100644\n> > --- a/Documentation/git-stash.txt\n> > +++ b/Documentation/git-stash.txt\n> > @@ -20,6 +20,7 @@ SYNOPSIS\n> >  'git stash' clear\n> >  'git stash' create [<message>]\n> >  'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n> > +'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n> >  \n> >  DESCRIPTION\n> >  -----------\n> > @@ -151,6 +152,12 @@ store::\n> >  \treflog.  This is intended to be useful for scripts.  It is\n> >  \tprobably not the command you want to use; see \"push\" above.\n> >  \n> > +export ( --print | --to-ref <ref> ) [<stash>...]::\n> > +\n> \n> I think this extra \\n here isn't needed.\n\nAll the rest of the entries have it that way.  Junio likes it, you\ndon't, but it's consistent with the rest of the file and I'm just\nfollowing along.\n\n> > +static const char * const git_stash_export_usage[] = {\n> > +\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n> > +\tNULL\n> > +};\n> > +\n> > +\n> \n> Stray too-much-whitespace.\n\nFixed in v3 already.\n\n> > +\tthis = lookup_commit_reference(the_repository, oid);\n> > +\tbuffer = get_commit_buffer(this, &bufsize);\n> > +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n> > +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n> > +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n> > +\n> > +\tif (!orig_author || !orig_committer || !p) {\n> > +\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n> > +\t\tgoto out;\n> \n> ..isn't this a logic error, shouldn't we return -1 here?\n\nYes, it is.\n\n> better as \"ret = error(...\"?\n\nYup.  v4 will have it fixed in both places.\n\n> Can nalloc be moved into the if=else scopes?\n\nIt _can_, but it's used in both, so I don't see a particular reason to\ndo so.\n\n> Some very long lines here.\n\nWill wrap in v4.\n\n> > +\t\treturn error(_(\"unable to write base commit\"));\n> > +\n> > +\tprev = lookup_commit_reference(the_repository, &base);\n> > +\n> > +\tif (argc) {\n> > +\t\t/*\n> > +\t\t * Find each specified stash, and load data into the array.\n> > +\t\t */\n> > +\t\tfor (int i = 0; i < argc; i++) {\n> \n> ...as this is size_t, not int.\n\nI'll make argc int.\n\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> > +\t\t}\n> > +\t} else {\n> > +\t\t/*\n> > +\t\t * Walk the reflog, finding each stash entry, and load data into the\n> > +\t\t * array.\n> > +\t\t */\n> > +\t\tfor (int i = 0;; i++) {\n> \n> Aside from the C99 dependency Junio mentioned, this should also be size_t.\n\nNope.  I specifically decided not to do that and mentioned it in the\ncover letter.  Since Windows doesn't let us have nice things like %zu,\nI'm going to switch to int here and be consistent.  I'm not coding for\n16-bit systems here and I feel 2 billion stashes is sufficient for the\nneeds of the project for the indefinite future based on the capabilities\nof current human beings.\n\nThe C99 dependency has been removed in v3.\n\n> > +\t/*\n> > +\t * Now, create a set of commits identical to the regular stash commits,\n> > +\t * but where their first parents form a chain to our original empty\n> > +\t * base commit.\n> > +\t */\n> > +\tfor (int i = nitems - 1; i >= 0; i--) {\n> \n> Did you spot my \"count down\" suggestion in\n> https://lore.kernel.org/git/220311.86bkydi65v.gmgdl@evledraar.gmail.com/\n> on the v1?\n\nI did, and I prefer this approach.  That would be necessary if we were\nusing size_t here, but we're not.\n\n> > +\t\tstruct commit_list *parents = NULL;\n> > +\t\tstruct commit_list **next = &parents;\n> > +\t\tstruct object_id out;\n> > +\n> > +\t\tnext = commit_list_append(prev, next);\n> > +\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i]), next);\n> > +\t\tif (write_commit_with_parents(&out, &items[i], parents)) {\n> \n> Here we returned -1 from this earlier, I think this would be more\n> straightforward as:\n> \t\n> \tres = write_commit_with_parents(...)\n> \tif (res < 0)\n> \t\tgoto out;\n> \t\n> \n> > +\t\t\tres = -1;\n> > +\t\t\tgoto out;\n> \n> So one doesn't have to wonder why we're ignoring the error value, and\n> using -1, but then treating all non-zero as errors.\n\nThat will be fixed in v4.\n\n> It looks like we don't need to initialize this.\n\nIt'll be removed in v4.\n\n> > +\tret = do_export_stash(ref, argc, argv);\n> > +\treturn ret;\n> \n> Aside from the \"ret\" case above, maybe this would be better if the\n> \"action\" check became a swith, then the compiler would help check it\n> against the enum, and this wouldn't implicitly be both ACTION_PRINT and\n> ACTION_TO_REF, but could be done via a fall-through.\n\nNormally I'm a big fan of switch statements, but I don't feel in this\ncase that it logically represents the current code.  I think, given the\ncontext, that an if-else is easier to read.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"453165","messageId":"220406.86k0c2egb0.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"YkwT6pwfC1d/PG4Q@camp.crustytoothpaste.net","subject":"Re: [PATCH v3 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-06T09:00:44Z","receivedAt":"2022-04-06T12:46:49Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Apr 05 2022, brian m. carlson wrote:\n\n>> This test has an implicit dependency on your earlier test and will break\n>> if it's not defining stash0, e.g. if you use --run=N or use other skip\n>> test features of test-lib.sh.\n>> \n>> Just factor that into a setup function & have the rest call it?\n>\n> Yes, most of our tests have that problem.  I don't think it's worth\n> changing the way we do things unless we plan to make a concerted effort\n> to do that across the codebase and verify that in CI.\n>\n> If we really want to make that change across the codebase for the\n> future, that's fine, but I haven't seen such a discussion on the list so\n> far.\n\nFair enough.\n\nI do think it's a good pattern when adding /new/ code to follow that\npractice, even if it requires the first added test to have a \"git reset\n--hard\" (in liue of fixing various tests above).\n\nBut not enough to quibbly about it here :) Thanks.\n"},{"id":"453168","messageId":"220406.86fsmqefal.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"YkwgGjj0JIcOhlMH@camp.crustytoothpaste.net","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-06T09:05:28Z","receivedAt":"2022-04-06T12:58:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Apr 05 2022, brian m. carlson wrote:\n\n> [[PGP Signed Part:Undecided]]\n> On 2022-03-31 at 01:56:01, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> On Tue, Mar 29 2022, brian m. carlson wrote:\n>> \n>> > diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\n>> > index 6e15f47525..162110314e 100644\n>> > --- a/Documentation/git-stash.txt\n>> > +++ b/Documentation/git-stash.txt\n>> > @@ -20,6 +20,7 @@ SYNOPSIS\n>> >  'git stash' clear\n>> >  'git stash' create [<message>]\n>> >  'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n>> > +'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n>> >  \n>> >  DESCRIPTION\n>> >  -----------\n>> > @@ -151,6 +152,12 @@ store::\n>> >  \treflog.  This is intended to be useful for scripts.  It is\n>> >  \tprobably not the command you want to use; see \"push\" above.\n>> >  \n>> > +export ( --print | --to-ref <ref> ) [<stash>...]::\n>> > +\n>> \n>> I think this extra \\n here isn't needed.\n>\n> All the rest of the entries have it that way.  Junio likes it, you\n> don't, but it's consistent with the rest of the file and I'm just\n> following along.\n\nFWIW I really don't mind. I vaguely thought it might be an ASCIIDOC\nsyntax error as I'm used to seeing the other form, as e.g. adding an\nextra \\n before the following \"+\" is.\n\nBut it's not, and it's indeed consistent with the rest. looks good.\n\n>> Can nalloc be moved into the if=else scopes?\n>\n> It _can_, but it's used in both, so I don't see a particular reason to\n> do so.\n\nI don't mind, FWIW the reason is just to save the reader skimming to\ntrack down the various bits of the manual memory allocation.\n\nBut as you noted in the v3 reply this can also just use oidset, so ...\n\n>> > +\t\t\t\tgoto out;\n>> > +\t\t\t}\n>> > +\t\t}\n>> > +\t} else {\n>> > +\t\t/*\n>> > +\t\t * Walk the reflog, finding each stash entry, and load data into the\n>> > +\t\t * array.\n>> > +\t\t */\n>> > +\t\tfor (int i = 0;; i++) {\n>> \n>> Aside from the C99 dependency Junio mentioned, this should also be size_t.\n>\n> Nope.  I specifically decided not to do that and mentioned it in the\n> cover letter.  Since Windows doesn't let us have nice things like %zu,\n> I'm going to switch to int here and be consistent.\n\nYou mean to avoid PRIuMAX instead of %d with:\n\n    snprintf(buf, sizeof(buf), \"%d\", i);\n\n?\n\n> [...(moved around)...]\n>> Did you spot my \"count down\" suggestion in\n>> https://lore.kernel.org/git/220311.86bkydi65v.gmgdl@evledraar.gmail.com/\n>> on the v1?\n>\n> I did, and I prefer this approach.  That would be necessary if we were\n> using size_t here, but we're not.\n> [...]\n\n> I'm not coding for 16-bit systems here and I feel 2 billion stashes is\n> sufficient for the needs of the project for the indefinite future\n> based on the capabilities of current human beings.\n\nI just thought you might not have seen the v1 feedback, fair enough. And\nI'm not going to argue that point here.\n\nJust as an explanation: The reason to use these sorts of patterns isn't\nbecause we might need to support 16 bit in the future, which we won't,\nor that I think it's plausible that someone might have >2^31-1 stashes.\n\nIt's that we've been changing to larger types across the codebase, and\ne.g. in 99d60545f87 (string-list API: change \"nr\" and \"alloc\" to\n\"size_t\", 2022-03-07) changing to an unsigned type required changing\nsuch an iterator.\n\nSo if we really don't need negative numbers, but are just using -1 as a\nvalue to \"stop\" in a for-loop it's IMO worth it in general to just stick\nwith unsigned, it makes things more future-proof.\n\nAlso, not gcc or clang, but e.g. HP/UX's aCC compiler screams bloddy\nmurder about various \"why are you assuming unsigned here?\" in\ncomparisons across the codebase, which gcc/clang stay quiet about, to\nthe point where it downs out other useful warnings it gives about actual\npotential issues.\n\nThat's not *directly* related to this, which it wouldn't warn about as\nit's \"int\". But generally it's a result of us being loose in mixing\nunsigned and signed types when it comes to a count of a number of items\nthat can't be negative, and won't be exact for \"guard clause\" code like\nthis.\n\nAnd finally, even if we might say that more than 2^31-1 stashes is\ninsane it's just easier to reason about code where multiple \"counts\" are\nin play if you don't need to carefully think about that aspect.\n\nAnyway, </by-way-of-explanation over>. But just to be clear I think it's\nfine that you want to keep this as-is.\n\n>> > +\tret = do_export_stash(ref, argc, argv);\n>> > +\treturn ret;\n>> \n>> Aside from the \"ret\" case above, maybe this would be better if the\n>> \"action\" check became a swith, then the compiler would help check it\n>> against the enum, and this wouldn't implicitly be both ACTION_PRINT and\n>> ACTION_TO_REF, but could be done via a fall-through.\n>\n> Normally I'm a big fan of switch statements, but I don't feel in this\n> case that it logically represents the current code.  I think, given the\n> context, that an if-else is easier to read.\n\nMakes sense. Thanks.\n"},{"id":"453201","messageId":"xmqqk0c2yxnk.fsf@gitster.g","threadId":"57531","inReplyTo":"YkwgGjj0JIcOhlMH@camp.crustytoothpaste.net","subject":"Re: [PATCH v2 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-04-06T16:38:55Z","receivedAt":"2022-04-06T18:02:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n>> >  \tprobably not the command you want to use; see \"push\" above.\n>> >  \n>> > +export ( --print | --to-ref <ref> ) [<stash>...]::\n>> > +\n>> \n>> I think this extra \\n here isn't needed.\n>\n> All the rest of the entries have it that way.  Junio likes it, you\n> don't, but it's consistent with the rest of the file and I'm just\n> following along.\n\nI do not have preference either way.  The only reason why I think\nthis patch is better with the blank line is because other existing\nentries are with a blank line between the header and the body.\n\nThanks.\n"},{"id":"453297","messageId":"20220407215352.3491567-4-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220407215352.3491567-1-sandals@crustytoothpaste.net","subject":"[PATCH v4 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-07T21:53:51Z","receivedAt":"2022-04-07T22:08:27Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A common user problem is how to sync in-progress work to another\nmachine.  Users currently must use some sort of transfer of the working\ntree, which poses security risks and also necessarily causes the index\nto become dirty.  The experience is suboptimal and frustrating for\nusers.\n\nA reasonable idea is to use the stash for this purpose, but the stash is\nstored in the reflog, not in a ref, and as such it cannot be pushed or\npulled.  This also means that it cannot be saved into a bundle or\npreserved elsewhere, which is a problem when using throwaway development\nenvironments.\n\nLet's solve this problem by allowing the user to export the stash to a\nref (or, to just write it into the repository and print the hash, à la\ngit commit-tree).  Introduce git stash export, which writes a chain of\ncommits where the first parent is always a chain to the previous stash,\nor to a single, empty commit (for the final item) and the second is the\nstash commit normally written to the reflog.\n\nIterate over each stash from topmost to bottomost, looking up the data\nfor each one, and then create the chain from the single empty commit\nback up in reverse order.  Generate a predictable empty commit so our\nbehavior is reproducible.  Create a useful commit message, preserving\nthe author and committer information, to help users identify stash\ncommits when viewing them as normal commits.\n\nIf the user has specified specific stashes they'd like to export\ninstead, use those instead of iterating over all of the stashes.\n\nAs part of this, specifically request quiet behavior when looking up the\nOID for a revision because we will eventually hit a revision that\ndoesn't exist and we don't want to die when that occurs.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-stash.txt |  22 ++++-\n builtin/stash.c             | 182 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 203 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 6e15f47525..162110314e 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -20,6 +20,7 @@ SYNOPSIS\n 'git stash' clear\n 'git stash' create [<message>]\n 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n+'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n \n DESCRIPTION\n -----------\n@@ -151,6 +152,12 @@ store::\n \treflog.  This is intended to be useful for scripts.  It is\n \tprobably not the command you want to use; see \"push\" above.\n \n+export ( --print | --to-ref <ref> ) [<stash>...]::\n+\n+\tExport the specified stashes, or all of them if none are specified, to\n+\ta chain of commits which can be transferred using the normal fetch and\n+\tpush mechanisms, then imported using the `import` subcommand.\n+\n OPTIONS\n -------\n -a::\n@@ -239,6 +246,19 @@ literally (including newlines and quotes).\n +\n Quiet, suppress feedback messages.\n \n+--print::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes without\n+storing it anywhere in the ref namespace and print the object ID to\n+standard output.  This is designed for scripts.\n+\n+--to-ref::\n+\tThis option is only valid for `export`.\n++\n+Create the chain of commits representing the exported stashes and store\n+it to the specified ref.\n+\n \\--::\n \tThis option is only valid for `push` command.\n +\n@@ -256,7 +276,7 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n \n <stash>::\n \tThis option is only valid for `apply`, `branch`, `drop`, `pop`,\n-\t`show` commands.\n+\t`show`, and `export` commands.\n +\n A reference of the form `stash@{<revision>}`. When no `<stash>` is\n given, the latest stash is assumed (that is, `stash@{0}`).\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex b677739bcd..07b0897eda 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -33,6 +33,7 @@ static const char * const git_stash_usage[] = {\n \t   \"          [--] [<pathspec>...]]\"),\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n \tNULL\n };\n \n@@ -89,6 +90,12 @@ static const char * const git_stash_save_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_export_usage[] = {\n+\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n+\tNULL\n+};\n+\n+\n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n \n@@ -1775,6 +1782,179 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \treturn ret;\n }\n \n+static int write_commit_with_parents(struct object_id *out, const struct object_id *oid, struct commit_list *parents)\n+{\n+\tsize_t author_len, committer_len;\n+\tstruct commit *this;\n+\tconst char *orig_author, *orig_committer;\n+\tchar *author = NULL, *committer = NULL;\n+\tconst char *buffer;\n+\tunsigned long bufsize;\n+\tconst char *p;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tthis = lookup_commit_reference(the_repository, oid);\n+\tbuffer = get_commit_buffer(this, &bufsize);\n+\torig_author = find_commit_header(buffer, \"author\", &author_len);\n+\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n+\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n+\n+\tif (!orig_author || !orig_committer || !p) {\n+\t\tret = error(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n+\t\tgoto out;\n+\t}\n+\t/* Jump to message. */\n+\tp += 2;\n+\tstrbuf_addstr(&msg, \"git stash: \");\n+\tstrbuf_add(&msg, p, bufsize - (p - buffer));\n+\n+\tauthor = xmemdupz(orig_author, author_len);\n+\tcommitter = xmemdupz(orig_committer, committer_len);\n+\n+\tif (commit_tree_extended(msg.buf, msg.len,\n+\t\t\t\t the_hash_algo->empty_tree, parents,\n+\t\t\t\t out, author, committer,\n+\t\t\t\t NULL, NULL)) {\n+\t\tret = error(_(\"could not write commit\"));\n+\t\tgoto out;\n+\t}\n+out:\n+\tstrbuf_release(&msg);\n+\tunuse_commit_buffer(this, buffer);\n+\tfree(author);\n+\tfree(committer);\n+\treturn ret;\n+}\n+\n+static int do_export_stash(const char *ref, int argc, const char **argv)\n+{\n+\tstruct object_id base;\n+\tstruct object_context unused;\n+\tstruct commit *prev;\n+\tstruct object_id *items = NULL;\n+\tint nitems = 0, nalloc = 0;\n+\tint res = 0;\n+\tint i;\n+\tstruct strbuf revision = STRBUF_INIT;\n+\tconst char *author, *committer;\n+\n+\t/*\n+\t * This is an arbitrary, fixed date, specifically the one used by git\n+\t * format-patch.  The goal is merely to produce reproducible output.\n+\t */\n+\tprepare_fallback_ident(\"git stash\", \"git@stash\");\n+\tauthor = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT,\n+\t\t\t   \"2001-09-17T00:00:00Z\", 0);\n+\tcommitter = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT,\n+\t\t\t      \"2001-09-17T00:00:00Z\", 0);\n+\n+\t/* First, we create a single empty commit. */\n+\tif (commit_tree_extended(NULL, 0, the_hash_algo->empty_tree, NULL,\n+\t\t\t\t &base, author, committer, NULL, NULL))\n+\t\treturn error(_(\"unable to write base commit\"));\n+\n+\tprev = lookup_commit_reference(the_repository, &base);\n+\n+\tif (argc) {\n+\t\t/*\n+\t\t * Find each specified stash, and load data into the array.\n+\t\t */\n+\t\tfor (i = 0; i < argc; i++) {\n+\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\t\tif (parse_revision(&revision, argv[i], 1) ||\n+\t\t\t    get_oid_with_context(the_repository, revision.buf,\n+\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n+\t\t\t\t\t\t &items[i], &unused)) {\n+\t\t\t\tres = error(_(\"unable to find stash entry %s\"), argv[i]);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * Walk the reflog, finding each stash entry, and load data into the\n+\t\t * array.\n+\t\t */\n+\t\tfor (i = 0;; i++) {\n+\t\t\tchar buf[32];\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tsnprintf(buf, sizeof(buf), \"%d\", i);\n+\t\t\tif (parse_revision(&revision, buf, 1) ||\n+\t\t\t    get_oid_with_context(the_repository, revision.buf,\n+\t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n+\t\t\t\t\t\t &oid, &unused))\n+\t\t\t\tbreak;\n+\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\t\toidcpy(&items[i], &oid);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Now, create a set of commits identical to the regular stash commits,\n+\t * but where their first parents form a chain to our original empty\n+\t * base commit.\n+\t */\n+\tfor (i = nitems - 1; i >= 0; i--) {\n+\t\tstruct commit_list *parents = NULL;\n+\t\tstruct commit_list **next = &parents;\n+\t\tstruct object_id out;\n+\n+\t\tnext = commit_list_append(prev, next);\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i]), next);\n+\t\tres = write_commit_with_parents(&out, &items[i], parents);\n+\t\tif (res)\n+\t\t\tgoto out;\n+\t\tprev = lookup_commit_reference(the_repository, &out);\n+\t}\n+\tif (ref)\n+\t\tupdate_ref(NULL, ref, &prev->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n+\telse\n+\t\tputs(oid_to_hex(&prev->object.oid));\n+out:\n+\tstrbuf_release(&revision);\n+\tfree(items);\n+\n+\treturn res;\n+}\n+\n+enum export_action {\n+\tACTION_NONE,\n+\tACTION_PRINT,\n+\tACTION_TO_REF,\n+};\n+\n+static int export_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *ref = NULL;\n+\tenum export_action action = ACTION_NONE;\n+\tstruct option options[] = {\n+\t\tOPT_CMDMODE(0, \"print\", &action,\n+\t\t\t    N_(\"print the object ID instead of writing it to a ref\"),\n+\t\t\t    ACTION_PRINT),\n+\t\tOPT_CMDMODE(0, \"to-ref\", &action,\n+\t\t\t    N_(\"save the data to the given ref\"),\n+\t\t\t    ACTION_TO_REF),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_export_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (action == ACTION_NONE) {\n+\t\treturn error(_(\"exactly one of --print and --to-ref is required\"));\n+\t} else if (action == ACTION_TO_REF) {\n+\t\tif (!argc)\n+\t\t\treturn error(_(\"--to-ref requires an argument\"));\n+\t\tref = argv[0];\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n+\treturn do_export_stash(ref, argc, argv);\n+}\n+\n int cmd_stash(int argc, const char **argv, const char *prefix)\n {\n \tpid_t pid = getpid();\n@@ -1818,6 +1998,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!push_stash(argc, argv, prefix, 0);\n \telse if (!strcmp(argv[0], \"save\"))\n \t\treturn !!save_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"export\"))\n+\t\treturn !!export_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\n"},{"id":"453299","messageId":"20220407215352.3491567-3-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220407215352.3491567-1-sandals@crustytoothpaste.net","subject":"[PATCH v4 2/4] builtin/stash: factor out revision parsing into a function","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-07T21:53:50Z","receivedAt":"2022-04-07T22:08:29Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"We allow several special forms of stash names in this code.  In the\nfuture, we'll want to allow these same forms without parsing a stash\ncommit, so let's refactor this code out into a function for reuse.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n builtin/stash.c | 34 ++++++++++++++++++++++------------\n 1 file changed, 22 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 5897febfbe..b677739bcd 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -130,6 +130,25 @@ static void assert_stash_like(struct stash_info *info, const char *revision)\n \t\tdie(_(\"'%s' is not a stash-like commit\"), revision);\n }\n \n+static int parse_revision(struct strbuf *revision, const char *commit, int quiet)\n+{\n+\tstrbuf_reset(revision);\n+\tif (!commit) {\n+\t\tif (!ref_exists(ref_stash)) {\n+\t\t\tif (!quiet)\n+\t\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tstrbuf_addf(revision, \"%s@{0}\", ref_stash);\n+\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n+\t\tstrbuf_addf(revision, \"%s@{%s}\", ref_stash, commit);\n+\t} else {\n+\t\tstrbuf_addstr(revision, commit);\n+\t}\n+\treturn 0;\n+}\n+\n static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n {\n \tint ret;\n@@ -158,18 +177,9 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)\n \t\tcommit = argv[0];\n \n \tstrbuf_init(&info->revision, 0);\n-\tif (!commit) {\n-\t\tif (!ref_exists(ref_stash)) {\n-\t\t\tfree_stash_info(info);\n-\t\t\tfprintf_ln(stderr, _(\"No stash entries found.\"));\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\tstrbuf_addf(&info->revision, \"%s@{0}\", ref_stash);\n-\t} else if (strspn(commit, \"0123456789\") == strlen(commit)) {\n-\t\tstrbuf_addf(&info->revision, \"%s@{%s}\", ref_stash, commit);\n-\t} else {\n-\t\tstrbuf_addstr(&info->revision, commit);\n+\tif (parse_revision(&info->revision, commit, 0)) {\n+\t\tfree_stash_info(info);\n+\t\treturn -1;\n \t}\n \n \trevision = info->revision.buf;\n"},{"id":"453298","messageId":"20220407215352.3491567-2-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220407215352.3491567-1-sandals@crustytoothpaste.net","subject":"[PATCH v4 1/4] object-name: make get_oid quietly return an error","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-07T21:53:49Z","receivedAt":"2022-04-07T22:08:30Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"A reasonable person looking at the signature and usage of get_oid and\nfriends might conclude that in the event of an error, it always returns\n-1.  However, this is not the case.  Instead, get_oid_basic dies if we\ngo too far back into the history of a reflog (or, when quiet, simply\nexits).\n\nThis is not especially useful, since in many cases, we might want to\nhandle this error differently.  Let's add a flag here to make it just\nreturn -1 like elsewhere in these code paths.\n\nNote that we cannot make this behavior the default, since we have many\nother codepaths that rely on the existing behavior, including in tests.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n cache.h       | 1 +\n object-name.c | 6 +++++-\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/cache.h b/cache.h\nindex 825ec17198..657e3ff17f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1376,6 +1376,7 @@ struct object_context {\n #define GET_OID_RECORD_PATH     0200\n #define GET_OID_ONLY_TO_DIE    04000\n #define GET_OID_REQUIRE_PATH  010000\n+#define GET_OID_GENTLY        020000\n \n #define GET_OID_DISAMBIGUATORS \\\n \t(GET_OID_COMMIT | GET_OID_COMMITTISH | \\\ndiff --git a/object-name.c b/object-name.c\nindex 92862eeb1a..46dbfe36a6 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -911,13 +911,17 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\t\tlen, str,\n \t\t\t\t\t\tshow_date(co_time, co_tz, DATE_MODE(RFC2822)));\n \t\t\t\t}\n-\t\t\t} else {\n+\t\t\t} else if (!(flags & GET_OID_GENTLY)) {\n \t\t\t\tif (flags & GET_OID_QUIETLY) {\n \t\t\t\t\texit(128);\n \t\t\t\t}\n \t\t\t\tdie(_(\"log for '%.*s' only has %d entries\"),\n \t\t\t\t    len, str, co_cnt);\n \t\t\t}\n+\t\t\tif (flags & GET_OID_GENTLY) {\n+\t\t\t\tfree(real_ref);\n+\t\t\t\treturn -1;\n+\t\t\t}\n \t\t}\n \t}\n \n"},{"id":"453300","messageId":"20220407215352.3491567-5-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220407215352.3491567-1-sandals@crustytoothpaste.net","subject":"[PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-07T21:53:52Z","receivedAt":"2022-04-07T22:08:31Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Now that we have a way to export stashes to a ref, let's provide a way\nto import them from such a ref back to the stash.  This works much the\nway the export code does, except that we strip off the first parent\nchain commit and then store each resulting commit back to the stash.\n\nWe don't clear the stash first and instead add the specified stashes to\nthe top of the stash.  This is because users may want to export just a\nfew stashes, such as to share a small amount of work in progress with a\ncolleague, and it would be undesirable for the receiving user to lose\nall of their data.  For users who do want to replace the stash, it's\neasy to do to: simply run \"git stash clear\" first.\n\nWe specifically rely on the fact that we'll produce identical stash\ncommits on both sides in our tests.  This provides a cheap,\nstraightforward check for our tests and also makes it easy for users to\nsee if they already have the same data in both repositories.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-stash.txt |   7 +++\n builtin/stash.c             | 105 ++++++++++++++++++++++++++++++++++++\n t/t3903-stash.sh            |  63 ++++++++++++++++++++++\n 3 files changed, 175 insertions(+)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 162110314e..28eb9cab0c 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -21,6 +21,7 @@ SYNOPSIS\n 'git stash' create [<message>]\n 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>\n 'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n+'git stash' import <commit>\n \n DESCRIPTION\n -----------\n@@ -158,6 +159,12 @@ export ( --print | --to-ref <ref> ) [<stash>...]::\n \ta chain of commits which can be transferred using the normal fetch and\n \tpush mechanisms, then imported using the `import` subcommand.\n \n+import <commit>::\n+\n+\tImport the specified stashes from the specified commit, which must have been\n+\tcreated by `export`, and add them to the list of stashes.  To replace the\n+\texisting stashes, use `clear` first.\n+\n OPTIONS\n -------\n -a::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 07b0897eda..06fa74c0c2 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -34,6 +34,7 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n+\tN_(\"git stash import <commit>\"),\n \tNULL\n };\n \n@@ -95,6 +96,10 @@ static const char * const git_stash_export_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_import_usage[] = {\n+\tN_(\"git stash import <commit>\"),\n+\tNULL\n+};\n \n static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n@@ -104,6 +109,7 @@ static struct strbuf stash_index_path = STRBUF_INIT;\n  * b_commit is set to the base commit\n  * i_commit is set to the commit containing the index tree\n  * u_commit is set to the commit containing the untracked files tree\n+ * c_commit is set to the first parent (chain commit) when importing and is otherwise unset\n  * w_tree is set to the working tree\n  * b_tree is set to the base tree\n  * i_tree is set to the index tree\n@@ -114,6 +120,7 @@ struct stash_info {\n \tstruct object_id b_commit;\n \tstruct object_id i_commit;\n \tstruct object_id u_commit;\n+\tstruct object_id c_commit;\n \tstruct object_id w_tree;\n \tstruct object_id b_tree;\n \tstruct object_id i_tree;\n@@ -1827,6 +1834,102 @@ static int write_commit_with_parents(struct object_id *out, const struct object_\n \treturn ret;\n }\n \n+static int do_import_stash(const char *rev)\n+{\n+\tstruct object_id chain;\n+\tstruct oid_array items = OID_ARRAY_INIT;\n+\tint res = 0;\n+\tint i;\n+\tconst char *buffer = NULL;\n+\tstruct commit *this = NULL;\n+\tchar *msg = NULL;\n+\n+\tif (get_oid(rev, &chain))\n+\t\treturn error(_(\"not a valid revision: %s\"), rev);\n+\n+\t/*\n+\t * Walk the commit history, finding each stash entry, and load data into\n+\t * the array.\n+\t */\n+\tfor (i = 0;; i++) {\n+\t\tstruct object_id tree, oid;\n+\t\tchar revision[GIT_MAX_HEXSZ + 1];\n+\n+\t\toid_to_hex_r(revision, &chain);\n+\n+\t\tif (get_oidf(&tree, \"%s:\", revision) ||\n+\t\t    !oideq(&tree, the_hash_algo->empty_tree)) {\n+\t\t\treturn error(_(\"%s is not a valid exported stash commit\"), revision);\n+\t\t}\n+\t\tif (get_oidf(&chain, \"%s^1\", revision) ||\n+\t\t    get_oidf(&oid, \"%s^2\", revision))\n+\t\t\tbreak;\n+\t\toid_array_append(&items, &oid);\n+\t}\n+\n+\t/*\n+\t * Now, walk each entry, adding it to the stash as a normal stash\n+\t * commit.\n+\t */\n+\tfor (i = items.nr - 1; i >= 0; i--) {\n+\t\tunsigned long bufsize;\n+\t\tconst char *p;\n+\t\tconst struct object_id *oid = items.oid + i;\n+\n+\t\tthis = lookup_commit_reference(the_repository, oid);\n+\t\tbuffer = get_commit_buffer(this, &bufsize);\n+\t\tif (!buffer) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot read commit buffer for %s\"), oid_to_hex(oid));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n+\t\tif (!p) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tp += 2;\n+\t\tmsg = xmemdupz(p, bufsize - (p - buffer));\n+\t\tunuse_commit_buffer(this, buffer);\n+\t\tbuffer = NULL;\n+\n+\t\tif (do_store_stash(oid, msg, 1)) {\n+\t\t\tres = -1;\n+\t\t\terror(_(\"cannot save the stash for %s\"), oid_to_hex(oid));\n+\t\t\tgoto out;\n+\t\t}\n+\t\tFREE_AND_NULL(msg);\n+\t}\n+out:\n+\tif (this && buffer)\n+\t\tunuse_commit_buffer(this, buffer);\n+\toid_array_clear(&items);\n+\tfree(msg);\n+\n+\treturn res;\n+}\n+\n+static int import_stash(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_import_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (argc != 1) {\n+\t\tusage_with_options(git_stash_import_usage, options);\n+\t\treturn -1;\n+\t}\n+\n+\treturn do_import_stash(argv[0]);\n+}\n+\n static int do_export_stash(const char *ref, int argc, const char **argv)\n {\n \tstruct object_id base;\n@@ -2000,6 +2103,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)\n \t\treturn !!save_stash(argc, argv, prefix);\n \telse if (!strcmp(argv[0], \"export\"))\n \t\treturn !!export_stash(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"import\"))\n+\t\treturn !!import_stash(argc, argv, prefix);\n \telse if (*argv[0] != '-')\n \t\tusage_msg_optf(_(\"unknown subcommand: %s\"),\n \t\t\t       git_stash_usage, options, argv[0]);\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex b149e2af44..03607a5a38 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -10,6 +10,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n+test_expect_success 'setup' '\n+\ttest_oid_cache <<-EOF\n+\texport_base sha1:73c9bab443d1f88ac61aa533d2eeaaa15451239c\n+\texport_base sha256:f210fa6346e3e2ce047bdb570426b17075980c1ac01fec8fc4b75bd3ab4bcfe4\n+\tEOF\n+'\n+\n test_expect_success 'usage on cmd and subcommand invalid option' '\n \ttest_expect_code 129 git stash --invalid-option 2>usage &&\n \tgrep \"or: git stash\" usage &&\n@@ -1295,6 +1302,62 @@ test_expect_success 'stash --keep-index with file deleted in index does not resu\n \ttest_path_is_missing to-remove\n '\n \n+test_expect_success 'stash export and import round-trip stashes' '\n+\tgit reset &&\n+\t>untracked &&\n+\t>tracked1 &&\n+\t>tracked2 &&\n+\tgit add tracked* &&\n+\tgit stash -- &&\n+\t>subdir/untracked &&\n+\t>subdir/tracked1 &&\n+\t>subdir/tracked2 &&\n+\tgit add subdir/tracked* &&\n+\tgit stash --include-untracked -- subdir/ &&\n+\tgit tag t-stash0 stash@{0} &&\n+\tgit tag t-stash1 stash@{1} &&\n+\tsimple=$(git stash export --print) &&\n+\tgit stash clear &&\n+\tgit stash import \"$simple\" &&\n+\ttest_cmp_rev stash@{0} t-stash0 &&\n+\ttest_cmp_rev stash@{1} t-stash1 &&\n+\tgit stash export --to-ref refs/heads/foo &&\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\ttest_cmp_rev stash@{0} t-stash0 &&\n+\ttest_cmp_rev stash@{1} t-stash1\n+'\n+\n+test_expect_success 'stash import appends commits' '\n+\tgit log --format=oneline -g refs/stash >out &&\n+\tcat out out >out2 &&\n+\tgit stash import refs/heads/foo &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = $(wc -l <out2) actual\n+'\n+\n+test_expect_success 'stash export can accept specified stashes' '\n+\tgit stash clear &&\n+\tgit stash import foo &&\n+\tgit stash export --to-ref bar stash@{1} stash@{0} &&\n+\tgit stash clear &&\n+\tgit stash import bar &&\n+\ttest_cmp_rev stash@{1} t-stash0 &&\n+\ttest_cmp_rev stash@{0} t-stash1 &&\n+\tgit log --format=oneline -g refs/stash >actual &&\n+\ttest_line_count = 2 actual\n+'\n+\n+test_expect_success 'stash can import and export zero stashes' '\n+\tgit stash clear &&\n+\tgit stash export --to-ref baz &&\n+\ttest_cmp_rev \"$(test_oid empty_tree)\" baz: &&\n+\ttest_cmp_rev \"$(test_oid export_base)\" baz &&\n+\ttest_must_fail git rev-parse baz^1 &&\n+\tgit stash import baz &&\n+\ttest_must_fail git rev-parse refs/stash\n+'\n+\n test_expect_success 'stash apply should succeed with unmodified file' '\n \techo base >file &&\n \tgit add file &&\n"},{"id":"453301","messageId":"20220407215352.3491567-1-sandals@crustytoothpaste.net","threadId":"57531","inReplyTo":"20220310173236.4165310-1-sandals@crustytoothpaste.net","subject":"[PATCH v4 0/4] Importing and exporting stashes to refs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-07T21:53:48Z","receivedAt":"2022-04-07T22:08:32Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Stashes are currently stored using the reflog in a given repository.\nThis is an interesting and novel way to handle them, but there is no way\nto easily move a stash across machines.  For example, stashes cannot be\nbundled, pushed, or fetched.\n\nLet's solve this problem by allowing users to import and export stashes\nto a chain of commits.  The commits used in a stash export contain two\nparents: one which is the pointer to the next exported stash (or to an\nempty commit with no parents if there are no more) and the second is the\nstash commit that would normally be stored in the reflog.\n\nChanges from v3:\n* Fix strbuf handling to avoid leaks and generally be more sensible.\n* Make use of the error return code more often.\n* Use oid_array.\n* Tidy various parts of the code and fix long lines.\n* Simplify tests using git tag.\n* Shorten and tidy tests.\n* Add an additional test covering the base commit OID and importing and\n  exporting empty stashes.\n\nChanges from v2:\n* Fix uninitialized strbuf.\n* Avoid C99-style initializations.\n\nChanges from v1:\n* Change storage format as suggested by Junio.\n* Rename to GIT_OID_GENTLY.\n* Remove unnecessary initializations.\n* Use ALLOC_GROW_BY.\n* Ensure completely reproducible exports.\n* Avoid size_t.\n* Various other code cleanups.\n\nbrian m. carlson (4):\n  object-name: make get_oid quietly return an error\n  builtin/stash: factor out revision parsing into a function\n  builtin/stash: provide a way to export stashes to a ref\n  builtin/stash: provide a way to import stashes from a ref\n\n Documentation/git-stash.txt |  29 +++-\n builtin/stash.c             | 321 ++++++++++++++++++++++++++++++++++--\n cache.h                     |   1 +\n object-name.c               |   6 +-\n t/t3903-stash.sh            |  63 +++++++\n 5 files changed, 406 insertions(+), 14 deletions(-)\n\n"},{"id":"453462","messageId":"20220412201435.803424-1-jonathantanmy@google.com","threadId":"57531","inReplyTo":"20220407215352.3491567-5-sandals@crustytoothpaste.net","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-04-12T20:14:34Z","receivedAt":"2022-04-12T20:17:21Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> Now that we have a way to export stashes to a ref, let's provide a way\n> +\t/*\n> +\t * Now, walk each entry, adding it to the stash as a normal stash\n> +\t * commit.\n> +\t */\n> +\tfor (i = items.nr - 1; i >= 0; i--) {\n> +\t\tunsigned long bufsize;\n> +\t\tconst char *p;\n> +\t\tconst struct object_id *oid = items.oid + i;\n> +\n> +\t\tthis = lookup_commit_reference(the_repository, oid);\n> +\t\tbuffer = get_commit_buffer(this, &bufsize);\n> +\t\tif (!buffer) {\n> +\t\t\tres = -1;\n> +\t\t\terror(_(\"cannot read commit buffer for %s\"), oid_to_hex(oid));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n> +\t\tif (!p) {\n> +\t\t\tres = -1;\n> +\t\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tp += 2;\n> +\t\tmsg = xmemdupz(p, bufsize - (p - buffer));\n> +\t\tunuse_commit_buffer(this, buffer);\n> +\t\tbuffer = NULL;\n> +\n> +\t\tif (do_store_stash(oid, msg, 1)) {\n\nThis seems like you're using the commit message as the reflog message -\nis this necessary? For what it's worth, all tests still pass if I\nreplace \"msg\" with \"NULL\".\n\nOther than that, everything looks good to me.\n\nIt might be worth adding tests that check that the exported stashes are\nin the expected format (to ensure that we can read stashes exported from\nanother Git version) but I don't think that has to block the submission\nof this patch set.\n"},{"id":"453468","messageId":"YlYjgLcnNH8V1yj0@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"20220412201435.803424-1-jonathantanmy@google.com","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-13T01:12:32Z","receivedAt":"2022-04-13T01:12:38Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-04-12 at 20:14:34, Jonathan Tan wrote:\n> This seems like you're using the commit message as the reflog message -\n> is this necessary? For what it's worth, all tests still pass if I\n> replace \"msg\" with \"NULL\".\n\nI think that's what the existing stash code does, and so I did the same\nhere.  It's not strictly necessary, but it's a nice to have.\n\nI didn't think it worth testing, because I don't think we test it\nelsewhere, either.\n\n> It might be worth adding tests that check that the exported stashes are\n> in the expected format (to ensure that we can read stashes exported from\n> another Git version) but I don't think that has to block the submission\n> of this patch set.\n\nThere's a tiny patch for that for the base commit, but you're right that\nsome more tests wouldn't hurt.  I can send a followup patch or two as\npart of a new series.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"453484","messageId":"220413.86mtgpf1ed.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220407215352.3491567-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH v4 0/4] Importing and exporting stashes to refs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-13T15:25:41Z","receivedAt":"2022-04-13T15:28:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Apr 07 2022, brian m. carlson wrote:\n\n> * Use oid_array.\n\nIt looks like only 1/2 of the functions using the manual\nnot-quite-an-oid_array were converted. Here's a diff to squash for the\nother one:\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex e4b99188836..7d5493581e6 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1925,8 +1925,7 @@ static int do_export_stash(const char *ref, int argc, const char **argv)\n \tstruct object_id base;\n \tstruct object_context unused;\n \tstruct commit *prev;\n-\tstruct object_id *items = NULL;\n-\tint nitems = 0, nalloc = 0;\n+\tstruct oid_array items = OID_ARRAY_INIT;\n \tint res = 0;\n \tint i;\n \tstruct strbuf revision = STRBUF_INIT;\n@@ -1954,14 +1953,16 @@ static int do_export_stash(const char *ref, int argc, const char **argv)\n \t\t * Find each specified stash, and load data into the array.\n \t\t */\n \t\tfor (i = 0; i < argc; i++) {\n-\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n+\t\t\tstruct object_id oid;\n+\n \t\t\tif (parse_revision(&revision, argv[i], 1) ||\n \t\t\t    get_oid_with_context(the_repository, revision.buf,\n \t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n-\t\t\t\t\t\t &items[i], &unused)) {\n+\t\t\t\t\t\t &oid, &unused)) {\n \t\t\t\tres = error(_(\"unable to find stash entry %s\"), argv[i]);\n \t\t\t\tgoto out;\n \t\t\t}\n+\t\t\toid_array_append(&items, &oid);\n \t\t}\n \t} else {\n \t\t/*\n@@ -1978,8 +1979,7 @@ static int do_export_stash(const char *ref, int argc, const char **argv)\n \t\t\t\t\t\t GET_OID_QUIETLY | GET_OID_GENTLY,\n \t\t\t\t\t\t &oid, &unused))\n \t\t\t\tbreak;\n-\t\t\tALLOC_GROW_BY(items, nitems, 1, nalloc);\n-\t\t\toidcpy(&items[i], &oid);\n+\t\t\toid_array_append(&items, &oid);\n \t\t}\n \t}\n \n@@ -1988,14 +1988,15 @@ static int do_export_stash(const char *ref, int argc, const char **argv)\n \t * but where their first parents form a chain to our original empty\n \t * base commit.\n \t */\n-\tfor (i = nitems - 1; i >= 0; i--) {\n+\tfor (i = items.nr - 1; i >= 0; i--) {\n \t\tstruct commit_list *parents = NULL;\n \t\tstruct commit_list **next = &parents;\n \t\tstruct object_id out;\n+\t\tconst struct object_id *oid = items.oid + i;\n \n \t\tnext = commit_list_append(prev, next);\n-\t\tnext = commit_list_append(lookup_commit_reference(the_repository, &items[i]), next);\n-\t\tres = write_commit_with_parents(&out, &items[i], parents);\n+\t\tnext = commit_list_append(lookup_commit_reference(the_repository, oid), next);\n+\t\tres = write_commit_with_parents(&out, oid, parents);\n \t\tif (res)\n \t\t\tgoto out;\n \t\tprev = lookup_commit_reference(the_repository, &out);\n@@ -2006,7 +2007,7 @@ static int do_export_stash(const char *ref, int argc, const char **argv)\n \t\tputs(oid_to_hex(&prev->object.oid));\n out:\n \tstrbuf_release(&revision);\n-\tfree(items);\n+\toid_array_clear(&items);\n \n \treturn res;\n }\n"},{"id":"453485","messageId":"220413.86ilrdf1b3.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220407215352.3491567-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH v4 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-13T15:29:38Z","receivedAt":"2022-04-13T15:30:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Apr 07 2022, brian m. carlson wrote:\n\n> +'git stash' export ( --print | --to-ref <ref> ) [<stash>...]\n\nThis has whitespace-padding around \"(\" and \")\" but the C code verison\nyou added doesn't, i.e.:\n\n> +static const char * const git_stash_export_usage[] = {\n> +\tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\"),\n> +\tNULL\n> +};\n\nThe *.txt version should be made the same as the C one.\n\n(Spotted with an automated local check I have for finding these\ndifferences in *.txt v.s. code).\n"},{"id":"453491","messageId":"220413.86ee21f0g9.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220407215352.3491567-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH v4 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-13T15:36:05Z","receivedAt":"2022-04-13T15:49:17Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Apr 07 2022, brian m. carlson wrote:\n\n> +static int write_commit_with_parents(struct object_id *out, const struct object_id *oid, struct commit_list *parents)\n> +{\n> +\tsize_t author_len, committer_len;\n> +\tstruct commit *this;\n> +\tconst char *orig_author, *orig_committer;\n> +\tchar *author = NULL, *committer = NULL;\n> +\tconst char *buffer;\n> +\tunsigned long bufsize;\n> +\tconst char *p;\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\tint ret = 0;\n> +\n> +\tthis = lookup_commit_reference(the_repository, oid);\n> +\tbuffer = get_commit_buffer(this, &bufsize);\n> +\torig_author = find_commit_header(buffer, \"author\", &author_len);\n> +\torig_committer = find_commit_header(buffer, \"committer\", &committer_len);\n\nIn builtin/am.c we also start with this, but follow it with passing the\ndata through split_ident_line(), any reason we're not doing the same\nthing here, and are there cases wehre that would result in propagating\nnot-sanity-checked-enough data?\n\n\n> +\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n> +\n> +\tif (!orig_author || !orig_committer || !p) {\n> +\t\tret = error(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n> +\t\tgoto out;\n> +\t}\n> +\t/* Jump to message. */\n> +\tp += 2;\n> +\tstrbuf_addstr(&msg, \"git stash: \");\n> +\tstrbuf_add(&msg, p, bufsize - (p - buffer));\n\nAnd more on those behavior differences, the am.c version seems to not\ncare that we *might* have a \\0 in a commit buffer and just uses\nxstrdup(msg + 2).\n\nBut this one looks like it will faithfully carry that \\0-containing\nmessage forward.\n\nProbably OK, just checking...\n"},{"id":"453493","messageId":"220413.86a6cpezsy.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220407215352.3491567-4-sandals@crustytoothpaste.net","subject":"Re: [PATCH v4 3/4] builtin/stash: provide a way to export stashes to a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-13T15:55:23Z","receivedAt":"2022-04-13T16:03:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Apr 07 2022, brian m. carlson wrote:\n\n> +\t/*\n> +\t * This is an arbitrary, fixed date, specifically the one used by git\n> +\t * format-patch.  The goal is merely to produce reproducible output.\n> +\t */\n> +\tprepare_fallback_ident(\"git stash\", \"git@stash\");\n> +\tauthor = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT,\n> +\t\t\t   \"2001-09-17T00:00:00Z\", 0);\n> +\tcommitter = fmt_ident(\"git stash\", \"git@stash\", WANT_BLANK_IDENT,\n> +\t\t\t      \"2001-09-17T00:00:00Z\", 0);\n\nHrm, reproducable with what? Because you're doing this we won't get a\ndifferent OID every time you do:\n\n    git stash export --print\n\nThat part I'm completely on board with, but not pick the author/date\nfrom the tip of the stash we're exporting instead?\n\nAFAICT the only negative side-effect of that is that we'll create\nanother such \"base\" commit for every stash we export, which is a bit of\ngarbage churn.\n\nBut I think it's worth it to not create magical author/committer entries\nwith nonsensical dates.\n"},{"id":"453508","messageId":"20220413173429.971159-1-jonathantanmy@google.com","threadId":"57531","inReplyTo":"YlYjgLcnNH8V1yj0@camp.crustytoothpaste.net","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-04-13T17:34:28Z","receivedAt":"2022-04-13T17:34:36Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> On 2022-04-12 at 20:14:34, Jonathan Tan wrote:\n> > This seems like you're using the commit message as the reflog message -\n> > is this necessary? For what it's worth, all tests still pass if I\n> > replace \"msg\" with \"NULL\".\n> \n> I think that's what the existing stash code does, and so I did the same\n> here.  It's not strictly necessary, but it's a nice to have.\n> \n> I didn't think it worth testing, because I don't think we test it\n> elsewhere, either.\n> \n> > It might be worth adding tests that check that the exported stashes are\n> > in the expected format (to ensure that we can read stashes exported from\n> > another Git version) but I don't think that has to block the submission\n> > of this patch set.\n> \n> There's a tiny patch for that for the base commit, but you're right that\n> some more tests wouldn't hurt.  I can send a followup patch or two as\n> part of a new series.\n\nOK, these sound good to me.\n"},{"id":"453515","messageId":"220413.865yncg7et.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"YlYjgLcnNH8V1yj0@camp.crustytoothpaste.net","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-13T18:25:20Z","receivedAt":"2022-04-13T18:33:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Apr 13 2022, brian m. carlson wrote:\n\n> [[PGP Signed Part:Undecided]]\n> On 2022-04-12 at 20:14:34, Jonathan Tan wrote:\n>> This seems like you're using the commit message as the reflog message -\n>> is this necessary? For what it's worth, all tests still pass if I\n>> replace \"msg\" with \"NULL\".\n>\n> I think that's what the existing stash code does, and so I did the same\n> here.  It's not strictly necessary, but it's a nice to have.\n\nIn any case: Jonathan changed the code to omit the commit message & the\ntests still passed? Is that to do with:\n\n    We specifically rely on the fact that we'll produce identical stash\n    commits on both sides in our tests.\n\nI.e. that they're checking round-tripping, but not known correctness?\n\nAlso: Maybe I'm missing something but stashes start with \"WIP\" or \"On\",\nbut the export adds a prefix \"git stash: \". That seems like an odd\ninconsistency, why not keep the message as-is as the commit message?\n\nIn any case, the import then takes the message as-is without stripping\nthe \"\"git stash: \" prefix?\n\n"},{"id":"453516","messageId":"220413.861qy0g6yk.gmgdl@evledraar.gmail.com","threadId":"57531","inReplyTo":"20220407215352.3491567-5-sandals@crustytoothpaste.net","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-04-13T18:33:33Z","receivedAt":"2022-04-13T18:43:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Apr 07 2022, brian m. carlson wrote:\n\n[Finding time to go over this in a few passes, so some disjointed\nreplies, sorry]\n\n> +\tfor (i = 0;; i++) {\n> +\t\tstruct object_id tree, oid;\n> +\t\tchar revision[GIT_MAX_HEXSZ + 1];\n> +\n> +\t\toid_to_hex_r(revision, &chain);\n> +\n> +\t\tif (get_oidf(&tree, \"%s:\", revision) ||\n> +\t\t    !oideq(&tree, the_hash_algo->empty_tree)) {\n> +\t\t\treturn error(_(\"%s is not a valid exported stash commit\"), revision);\n\nI think you're leaking memory here, i.e. you're in the for-loop and\ndoing oid_array_append()< but here you won't clear that or do other\n\"out\" free-ing at the end.\n\nBut I also checked if your tests leaked with SANITIZE=leak, and (after\nomitting the existing leaks) they didn't, so either I'm wrong or it's a\ntest blindspot.\n\nHave you tried \"make coverage-report\" with this?\n\n> +\t\t}\n> +\t\tif (get_oidf(&chain, \"%s^1\", revision) ||\n> +\t\t    get_oidf(&oid, \"%s^2\", revision))\n> +\t\t\tbreak;\n> +\t\toid_array_append(&items, &oid);\n> +\t}\n> +\n> +\t/*\n> +\t * Now, walk each entry, adding it to the stash as a normal stash\n> +\t * commit.\n> +\t */\n> +\tfor (i = items.nr - 1; i >= 0; i--) {\n> +\t\tunsigned long bufsize;\n> +\t\tconst char *p;\n> +\t\tconst struct object_id *oid = items.oid + i;\n> +\n> +\t\tthis = lookup_commit_reference(the_repository, oid);\n> +\t\tbuffer = get_commit_buffer(this, &bufsize);\n> +\t\tif (!buffer) {\n> +\t\t\tres = -1;\n> +\t\t\terror(_(\"cannot read commit buffer for %s\"), oid_to_hex(oid));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tp = memmem(buffer, bufsize, \"\\n\\n\", 2);\n\nNit: Grepping in-tree all other API users of get_commit_buffer() just\nuse strstr(buffer, \"\\n\\n\"), if this one needs to handle \\0 specially\n(for reasons I'm missing) perhaps a comment here discussing why?\n\n> +\t\tif (!p) {\n> +\t\t\tres = -1;\n> +\t\t\terror(_(\"cannot parse commit %s\"), oid_to_hex(oid));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tp += 2;\n> +\t\tmsg = xmemdupz(p, bufsize - (p - buffer));\n> +\t\tunuse_commit_buffer(this, buffer);\n> +\t\tbuffer = NULL;\n> +\n> +\t\tif (do_store_stash(oid, msg, 1)) {\n> +\t\t\tres = -1;\n> +\t\t\terror(_(\"cannot save the stash for %s\"), oid_to_hex(oid));\n\nMaybe just \"res = error\" for these? You use that in 3/4, would be good\nto continue the same pattern consistently in 4/4.\n\n> +\t\t\tgoto out;\n> +\t\t}\n> +\t\tFREE_AND_NULL(msg);\n> +\t}\n> +out:\n> +\tif (this && buffer)\n> +\t\tunuse_commit_buffer(this, buffer);\n> +\toid_array_clear(&items);\n> +\tfree(msg);\n> +\n> +\treturn res;\n> +}\n> +\n> +static int import_stash(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct option options[] = {\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\targc = parse_options(argc, argv, prefix, options,\n> +\t\t\t     git_stash_import_usage,\n> +\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n> +\n> +\tif (argc != 1) {\n> +\t\tusage_with_options(git_stash_import_usage, options);\n\nThis function is a NORETURN....\n\n> +\t\treturn -1;\n\n...so this code isn't reachable, and will warn on some compilers (suncc\nat least).\n\nBut consider using usage_msg_opt() instead, i.e. tell the user what went\nwrong.\n"},{"id":"453520","messageId":"20220413191421.984299-1-jonathantanmy@google.com","threadId":"57531","inReplyTo":"220413.865yncg7et.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-04-13T19:14:21Z","receivedAt":"2022-04-13T19:15:58Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n> \n> On Wed, Apr 13 2022, brian m. carlson wrote:\n> \n> > [[PGP Signed Part:Undecided]]\n> > On 2022-04-12 at 20:14:34, Jonathan Tan wrote:\n> >> This seems like you're using the commit message as the reflog message -\n> >> is this necessary? For what it's worth, all tests still pass if I\n> >> replace \"msg\" with \"NULL\".\n> >\n> > I think that's what the existing stash code does, and so I did the same\n> > here.  It's not strictly necessary, but it's a nice to have.\n> \n> In any case: Jonathan changed the code to omit the commit message & the\n> tests still passed? Is that to do with:\n> \n>     We specifically rely on the fact that we'll produce identical stash\n>     commits on both sides in our tests.\n> \n> I.e. that they're checking round-tripping, but not known correctness?\n\nTo clarify: I omitted something in the reflog message, not in any of the\ncommit messages. The round-tripping only checks commit messages (which I\nagree is the important thing). I still do think that the correctness of\nwhat's being exported is important, but brian has already said that he\ncan send a followup as part of a new series [1].\n\n[1] https://lore.kernel.org/git/YlYjgLcnNH8V1yj0@camp.crustytoothpaste.net/\n"},{"id":"453591","messageId":"xmqqzgko7nj4.fsf@gitster.g","threadId":"57531","inReplyTo":"YlYjgLcnNH8V1yj0@camp.crustytoothpaste.net","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-04-13T20:10:07Z","receivedAt":"2022-04-13T20:10:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> On 2022-04-12 at 20:14:34, Jonathan Tan wrote:\n>> This seems like you're using the commit message as the reflog message -\n>> is this necessary? For what it's worth, all tests still pass if I\n>> replace \"msg\" with \"NULL\".\n>\n> I think that's what the existing stash code does, and so I did the same\n> here.  It's not strictly necessary, but it's a nice to have.\n>\n> I didn't think it worth testing, because I don't think we test it\n> elsewhere, either.\n>\n>> It might be worth adding tests that check that the exported stashes are\n>> in the expected format (to ensure that we can read stashes exported from\n>> another Git version) but I don't think that has to block the submission\n>> of this patch set.\n>\n> There's a tiny patch for that for the base commit, but you're right that\n> some more tests wouldn't hurt.  I can send a followup patch or two as\n> part of a new series.\n\nIs this about the log messages recorded in the throw-away commits\nthat are only used to form a single backbone chain, to which the\ncommits used to represent stash entries are linked to?\n\nAre these messages meant to be used in any way?  I do not think\nthese messages contribute anything to the end result (they are just\ndiscarded once they serve their purpose of transferring the\nunderlying stash entries, if I recall the design discussion\ncorrectly), so I am not sure if we would even want to cast in stone\nwhat they would say.\n\nIf on the other hand they are meant to be read by something (either\nprograms or end-user humans), it does make sense to ensure that we\nare recording what we think we are recording.\n\nThanks.\n"},{"id":"453603","messageId":"YldBlydglKkBhR9y@camp.crustytoothpaste.net","threadId":"57531","inReplyTo":"xmqqzgko7nj4.fsf@gitster.g","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-04-13T21:33:11Z","receivedAt":"2022-04-13T21:33:16Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-04-13 at 20:10:07, Junio C Hamano wrote:\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> \n> > On 2022-04-12 at 20:14:34, Jonathan Tan wrote:\n> >> This seems like you're using the commit message as the reflog message -\n> >> is this necessary? For what it's worth, all tests still pass if I\n> >> replace \"msg\" with \"NULL\".\n> >\n> > I think that's what the existing stash code does, and so I did the same\n> > here.  It's not strictly necessary, but it's a nice to have.\n> >\n> > I didn't think it worth testing, because I don't think we test it\n> > elsewhere, either.\n> >\n> >> It might be worth adding tests that check that the exported stashes are\n> >> in the expected format (to ensure that we can read stashes exported from\n> >> another Git version) but I don't think that has to block the submission\n> >> of this patch set.\n> >\n> > There's a tiny patch for that for the base commit, but you're right that\n> > some more tests wouldn't hurt.  I can send a followup patch or two as\n> > part of a new series.\n> \n> Is this about the log messages recorded in the throw-away commits\n> that are only used to form a single backbone chain, to which the\n> commits used to represent stash entries are linked to?\n\nMy response to the first paragraph was talking about the messages in the\nreflog.  When we create a reflog entry, we add a message, which I've set\nto the commit message of the stash entry, like the existing code does.\nI don't think that's an important detail, but I did it to be consistent.\nI think it's better to put something useful there, at least, rather than\nnot put any message at all.\n\nThe log messages recorded in the chain of throwaway commits are\nidentical to the corresponding stash commit's message with a prefix of\n\"git stash: \".  There's currently a test for the base commit having a\ncertain fixed value, but not the contents of the other commits.\n\n> Are these messages meant to be used in any way?  I do not think\n> these messages contribute anything to the end result (they are just\n> discarded once they serve their purpose of transferring the\n> underlying stash entries, if I recall the design discussion\n> correctly), so I am not sure if we would even want to cast in stone\n> what they would say.\n> \n> If on the other hand they are meant to be read by something (either\n> programs or end-user humans), it does make sense to ensure that we\n> are recording what we think we are recording.\n\nThe log (not reflog) messages are valuable because since these message\nare pushed as refs, someone may look at them (e.g., on GitHub or with\ngit log) and it is helpful to provide something that tells the user\nwhat's going on.\n\nFor example, since our throwaway commits are empty, it would seem\nbizarre to users that someone pushed a commit with an empty tree, but if\nthey see that the commits are stash commits, that may help them have\nuseful context.\n\nI'm happy to add a few more tests for this in a followup series.  I'll\nlikely get to it this weekend and can include some checks for the commit\nmessage and some improved verification of commits in the testsuite.  I\ndon't think this should hold up the rest of the series, however.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"453604","messageId":"xmqq1qy07j6v.fsf@gitster.g","threadId":"57531","inReplyTo":"YldBlydglKkBhR9y@camp.crustytoothpaste.net","subject":"Re: [PATCH v4 4/4] builtin/stash: provide a way to import stashes from a ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-04-13T21:43:52Z","receivedAt":"2022-04-13T21:44:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> For example, since our throwaway commits are empty, it would seem\n> bizarre to users that someone pushed a commit with an empty tree, but if\n> they see that the commits are stash commits, that may help them have\n> useful context.\n\nUnderstood.  Also recording the tree of the state the stash entry\nrepresents the working tree state (instead of an empty tree) might\nmake it easier to see.  Then even in UI like GitHub web interface,\nthe users can inspect how various paths in the tree looks like.\n\n> I'm happy to add a few more tests for this in a followup series.  I'll\n> likely get to it this weekend and can include some checks for the commit\n> message and some improved verification of commits in the testsuite.  I\n> don't think this should hold up the rest of the series, however.\n\nTHanks.\n\n"}]}