{"thread":{"id":"6936","subject":"[PATCH] object name: introduce ':/<oneline prefix>' notation","startedAt":"2007-02-24T02:08:20Z","lastAt":"2007-02-24T02:08:20Z","messageCount":1,"participants":["Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"35378","messageId":"Pine.LNX.4.63.0702240307460.22628@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"6936","inReplyTo":null,"subject":"[PATCH] object name: introduce ':/<oneline prefix>' notation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-02-24T02:08:20Z","receivedAt":"2007-02-24T02:08:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nTo name a commit, you can now say\n\n\t$ git rev-parse ':/Initial revision of \"git\"'\n\nand it will return the hash of the youngest commit whose\ncommit message (the oneline) begins with the given prefix.\n\nFor future extension, a leading exclamation mark is treated\nspecially: if you want to match a commit message starting with\na '!', just repeat the exclamation mark. So, to match a commit\nwhich starts with '!Hello World', use\n\n\t$ git show ':/!!Hello World'\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tThis obviously replaces the earlier patch.\n\n Documentation/git-rev-parse.txt |    7 +++++\n sha1_name.c                     |   58 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 65 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 081b9d6..41b8a51 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -193,6 +193,13 @@ blobs contained in a commit.\n   and dereference the tag recursively until a non-tag object is\n   found.\n \n+* A colon, followed by a slash, followed by a text: this names\n+  a commit whose commit message starts with the specified text.\n+  This name returns the youngest matching commit which is\n+  reachable from any ref.  If the commit message starts with a\n+  '!', you have to repeat that;  the special sequence ':/!',\n+  followed by something else than '!' is reserved for now.\n+\n * A suffix ':' followed by a path; this names the blob or tree\n   at the given path in the tree-ish object named by the part\n   before the colon.\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 2793de9..6ad2662 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -578,6 +578,62 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)\n \treturn get_short_sha1(name, len, sha1, 0);\n }\n \n+static int handle_one_ref(const char *path,\n+\t\tconst unsigned char *sha1, int flag, void *cb_data)\n+{\n+\tstruct commit_list **list = cb_data;\n+\tstruct object *object = parse_object(sha1);\n+\tif (!object)\n+\t\treturn 0;\n+\tif (object->type == OBJ_TAG)\n+\t\tobject = deref_tag(object, path, strlen(path));\n+\tif (object->type != OBJ_COMMIT)\n+\t\treturn 0;\n+\tinsert_by_date((struct commit *)object, list);\n+\treturn 0;\n+}\n+\n+/*\n+ * This interprets names like ':/Initial revision of \"git\"' by searching\n+ * through history and returning the first commit whose message starts\n+ * with the given string.\n+ *\n+ * For future extension, ':/!' is reserved. If you want to match a message\n+ * beginning with a '!', you have to repeat the exclamation mark.\n+ */\n+\n+#define ONELINE_SEEN (1u<<20)\n+int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n+{\n+\tstruct commit_list *list = NULL, *backup = NULL, *l;\n+\tstruct commit *commit;\n+\n+\tif (prefix[0] == '!') {\n+\t\tif (prefix[1] != '!')\n+\t\t\tdie (\"Invalid search pattern: %s\", prefix);\n+\t\tprefix++;\n+\t}\n+\tif (!save_commit_buffer)\n+\t\treturn error(\"Could not expand oneline-name.\");\n+\tfor_each_ref(handle_one_ref, &list);\n+\tfor (l = list; l; l = l->next)\n+\t\tcommit_list_insert(l->item, &backup);\n+\twhile ((commit = pop_most_recent_commit(&list, ONELINE_SEEN))) {\n+\t\tchar *p;\n+\t\tparse_object(commit->object.sha1);\n+\t\tif (!commit->buffer || !(p = strstr(commit->buffer, \"\\n\\n\")))\n+\t\t\tcontinue;\n+\t\tif (!prefixcmp(p + 2, prefix)) {\n+\t\t\thashcpy(sha1, commit->object.sha1);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tfree_commit_list(list);\n+\tfor (l = backup; l; l = l->next)\n+\t\tclear_commit_marks(l->item, ONELINE_SEEN);\n+\treturn commit == NULL;\n+}\n+\n /*\n  * This is like \"get_sha1_basic()\", except it allows \"sha1 expressions\",\n  * notably \"xyz^\" for \"parent of xyz\"\n@@ -601,6 +657,8 @@ int get_sha1(const char *name, unsigned char *sha1)\n \t\tint stage = 0;\n \t\tstruct cache_entry *ce = NULL;\n \t\tint pos;\n+\t\tif (namelen > 2 && name[1] == '/')\n+\t\t\treturn get_sha1_oneline(name + 2, sha1);\n \t\tif (namelen < 3 ||\n \t\t    name[2] != ':' ||\n \t\t    name[1] < '0' || '3' < name[1])\n-- \n1.5.0.1.2250.g8afc1-dirty\n"}]}