{"thread":{"id":"24155","subject":"Proposal for git stash rename","startedAt":"2010-06-20T09:31:42Z","lastAt":"2013-01-09T08:38:16Z","messageCount":6,"participants":["Greg Hewgill","Ævar Arnfjörð Bjarmason","Micheil Smith","Junio C Hamano","Michael Haggerty"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"143951","messageId":"20100620093142.GF24805@occam.hewgill.net","threadId":"24155","inReplyTo":null,"subject":"Proposal for git stash rename","fromName":"Greg Hewgill","fromEmail":"greg@hewgill.com","sentAt":"2010-06-20T09:31:42Z","receivedAt":"2010-06-20T09:31:42Z","isPatch":false,"sender":{"key":"greg@hewgill.com","avatar":null},"body":"I'd like to teach git-stash to rename stashes. Sometimes I'll create a\nstash and then later think of a better or more descriptive name for it.\nOr, I'll create a stash with \"git stash -p\" and forget to give it a\nuseful name, and there's no easy way to undo and redo that without\ndoing the interactive part again.\n\nI noticed that although the stash message is used as the commit\ndescription for the stash commit(s), it is the message in the reflog\nthat is actually displayed on \"git stash list\". So to rename a stash,\nchanging the message in the reflog is sufficient (this can be\ndemonstrated with a text editor).\n\nMy general idea is:\n\n1. Implement a new \"git reflog update\" command that updates the message\nassociated with a specific reflog entry. To do this, a new\nupdate_reflog_ent() function (in reflog.c) would change the message\nassociated with the specific reflog entry to update. An update_reflog()\nfunction would use for_each_reflog_ent() with update_reflog_ent to\nactually do the change.\n\n2. A \"git stash rename\" command would then only need to call \"git\nreflog update\" with the appropriate ref and new message.\n\nSince this is my first time modifying the Git source, I thought I'd\nsolicit some feedback on this idea.\n\n- Is this an appropriate approach to implementing this?\n- Is there a better way to do it?\n- I have a mostly working patch now, but it needs cleanup. What's the\n  next step?\n\nGreg Hewgill\nhttp://hewgill.com\n"},{"id":"143952","messageId":"AANLkTimjEksHI4nlefqmNzdNiseVyMQcw312g2AHFQDZ@mail.gmail.com","threadId":"24155","inReplyTo":"20100620093142.GF24805@occam.hewgill.net","subject":"Re: Proposal for git stash rename","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-06-20T10:54:43Z","receivedAt":"2010-06-20T10:54:43Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Sun, Jun 20, 2010 at 09:31, Greg Hewgill <greg@hewgill.com> wrote:\n> - Is this an appropriate approach to implementing this?\n> - Is there a better way to do it?\n\nNo idea.\n\n> - I have a mostly working patch now, but it needs cleanup. What's the\n>  next step?\n\nIt's good to post a WIP PATCH even if it needs cleanup, just as a\npoint for further discussion.\n"},{"id":"143953","messageId":"20100620111112.GH24805@occam.hewgill.net","threadId":"24155","inReplyTo":"AANLkTimjEksHI4nlefqmNzdNiseVyMQcw312g2AHFQDZ@mail.gmail.com","subject":"Re: Proposal for git stash rename","fromName":"Greg Hewgill","fromEmail":"greg@hewgill.com","sentAt":"2010-06-20T11:11:12Z","receivedAt":"2010-06-20T11:11:12Z","isPatch":false,"sender":{"key":"greg@hewgill.com","avatar":null},"body":"On Sun, Jun 20, 2010 at 10:54:43AM +0000, ??var Arnfj??r?? Bjarmason wrote:\n> It's good to post a WIP PATCH even if it needs cleanup, just as a\n> point for further discussion.\n\nThanks, point taken. WIP patch follows.\n\nThis patch implements a \"git stash rename\" using a new\n\"git reflog update\" command that updates the message associated\nwith a reflog entry.\n---\n builtin/reflog.c |  149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n git-stash.sh     |    5 ++\n t/t3903-stash.sh |   10 ++++\n 3 files changed, 164 insertions(+), 0 deletions(-)\n\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex ebf610e..35eae1f 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -7,6 +7,7 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"reachable.h\"\n+#include \"strbuf.h\"\n \n /*\n  * reflog expire\n@@ -16,6 +17,8 @@ static const char reflog_expire_usage[] =\n \"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...\";\n static const char reflog_delete_usage[] =\n \"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...\";\n+static const char reflog_update_usage[] =\n+\"git reflog update [--verbose] [--dry-run] [--rewrite] <ref> <newdescr>\";\n \n static unsigned long default_reflog_expire;\n static unsigned long default_reflog_expire_unreachable;\n@@ -30,6 +33,7 @@ struct cmd_reflog_expire_cb {\n \tunsigned long expire_total;\n \tunsigned long expire_unreachable;\n \tint recno;\n+\tstruct strbuf newmsg;\n };\n \n struct expire_reflog_cb {\n@@ -335,6 +339,30 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,\n \treturn 0;\n }\n \n+static int update_reflog_ent(unsigned char *osha1, unsigned char *nsha1,\n+\t\tconst char *email, unsigned long timestamp, int tz,\n+\t\tconst char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_cb *cb = cb_data;\n+\n+\tif (cb->cmd->rewrite)\n+\t\tosha1 = cb->last_kept_sha1;\n+\n+\tif (cb->cmd->recno && --(cb->cmd->recno) == 0)\n+\t\tmessage = cb->cmd->newmsg.buf;\n+\n+\tif (cb->newlog) {\n+\t\tchar sign = (tz < 0) ? '-' : '+';\n+\t\tint zone = (tz < 0) ? (-tz) : tz;\n+\t\tfprintf(cb->newlog, \"%s %s %s %lu %c%04d\\t%s\",\n+\t\t\tsha1_to_hex(osha1), sha1_to_hex(nsha1),\n+\t\t\temail, timestamp, sign, zone,\n+\t\t\tmessage);\n+\t\thashcpy(cb->last_kept_sha1, nsha1);\n+\t}\n+\treturn 0;\n+}\n+\n static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data)\n {\n \tstruct commit_list **list = cb_data;\n@@ -448,6 +476,65 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \treturn status;\n }\n \n+static int update_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)\n+{\n+\tstruct cmd_reflog_expire_cb *cmd = cb_data;\n+\tstruct expire_reflog_cb cb;\n+\tstruct ref_lock *lock;\n+\tchar *log_file, *newlog_path = NULL;\n+\tint status = 0;\n+\n+\tmemset(&cb, 0, sizeof(cb));\n+\n+\t/*\n+\t * we take the lock for the ref itself to prevent it from\n+\t * getting updated.\n+\t */\n+\tlock = lock_any_ref_for_update(ref, sha1, 0);\n+\tif (!lock)\n+\t\treturn error(\"cannot lock ref '%s'\", ref);\n+\tlog_file = git_pathdup(\"logs/%s\", ref);\n+\tif (!file_exists(log_file))\n+\t\tgoto finish;\n+\tif (!cmd->dry_run) {\n+\t\tnewlog_path = git_pathdup(\"logs/%s.lock\", ref);\n+\t\tcb.newlog = fopen(newlog_path, \"w\");\n+\t}\n+\n+\tcb.cmd = cmd;\n+\n+\tfor_each_reflog_ent(ref, update_reflog_ent, &cb);\n+\n+ finish:\n+\tif (cb.newlog) {\n+\t\tif (fclose(cb.newlog)) {\n+\t\t\tstatus |= error(\"%s: %s\", strerror(errno),\n+\t\t\t\t\tnewlog_path);\n+\t\t\tunlink(newlog_path);\n+\t\t} else if (cmd->updateref &&\n+\t\t\t(write_in_full(lock->lock_fd,\n+\t\t\t\tsha1_to_hex(cb.last_kept_sha1), 40) != 40 ||\n+\t\t\t write_str_in_full(lock->lock_fd, \"\\n\") != 1 ||\n+\t\t\t close_ref(lock) < 0)) {\n+\t\t\tstatus |= error(\"Couldn't write %s\",\n+\t\t\t\tlock->lk->filename);\n+\t\t\tunlink(newlog_path);\n+\t\t} else if (rename(newlog_path, log_file)) {\n+\t\t\tstatus |= error(\"cannot rename %s to %s\",\n+\t\t\t\t\tnewlog_path, log_file);\n+\t\t\tunlink(newlog_path);\n+\t\t} else if (cmd->updateref && commit_ref(lock)) {\n+\t\t\tstatus |= error(\"Couldn't set %s\", lock->ref_name);\n+\t\t} else {\n+\t\t\tadjust_shared_perm(log_file);\n+\t\t}\n+\t}\n+\tfree(newlog_path);\n+\tfree(log_file);\n+\tunlock_ref(lock);\n+\treturn status;\n+}\n+\n static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)\n {\n \tstruct collected_reflog *e;\n@@ -752,6 +839,65 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n+static int cmd_reflog_update(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct cmd_reflog_expire_cb cb;\n+\tint i, status = 0;\n+\n+\tmemset(&cb, 0, sizeof(cb));\n+\n+\tfor (i = 1; i < argc; i++) {\n+\t\tconst char *arg = argv[i];\n+\t\tif (!strcmp(arg, \"--dry-run\") || !strcmp(arg, \"-n\"))\n+\t\t\tcb.dry_run = 1;\n+\t\telse if (!strcmp(arg, \"--rewrite\"))\n+\t\t\tcb.rewrite = 1;\n+\t\telse if (!strcmp(arg, \"--verbose\"))\n+\t\t\tcb.verbose = 1;\n+\t\telse if (!strcmp(arg, \"--\")) {\n+\t\t\ti++;\n+\t\t\tbreak;\n+\t\t}\n+\t\telse if (arg[0] == '-')\n+\t\t\tusage(reflog_update_usage);\n+\t\telse\n+\t\t\tbreak;\n+\t}\n+\n+\tif (argc - i < 2)\n+\t\tusage(reflog_update_usage);\n+\n+\tstrbuf_init(&cb.newmsg, strlen(argv[i+1])+1);\n+\tstrbuf_addstr(&cb.newmsg, argv[i+1]);\n+\tstrbuf_addstr(&cb.newmsg, \"\\n\");\n+\n+\tconst char *spec = strstr(argv[i], \"@{\");\n+\tunsigned char sha1[20];\n+\tchar *ep, *ref;\n+\tint recno;\n+\n+\tif (!spec) {\n+\t\treturn error(\"Not a reflog: %s\", argv[i]);\n+\t}\n+\n+\tif (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {\n+\t\treturn error(\"no reflog for '%s'\", argv[i]);\n+\t}\n+\n+\trecno = strtoul(spec + 2, &ep, 10);\n+\tif (*ep == '}') {\n+\t\tcb.recno = -recno;\n+\t\tfor_each_reflog_ent(ref, count_reflog_ent, &cb);\n+\t} else {\n+\t\treturn error(\"specific ref please\");\n+\t}\n+\n+\tstatus |= update_reflog(ref, sha1, 0, &cb);\n+\tfree(ref);\n+\n+\treturn status;\n+}\n+\n /*\n  * main \"reflog\"\n  */\n@@ -777,6 +923,9 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)\n \tif (!strcmp(argv[1], \"delete\"))\n \t\treturn cmd_reflog_delete(argc - 1, argv + 1, prefix);\n \n+\tif (!strcmp(argv[1], \"update\"))\n+\t\treturn cmd_reflog_update(argc - 1, argv + 1, prefix);\n+\n \t/* Not a recognized reflog command..*/\n \tusage(reflog_usage);\n }\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 1d95447..aa80897 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -8,6 +8,7 @@ USAGE=\"list [<options>]\n    or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n    or: $dashless branch <branchname> [<stash>]\n    or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]\n+   or: $dashless rename <stash> <message>\n    or: $dashless clear\"\n \n SUBDIRECTORY_OK=Yes\n@@ -431,6 +432,10 @@ branch)\n \tshift\n \tapply_to_branch \"$@\"\n \t;;\n+rename)\n+\tshift\n+\tgit reflog update $1 \"$2\"\n+\t;;\n *)\n \tcase $# in\n \t0)\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 8fe14cc..c0de00c 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -378,4 +378,14 @@ test_expect_failure 'stash file to directory' '\n \ttest foo = \"$(cat file/file)\"\n '\n \n+test_expect_success 'update stash message' '\n+\tgit reset --hard &&\n+\tgit checkout master &&\n+\techo foo >file &&\n+\tgit stash save \"first message\" &&\n+\tgit stash list | grep \"^stash@{0}: On master: first message$\" &&\n+\tgit stash rename stash@{0} \"second message\" &&\n+\tgit stash list | grep \"^stash@{0}: second message$\"\n+'\n+\n test_done\n-- \n1.6.6\n"},{"id":"205973","messageId":"loom.20130104T192132-16@post.gmane.org","threadId":"24155","inReplyTo":"20100620111112.GH24805@occam.hewgill.net","subject":"Re: Proposal for git stash rename","fromName":"Micheil Smith","fromEmail":"micheil@brandedcode.com","sentAt":"2013-01-04T18:25:07Z","receivedAt":"2013-01-04T18:25:07Z","isPatch":false,"sender":{"key":"micheil@brandedcode.com","avatar":null},"body":"Greg Hewgill <greg <at> hewgill.com> writes:\n\n> \n> On Sun, Jun 20, 2010 at 10:54:43AM +0000, ??var Arnfj??r?? Bjarmason wrote:\n> > It's good to post a WIP PATCH even if it needs cleanup, just as a\n> > point for further discussion.\n> \n> Thanks, point taken. WIP patch follows.\n> \n> This patch implements a \"git stash rename\" using a new\n> \"git reflog update\" command that updates the message associated\n> with a reflog entry.\n> ---\n> [--snip--]\n\nHi, \n\nI note that this proposal is now two years old. A work in progress patch was \nrequested, however, after one was given this thread ended. I'm also finding \na need for this feature;\n\nNot to try and bump an old thread, but what's the best way to land this?\n\n– Micheil Smith\n@miksago\n"},{"id":"205982","messageId":"7vbod4tynt.fsf@alter.siamese.dyndns.org","threadId":"24155","inReplyTo":"loom.20130104T192132-16@post.gmane.org","subject":"Re: Proposal for git stash rename","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-04T21:40:22Z","receivedAt":"2013-01-04T21:40:22Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Micheil Smith <micheil@brandedcode.com> writes:\n\n>> This patch implements a \"git stash rename\" using a new\n>> \"git reflog update\" command that updates the message associated\n>> with a reflog entry.\n> ...\n> I note that this proposal is now two years old. A work in progress patch was \n> requested, however, after one was given this thread ended. I'm also finding \n> a need for this feature;\n\nThe whole point of reflog is that it is a mechanism to let users to\ngo safely back to the previous state, by using a file that is pretty\nmuch append-only.  It feels that a mechanism to \"rewrite\" one goes\ncompletely against that principle, at least to me.\n\nI have a feeling that \"need\" in \"need for this feature\" is a\nmisspelt \"want\", that occasional misspelling of the stash message\nmay give users awkward feelings when viewing \"git stash list\" output\nbut not severe enough to make them unable to identify which stash\nentry holds which change, and that it is sufficient to pop and then\nrestash if a user *really* cares.\n"},{"id":"206376","messageId":"50ED2C78.1030300@alum.mit.edu","threadId":"24155","inReplyTo":"7vbod4tynt.fsf@alter.siamese.dyndns.org","subject":"Re: Proposal for git stash rename","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-01-09T08:38:16Z","receivedAt":"2013-01-09T08:38:16Z","isPatch":false,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 01/04/2013 10:40 PM, Junio C Hamano wrote:\n> Micheil Smith <micheil@brandedcode.com> writes:\n> \n>>> This patch implements a \"git stash rename\" using a new\n>>> \"git reflog update\" command that updates the message associated\n>>> with a reflog entry.\n>> ...\n>> I note that this proposal is now two years old. A work in progress patch was \n>> requested, however, after one was given this thread ended. I'm also finding \n>> a need for this feature;\n> \n> The whole point of reflog is that it is a mechanism to let users to\n> go safely back to the previous state, by using a file that is pretty\n> much append-only.  It feels that a mechanism to \"rewrite\" one goes\n> completely against that principle, at least to me.\n\nThe implementation of \"git stash\" itself seems to violate your\nprinciple, by storing its branches-that-are-not-branches within a\nmutable reflog.\n\nJust an observation...\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"}]}