{"thread":{"id":"55818","subject":"[PATCH 0/2] [GSOC] ref-filter: add %(raw) atom","startedAt":"2021-06-01T14:37:28Z","lastAt":"2021-06-05T08:06:20Z","messageCount":23,"participants":["ZheNing Hu via GitGitGadget","Junio C Hamano","ZheNing Hu","Bagas Sanjaya","Christian Couder"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"426130","messageId":"pull.966.git.1622558243.gitgitgadget@gmail.com","threadId":"55818","inReplyTo":null,"subject":"[PATCH 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-06-01T14:37:21Z","receivedAt":"2021-06-01T14:37:28Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In order to make git cat-file --batch use ref-filter logic, I add %(raw)\natom to ref-filter.\n\nChange from last version:\n\n 1. Change is_empty() logic.\n 2. Simplify memcasecmp().\n 3. rebase on zh/ref-filter-atom-type.\n\nZheNing Hu (2):\n  [GSOC] ref-filter: add obj-type check in grab contents\n  [GSOC] ref-filter: add %(raw) atom\n\n Documentation/git-for-each-ref.txt |   9 ++\n ref-filter.c                       | 164 +++++++++++++++++------\n t/t6300-for-each-ref.sh            | 207 +++++++++++++++++++++++++++++\n 3 files changed, 343 insertions(+), 37 deletions(-)\n\n\nbase-commit: 1197f1a46360d3ae96bd9c15908a3a6f8e562207\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-966%2Fadlternative%2Fref-filter-raw-atom-v4-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-966/adlternative/ref-filter-raw-atom-v4-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/966\n-- \ngitgitgadget\n"},{"id":"426131","messageId":"97955705c22e00a718a8de7555ab7e0e401e792e.1622558243.git.gitgitgadget@gmail.com","threadId":"55818","inReplyTo":"pull.966.git.1622558243.gitgitgadget@gmail.com","subject":"[PATCH 1/2] [GSOC] ref-filter: add obj-type check in grab contents","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-06-01T14:37:22Z","receivedAt":"2021-06-01T14:37:29Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nOnly tag and commit objects use `grab_sub_body_contents()` to grab\nobject contents in the current codebase.  We want to teach the\nfunction to also handle blobs and trees to get their raw data,\nwithout parsing a blob (whose contents looks like a commit or a tag)\nincorrectly as a commit or a tag.\n\nSkip the block of code that is specific to handling commits and tags\nearly when the given object is of a wrong type to help later\naddition to handle other types of objects in this function.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Hariom Verma <hariom18599@gmail.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n ref-filter.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 4db0e40ff4c6..c0334857653a 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1356,7 +1356,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n }\n \n /* See grab_values */\n-static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n+static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n+\t\t\t\t   struct object *obj)\n {\n \tint i;\n \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n@@ -1371,10 +1372,13 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n-\t\tif (strcmp(name, \"body\") &&\n-\t\t    !starts_with(name, \"subject\") &&\n-\t\t    !starts_with(name, \"trailers\") &&\n-\t\t    !starts_with(name, \"contents\"))\n+\n+\t\tif ((obj->type != OBJ_TAG &&\n+\t\t     obj->type != OBJ_COMMIT) ||\n+\t\t    (strcmp(name, \"body\") &&\n+\t\t     !starts_with(name, \"subject\") &&\n+\t\t     !starts_with(name, \"trailers\") &&\n+\t\t     !starts_with(name, \"contents\")))\n \t\t\tcontinue;\n \t\tif (!subpos)\n \t\t\tfind_subpos(buf,\n@@ -1443,12 +1447,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v\n \tswitch (obj->type) {\n \tcase OBJ_TAG:\n \t\tgrab_tag_values(val, deref, obj);\n-\t\tgrab_sub_body_contents(val, deref, buf);\n+\t\tgrab_sub_body_contents(val, deref, buf, obj);\n \t\tgrab_person(\"tagger\", val, deref, buf);\n \t\tbreak;\n \tcase OBJ_COMMIT:\n \t\tgrab_commit_values(val, deref, obj);\n-\t\tgrab_sub_body_contents(val, deref, buf);\n+\t\tgrab_sub_body_contents(val, deref, buf, obj);\n \t\tgrab_person(\"author\", val, deref, buf);\n \t\tgrab_person(\"committer\", val, deref, buf);\n \t\tbreak;\n-- \ngitgitgadget\n\n"},{"id":"426132","messageId":"5a94705cdbc101169488919e35613d723e6ec581.1622558243.git.gitgitgadget@gmail.com","threadId":"55818","inReplyTo":"pull.966.git.1622558243.gitgitgadget@gmail.com","subject":"[PATCH 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-06-01T14:37:23Z","receivedAt":"2021-06-01T14:37:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nAdd new formatting option `%(raw)`, which will print the raw\nobject data without any changes. It will help further to migrate\nall cat-file formatting logic from cat-file to ref-filter.\n\nThe raw data of blob, tree objects may contain '\\0', but most of\nthe logic in `ref-filter` depands on the output of the atom being\ntext (specifically, no embedded NULs in it).\n\nE.g. `quote_formatting()` use `strbuf_addstr()` or `*._quote_buf()`\nadd the data to the buffer. The raw data of a tree object is\n`100644 one\\0...`, only the `100644 one` will be added to the buffer,\nwhich is incorrect.\n\nTherefore, add a new member in `struct atom_value`: `s_size`, which\ncan record raw object size, it can help us add raw object data to\nthe buffer or compare two buffers which contain raw object data.\n\nBeyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,\n`--tcl`, `--perl` because if our binary raw data is passed to a variable\nin the host language, the host language may not support arbitrary binary\ndata in the variables of its string type.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Hariom Verma <hariom18599@gmail.com>\nHelped-by: Felipe Contreras <felipe.contreras@gmail.com>\nHelped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nBased-on-patch-by: Olga Telezhnaya <olyatelezhnaya@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-for-each-ref.txt |   9 ++\n ref-filter.c                       | 152 ++++++++++++++++-----\n t/t6300-for-each-ref.sh            | 207 +++++++++++++++++++++++++++++\n 3 files changed, 335 insertions(+), 33 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 2ae2478de706..8f8d8cd1e04f 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -235,6 +235,15 @@ and `date` to extract the named component.  For email fields (`authoremail`,\n without angle brackets, and `:localpart` to get the part before the `@` symbol\n out of the trimmed email.\n \n+The raw data in a object is `raw`.\n+\n+raw:size::\n+\tThe raw data size of the object.\n+\n+Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,\n+`--perl` because the host language may not support arbitrary binary data in the\n+variables of its string type.\n+\n The message in a commit or a tag object is `contents`, from which\n `contents:<part>` can be used to extract various parts out of:\n \ndiff --git a/ref-filter.c b/ref-filter.c\nindex c0334857653a..4d053a9e14ba 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -144,6 +144,7 @@ enum atom_type {\n \tATOM_BODY,\n \tATOM_TRAILERS,\n \tATOM_CONTENTS,\n+\tATOM_RAW,\n \tATOM_UPSTREAM,\n \tATOM_PUSH,\n \tATOM_SYMREF,\n@@ -189,6 +190,9 @@ static struct used_atom {\n \t\t\tstruct process_trailer_options trailer_opts;\n \t\t\tunsigned int nlines;\n \t\t} contents;\n+\t\tstruct {\n+\t\t\tenum { RAW_BARE, RAW_LENGTH } option;\n+\t\t} raw_data;\n \t\tstruct {\n \t\t\tcmp_status cmp_status;\n \t\t\tconst char *str;\n@@ -426,6 +430,18 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato\n \treturn 0;\n }\n \n+static int raw_atom_parser(const struct ref_format *format, struct used_atom *atom,\n+\t\t\t\tconst char *arg, struct strbuf *err)\n+{\n+\tif (!arg)\n+\t\tatom->u.raw_data.option = RAW_BARE;\n+\telse if (!strcmp(arg, \"size\"))\n+\t\tatom->u.raw_data.option = RAW_LENGTH;\n+\telse\n+\t\treturn strbuf_addf_ret(err, -1, _(\"unrecognized %%(raw) argument: %s\"), arg);\n+\treturn 0;\n+}\n+\n static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,\n \t\t\t   const char *arg, struct strbuf *err)\n {\n@@ -586,6 +602,7 @@ static struct {\n \t[ATOM_BODY] = { \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n \t[ATOM_TRAILERS] = { \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n \t[ATOM_CONTENTS] = { \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n+\t[ATOM_RAW] = { \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n \t[ATOM_UPSTREAM] = { \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t[ATOM_PUSH] = { \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t[ATOM_SYMREF] = { \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n@@ -620,12 +637,15 @@ struct ref_formatting_state {\n \n struct atom_value {\n \tconst char *s;\n+\tsize_t s_size;\n \tint (*handler)(struct atom_value *atomv, struct ref_formatting_state *state,\n \t\t       struct strbuf *err);\n \tuintmax_t value; /* used for sorting when not FIELD_STR */\n \tstruct used_atom *atom;\n };\n \n+#define ATOM_VALUE_S_SIZE_INIT (-1)\n+\n /*\n  * Used to parse format string and sort specifiers\n  */\n@@ -644,13 +664,6 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n \t\treturn strbuf_addf_ret(err, -1, _(\"malformed field name: %.*s\"),\n \t\t\t\t       (int)(ep-atom), atom);\n \n-\t/* Do we have the atom already used elsewhere? */\n-\tfor (i = 0; i < used_atom_cnt; i++) {\n-\t\tint len = strlen(used_atom[i].name);\n-\t\tif (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n-\t\t\treturn i;\n-\t}\n-\n \t/*\n \t * If the atom name has a colon, strip it and everything after\n \t * it off - it specifies the format for this entry, and\n@@ -660,6 +673,17 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n \targ = memchr(sp, ':', ep - sp);\n \tatom_len = (arg ? arg : ep) - sp;\n \n+\tif (format->quote_style && !strncmp(sp, \"raw\", 3) && !arg)\n+\t\treturn strbuf_addf_ret(err, -1, _(\"--format=%.*s cannot be used with\"\n+\t\t\t\t\"--python, --shell, --tcl, --perl\"), (int)(ep-atom), atom);\n+\n+\t/* Do we have the atom already used elsewhere? */\n+\tfor (i = 0; i < used_atom_cnt; i++) {\n+\t\tint len = strlen(used_atom[i].name);\n+\t\tif (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n+\t\t\treturn i;\n+\t}\n+\n \t/* Is the atom a valid one? */\n \tfor (i = 0; i < ARRAY_SIZE(valid_atom); i++) {\n \t\tint len = strlen(valid_atom[i].name);\n@@ -709,11 +733,14 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n \treturn at;\n }\n \n-static void quote_formatting(struct strbuf *s, const char *str, int quote_style)\n+static void quote_formatting(struct strbuf *s, const char *str, size_t len, int quote_style)\n {\n \tswitch (quote_style) {\n \tcase QUOTE_NONE:\n-\t\tstrbuf_addstr(s, str);\n+\t\tif (len != ATOM_VALUE_S_SIZE_INIT)\n+\t\t\tstrbuf_add(s, str, len);\n+\t\telse\n+\t\t\tstrbuf_addstr(s, str);\n \t\tbreak;\n \tcase QUOTE_SHELL:\n \t\tsq_quote_buf(s, str);\n@@ -740,9 +767,12 @@ static int append_atom(struct atom_value *v, struct ref_formatting_state *state,\n \t * encountered.\n \t */\n \tif (!state->stack->prev)\n-\t\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\t\tquote_formatting(&state->stack->output, v->s, v->s_size, state->quote_style);\n \telse\n-\t\tstrbuf_addstr(&state->stack->output, v->s);\n+\t\tif (v->s_size != ATOM_VALUE_S_SIZE_INIT)\n+\t\t\tstrbuf_add(&state->stack->output, v->s, v->s_size);\n+\t\telse\n+\t\t\tstrbuf_addstr(&state->stack->output, v->s);\n \treturn 0;\n }\n \n@@ -842,21 +872,23 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state\n \treturn 0;\n }\n \n-static int is_empty(const char *s)\n+static int is_empty(struct strbuf *buf)\n {\n-\twhile (*s != '\\0') {\n-\t\tif (!isspace(*s))\n-\t\t\treturn 0;\n-\t\ts++;\n-\t}\n-\treturn 1;\n-}\n+\tconst char *cur = buf->buf;\n+\tconst char *end = buf->buf + buf->len;\n+\n+\twhile (cur != end && (isspace(*cur)))\n+\t\tcur++;\n+\n+\treturn cur == end;\n+ }\n \n static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,\n \t\t\t     struct strbuf *err)\n {\n \tstruct ref_formatting_stack *cur = state->stack;\n \tstruct if_then_else *if_then_else = NULL;\n+\tsize_t str_len = 0;\n \n \tif (cur->at_end == if_then_else_handler)\n \t\tif_then_else = (struct if_then_else *)cur->at_end_data;\n@@ -867,18 +899,22 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta\n \tif (if_then_else->else_atom_seen)\n \t\treturn strbuf_addf_ret(err, -1, _(\"format: %%(then) atom used after %%(else)\"));\n \tif_then_else->then_atom_seen = 1;\n+\tif (if_then_else->str)\n+\t\tstr_len = strlen(if_then_else->str);\n \t/*\n \t * If the 'equals' or 'notequals' attribute is used then\n \t * perform the required comparison. If not, only non-empty\n \t * strings satisfy the 'if' condition.\n \t */\n \tif (if_then_else->cmp_status == COMPARE_EQUAL) {\n-\t\tif (!strcmp(if_then_else->str, cur->output.buf))\n+\t\tif (str_len == cur->output.len &&\n+\t\t    !memcmp(if_then_else->str, cur->output.buf, cur->output.len))\n \t\t\tif_then_else->condition_satisfied = 1;\n \t} else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {\n-\t\tif (strcmp(if_then_else->str, cur->output.buf))\n+\t\tif (str_len != cur->output.len ||\n+\t\t    memcmp(if_then_else->str, cur->output.buf, cur->output.len))\n \t\t\tif_then_else->condition_satisfied = 1;\n-\t} else if (cur->output.len && !is_empty(cur->output.buf))\n+\t} else if (cur->output.len && !is_empty(&cur->output))\n \t\tif_then_else->condition_satisfied = 1;\n \tstrbuf_reset(&cur->output);\n \treturn 0;\n@@ -924,7 +960,7 @@ static int end_atom_handler(struct atom_value *atomv, struct ref_formatting_stat\n \t * only on the topmost supporting atom.\n \t */\n \tif (!current->prev->prev) {\n-\t\tquote_formatting(&s, current->output.buf, state->quote_style);\n+\t\tquote_formatting(&s, current->output.buf, current->output.len, state->quote_style);\n \t\tstrbuf_swap(&current->output, &s);\n \t}\n \tstrbuf_release(&s);\n@@ -1357,7 +1393,7 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n \n /* See grab_values */\n static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n-\t\t\t\t   struct object *obj)\n+\t\t\t\t   unsigned long buf_size, struct object *obj)\n {\n \tint i;\n \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n@@ -1367,12 +1403,23 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n \t\tstruct used_atom *atom = &used_atom[i];\n \t\tconst char *name = atom->name;\n \t\tstruct atom_value *v = &val[i];\n+\t\tenum atom_type atom_type = atom->atom_type;\n \n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n \n+\t\tif (atom_type == ATOM_RAW) {\n+\t\t\tif (atom->u.raw_data.option == RAW_BARE) {\n+\t\t\t\tv->s = xmemdupz(buf, buf_size);\n+\t\t\t\tv->s_size = buf_size;\n+\t\t\t} else if (atom->u.raw_data.option == RAW_LENGTH) {\n+\t\t\t\tv->s = xstrfmt(\"%\"PRIuMAX, (uintmax_t)buf_size);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif ((obj->type != OBJ_TAG &&\n \t\t     obj->type != OBJ_COMMIT) ||\n \t\t    (strcmp(name, \"body\") &&\n@@ -1442,25 +1489,30 @@ static void fill_missing_values(struct atom_value *val)\n  * pointed at by the ref itself; otherwise it is the object the\n  * ref (which is a tag) refers to.\n  */\n-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)\n+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n {\n+\tvoid *buf = data->content;\n+\tunsigned long buf_size = data->size;\n+\n \tswitch (obj->type) {\n \tcase OBJ_TAG:\n \t\tgrab_tag_values(val, deref, obj);\n-\t\tgrab_sub_body_contents(val, deref, buf, obj);\n+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n \t\tgrab_person(\"tagger\", val, deref, buf);\n \t\tbreak;\n \tcase OBJ_COMMIT:\n \t\tgrab_commit_values(val, deref, obj);\n-\t\tgrab_sub_body_contents(val, deref, buf, obj);\n+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n \t\tgrab_person(\"author\", val, deref, buf);\n \t\tgrab_person(\"committer\", val, deref, buf);\n \t\tbreak;\n \tcase OBJ_TREE:\n \t\t/* grab_tree_values(val, deref, obj, buf, sz); */\n+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n \t\tbreak;\n \tcase OBJ_BLOB:\n \t\t/* grab_blob_values(val, deref, obj, buf, sz); */\n+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n \t\tbreak;\n \tdefault:\n \t\tdie(\"Eh?  Object of type %d?\", obj->type);\n@@ -1682,7 +1734,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj\n \t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n \t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n \t\t}\n-\t\tgrab_values(ref->value, deref, *obj, oi->content);\n+\t\tgrab_values(ref->value, deref, *obj, oi);\n \t}\n \n \tgrab_common_values(ref->value, deref, oi);\n@@ -1763,7 +1815,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t\tint deref = 0;\n \t\tconst char *refname;\n \t\tstruct branch *branch = NULL;\n-\n+\t\tv->s_size = ATOM_VALUE_S_SIZE_INIT;\n \t\tv->handler = append_atom;\n \t\tv->atom = atom;\n \n@@ -2367,6 +2419,19 @@ static int compare_detached_head(struct ref_array_item *a, struct ref_array_item\n \treturn 0;\n }\n \n+static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n+{\n+\tconst char *s1 = vs1, *s2 = vs2;\n+\tconst char *end = s1 + n;\n+\n+\tfor (; s1 < end; s1++, s2++) {\n+\t\tint diff = tolower(*s1) - tolower(*s2);\n+\t\tif (diff)\n+\t\t\treturn diff;\n+\t}\n+\treturn 0;\n+}\n+\n static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)\n {\n \tstruct atom_value *va, *vb;\n@@ -2387,10 +2452,30 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n \t} else if (s->sort_flags & REF_SORTING_VERSION) {\n \t\tcmp = versioncmp(va->s, vb->s);\n \t} else if (cmp_type == FIELD_STR) {\n-\t\tint (*cmp_fn)(const char *, const char *);\n-\t\tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n-\t\t\t? strcasecmp : strcmp;\n-\t\tcmp = cmp_fn(va->s, vb->s);\n+\t\tif (va->s_size == ATOM_VALUE_S_SIZE_INIT &&\n+\t\t    vb->s_size == ATOM_VALUE_S_SIZE_INIT) {\n+\t\t\tint (*cmp_fn)(const char *, const char *);\n+\t\t\tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n+\t\t\t\t? strcasecmp : strcmp;\n+\t\t\tcmp = cmp_fn(va->s, vb->s);\n+\t\t} else {\n+\t\t\tint (*cmp_fn)(const void *, const void *, size_t);\n+\t\t\tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n+\t\t\t\t? memcasecmp : memcmp;\n+\t\t\tsize_t a_size = va->s_size == ATOM_VALUE_S_SIZE_INIT ?\n+\t\t\t\t\tstrlen(va->s) : va->s_size;\n+\t\t\tsize_t b_size = vb->s_size == ATOM_VALUE_S_SIZE_INIT ?\n+\t\t\t\t\tstrlen(vb->s) : vb->s_size;\n+\n+\t\t\tcmp = cmp_fn(va->s, vb->s, b_size > a_size ?\n+\t\t\t\t     a_size : b_size);\n+\t\t\tif (!cmp) {\n+\t\t\t\tif (a_size > b_size)\n+\t\t\t\t\tcmp = 1;\n+\t\t\t\telse if (a_size < b_size)\n+\t\t\t\t\tcmp = -1;\n+\t\t\t}\n+\t\t}\n \t} else {\n \t\tif (va->value < vb->value)\n \t\t\tcmp = -1;\n@@ -2490,6 +2575,7 @@ int format_ref_array_item(struct ref_array_item *info,\n \t}\n \tif (format->need_color_reset_at_eol) {\n \t\tstruct atom_value resetv;\n+\t\tresetv.s_size = ATOM_VALUE_S_SIZE_INIT;\n \t\tresetv.s = GIT_COLOR_RESET;\n \t\tif (append_atom(&resetv, &state, error_buf)) {\n \t\t\tpop_stack_element(&state.stack);\ndiff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh\nindex 9e0214076b4d..5f66d933ace0 100755\n--- a/t/t6300-for-each-ref.sh\n+++ b/t/t6300-for-each-ref.sh\n@@ -130,6 +130,8 @@ test_atom head parent:short=10 ''\n test_atom head numparent 0\n test_atom head object ''\n test_atom head type ''\n+test_atom head raw \"$(git cat-file commit refs/heads/main)\n+\"\n test_atom head '*objectname' ''\n test_atom head '*objecttype' ''\n test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'\n@@ -221,6 +223,15 @@ test_atom tag contents 'Tagging at 1151968727\n '\n test_atom tag HEAD ' '\n \n+test_expect_success 'basic atom: refs/tags/testtag *raw' '\n+\tgit cat-file commit refs/tags/testtag^{} >expected &&\n+\tgit for-each-ref --format=\"%(*raw)\" refs/tags/testtag >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_expect_success 'Check invalid atoms names are errors' '\n \ttest_must_fail git for-each-ref --format=\"%(INVALID)\" refs/heads\n '\n@@ -686,6 +697,15 @@ test_atom refs/tags/signed-empty contents:body ''\n test_atom refs/tags/signed-empty contents:signature \"$sig\"\n test_atom refs/tags/signed-empty contents \"$sig\"\n \n+test_expect_success 'basic atom: refs/tags/signed-empty raw' '\n+\tgit cat-file tag refs/tags/signed-empty >expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/tags/signed-empty >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_atom refs/tags/signed-short subject 'subject line'\n test_atom refs/tags/signed-short subject:sanitize 'subject-line'\n test_atom refs/tags/signed-short contents:subject 'subject line'\n@@ -695,6 +715,15 @@ test_atom refs/tags/signed-short contents:signature \"$sig\"\n test_atom refs/tags/signed-short contents \"subject line\n $sig\"\n \n+test_expect_success 'basic atom: refs/tags/signed-short raw' '\n+\tgit cat-file tag refs/tags/signed-short >expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/tags/signed-short >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_atom refs/tags/signed-long subject 'subject line'\n test_atom refs/tags/signed-long subject:sanitize 'subject-line'\n test_atom refs/tags/signed-long contents:subject 'subject line'\n@@ -708,6 +737,15 @@ test_atom refs/tags/signed-long contents \"subject line\n body contents\n $sig\"\n \n+test_expect_success 'basic atom: refs/tags/signed-long raw' '\n+\tgit cat-file tag refs/tags/signed-long >expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/tags/signed-long >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_expect_success 'set up refs pointing to tree and blob' '\n \tgit update-ref refs/mytrees/first refs/heads/main^{tree} &&\n \tgit update-ref refs/myblobs/first refs/heads/main:one\n@@ -720,6 +758,16 @@ test_atom refs/mytrees/first contents:body \"\"\n test_atom refs/mytrees/first contents:signature \"\"\n test_atom refs/mytrees/first contents \"\"\n \n+test_expect_success 'basic atom: refs/mytrees/first raw' '\n+\tgit cat-file tree refs/mytrees/first >expected &&\n+\techo \"\" >>expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/mytrees/first >actual &&\n+\ttest_cmp expected actual &&\n+\tgit cat-file -s refs/mytrees/first >expected &&\n+\tgit for-each-ref --format=\"%(raw:size)\" refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_atom refs/myblobs/first subject \"\"\n test_atom refs/myblobs/first contents:subject \"\"\n test_atom refs/myblobs/first body \"\"\n@@ -727,6 +775,165 @@ test_atom refs/myblobs/first contents:body \"\"\n test_atom refs/myblobs/first contents:signature \"\"\n test_atom refs/myblobs/first contents \"\"\n \n+test_expect_success 'basic atom: refs/myblobs/first raw' '\n+\tgit cat-file blob refs/myblobs/first >expected &&\n+\techo \"\" >>expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/myblobs/first >actual &&\n+\ttest_cmp expected actual &&\n+\tgit cat-file -s refs/myblobs/first >expected &&\n+\tgit for-each-ref --format=\"%(raw:size)\" refs/myblobs/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'set up refs pointing to binary blob' '\n+\tprintf \"%b\" \"a\\0b\\0c\" >blob1 &&\n+\tprintf \"%b\" \"a\\0c\\0b\" >blob2 &&\n+\tprintf \"%b\" \"\\0a\\0b\\0c\" >blob3 &&\n+\tprintf \"%b\" \"abc\" >blob4 &&\n+\tprintf \"%b\" \"\\0 \\0 \\0 \" >blob5 &&\n+\tprintf \"%b\" \"\\0 \\0a\\0 \" >blob6 &&\n+\tprintf \"%b\" \"  \" >blob7 &&\n+\t>blob8 &&\n+\tgit hash-object blob1 -w | xargs git update-ref refs/myblobs/blob1 &&\n+\tgit hash-object blob2 -w | xargs git update-ref refs/myblobs/blob2 &&\n+\tgit hash-object blob3 -w | xargs git update-ref refs/myblobs/blob3 &&\n+\tgit hash-object blob4 -w | xargs git update-ref refs/myblobs/blob4 &&\n+\tgit hash-object blob5 -w | xargs git update-ref refs/myblobs/blob5 &&\n+\tgit hash-object blob6 -w | xargs git update-ref refs/myblobs/blob6 &&\n+\tgit hash-object blob7 -w | xargs git update-ref refs/myblobs/blob7 &&\n+\tgit hash-object blob8 -w | xargs git update-ref refs/myblobs/blob8\n+'\n+\n+test_expect_success 'Verify sorts with raw' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob8\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/myblobs/blob3\n+\trefs/myblobs/blob7\n+\trefs/mytrees/first\n+\trefs/myblobs/first\n+\trefs/myblobs/blob1\n+\trefs/myblobs/blob2\n+\trefs/myblobs/blob4\n+\trefs/heads/main\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname)\" --sort=raw \\\n+\t\trefs/heads/main refs/myblobs/ refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Verify sorts with raw:size' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob8\n+\trefs/myblobs/first\n+\trefs/myblobs/blob7\n+\trefs/heads/main\n+\trefs/myblobs/blob4\n+\trefs/myblobs/blob1\n+\trefs/myblobs/blob2\n+\trefs/myblobs/blob3\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/mytrees/first\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname)\" --sort=raw:size \\\n+\t\trefs/heads/main refs/myblobs/ refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'validate raw atom with %(if:equals)' '\n+\tcat >expected <<-EOF &&\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\trefs/myblobs/blob4\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tEOF\n+\tgit for-each-ref --format=\"%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)\" \\\n+\t\trefs/myblobs/ refs/heads/ >actual &&\n+\ttest_cmp expected actual\n+'\n+test_expect_success 'validate raw atom with %(if:notequals)' '\n+\tcat >expected <<-EOF &&\n+\trefs/heads/ambiguous\n+\trefs/heads/main\n+\trefs/heads/newtag\n+\trefs/myblobs/blob1\n+\trefs/myblobs/blob2\n+\trefs/myblobs/blob3\n+\tequals\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/myblobs/blob7\n+\trefs/myblobs/blob8\n+\trefs/myblobs/first\n+\tEOF\n+\tgit for-each-ref --format=\"%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)\" \\\n+\t\trefs/myblobs/ refs/heads/ >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'empty raw refs with %(if)' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob1 not empty\n+\trefs/myblobs/blob2 not empty\n+\trefs/myblobs/blob3 not empty\n+\trefs/myblobs/blob4 not empty\n+\trefs/myblobs/blob5 not empty\n+\trefs/myblobs/blob6 not empty\n+\trefs/myblobs/blob7 empty\n+\trefs/myblobs/blob8 empty\n+\trefs/myblobs/first not empty\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)\" \\\n+\t\trefs/myblobs/ >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '%(raw) with --python must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --python\n+'\n+\n+test_expect_success '%(raw) with --tcl must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --tcl\n+'\n+\n+test_expect_success '%(raw) with --perl must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --perl\n+'\n+\n+test_expect_success '%(raw) with --shell must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --shell\n+'\n+\n+test_expect_success '%(raw) with --shell and --sort=raw must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --sort=raw --shell\n+'\n+\n+test_expect_success '%(raw:size) with --shell' '\n+\tgit for-each-ref --format=\"%(raw:size)\" | while read line\n+\tdo\n+\t\techo \"'\\''$line'\\''\" >>expect\n+\tdone &&\n+\tgit for-each-ref --format=\"%(raw:size)\" --shell >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'for-each-ref --format compare with cat-file --batch' '\n+\tgit rev-parse refs/mytrees/first | git cat-file --batch >expected &&\n+\tgit for-each-ref --format=\"%(objectname) %(objecttype) %(objectsize)\n+%(raw)\" refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'set up multiple-sort tags' '\n \tfor when in 100000 200000\n \tdo\n-- \ngitgitgadget\n"},{"id":"426280","messageId":"xmqq1r9j7l60.fsf@gitster.g","threadId":"55818","inReplyTo":"97955705c22e00a718a8de7555ab7e0e401e792e.1622558243.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add obj-type check in grab contents","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-06-03T02:10:31Z","receivedAt":"2021-06-03T02:10:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  /* See grab_values */\n> -static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n> +static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n> +\t\t\t\t   struct object *obj)\n\nNeither this step or the next change needs anything but type member\nof the 'obj' (and 'buf' is coming from oi.content of the result of\nasking about that same 'obj').\n\nI wonder if we should do one of the following:\n\n (1) stop passing \"void *buf\" and instead \"struct expand_data\n     *data\", and use \"data->content\" to access \"buf\", which would\n     allow you to access \"data->type\" to perform the added check.\n\n (2) instead of adding \"struct obj *obj\" to the parameters, just add\n     \"enum object_type type\", as that is the only thing you need.\n\nObviously (2) is with lessor impact, but if it can be done safely\nwithout breaking the code [*], (1) would probably be a much more\npreferrable direction to go in the longer term.\n\n    Side note [*].  A caller is allowed to choose to feed \"buf\" that\n    is different from \"oi.content\" (perhaps buf may sometimes want\n    to be a utf-8 recoded version of oi.content for certain types of\n    objects) with the current system, but if we pass expand_data\n    throughout the callchain, such a caller is broken, for example.\n\n"},{"id":"426286","messageId":"xmqqsg1z65b8.fsf@gitster.g","threadId":"55818","inReplyTo":"5a94705cdbc101169488919e35613d723e6ec581.1622558243.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-06-03T02:38:19Z","receivedAt":"2021-06-03T02:38:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -644,13 +664,6 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n>  \t\treturn strbuf_addf_ret(err, -1, _(\"malformed field name: %.*s\"),\n>  \t\t\t\t       (int)(ep-atom), atom);\n>  \n> -\t/* Do we have the atom already used elsewhere? */\n> -\tfor (i = 0; i < used_atom_cnt; i++) {\n> -\t\tint len = strlen(used_atom[i].name);\n> -\t\tif (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n> -\t\t\treturn i;\n> -\t}\n> -\n>  \t/*\n>  \t * If the atom name has a colon, strip it and everything after\n>  \t * it off - it specifies the format for this entry, and\n> @@ -660,6 +673,17 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n>  \targ = memchr(sp, ':', ep - sp);\n>  \tatom_len = (arg ? arg : ep) - sp;\n>  \n> +\tif (format->quote_style && !strncmp(sp, \"raw\", 3) && !arg)\n> +\t\treturn strbuf_addf_ret(err, -1, _(\"--format=%.*s cannot be used with\"\n> +\t\t\t\t\"--python, --shell, --tcl, --perl\"), (int)(ep-atom), atom);\n> +\n> +\t/* Do we have the atom already used elsewhere? */\n> +\tfor (i = 0; i < used_atom_cnt; i++) {\n> +\t\tint len = strlen(used_atom[i].name);\n> +\t\tif (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n> +\t\t\treturn i;\n> +\t}\n> +\n\nThese two hunks\n\n - hoists up the code that sets 'arg' to optional string after\n   \"<atom>:\" and counts how long the \"<atom>\" is in 'atom_len'; the\n   change causes the counting done even when the same placeholder is\n   already used elsewhere (in which case we do not have to do such\n   counting);\n\n - inserts the early return to reject use of \"raw\" atom when\n   language specific quoting is used.\n\nIt probably makes it easier to understand if the former is split\ninto a separate commit, but at the same time a series with too many\nsmall steps is harder to manage, so let's keep them in a single\nchange.\n\nBut I do not think we want to add the new change at this location,\nat least for two reasons:\n\n * The posted patch checks '!arg' to avoid rejecting \"raw:size\",\n   which would not scale at all.  What if you wanted to later add\n   \"raw:upcase\", which you must reject?\n\n * We do have enumerated constants for each atom types, but this\n   early check and return does string comparison.\n\nWhere it belongs is either after \"Is the atom a valid one?\" loop\nwhere 'atom_len' is used to locate the placeholder's atom in the\ntable of valid atoms [*], or inside raw_atom_parser().\n\n    Side note: If you read the original code, you would notice that\n    there already is a similar \"this is a valid atom that appear in\n    the valid_atom[] table, but unallowed in this situation\" check\n    done with .source != SOURCE_NONE conditional.  One downside is\n    that until calling raw_atom_parser(), you do not know if\n    RAW_BARE or RAW_LENGTH is requested.\n\nIf we do inside raw_atom_parser(), it would probably look like this:\n \n+static int raw_atom_parser(const struct ref_format *format, struct used_atom *atom,\n+\t\t\t\tconst char *arg, struct strbuf *err)\n+{\n+\tif (!arg)\n+\t\tatom->u.raw_data.option = RAW_BARE;\n+\telse if (!strcmp(arg, \"size\"))\n+\t\tatom->u.raw_data.option = RAW_LENGTH;\n+\telse\n+\t\treturn strbuf_addf_ret(err, -1, _(\"unrecognized %%(raw) argument: %s\"), arg);\n+\n+\tif (atom->u.raw_data.option == RAW_BARE && format->quote_style)\n+\t\treturn strbuf_addf_ret(err, -1,\n+\t\t\t\t       _(\"--format=%%(raw) cannot be used with ...\")...);\n+\n+\treturn 0;\n+}\n"},{"id":"426294","messageId":"CAOLTT8TzZHsZfLtgS8G=fvE5hQzP-1PBnS8yyj41y2PtZtBh0g@mail.gmail.com","threadId":"55818","inReplyTo":"xmqq1r9j7l60.fsf@gitster.g","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add obj-type check in grab contents","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-03T04:52:14Z","receivedAt":"2021-06-03T04:52:41Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年6月3日周四 上午10:10写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> >  /* See grab_values */\n> > -static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n> > +static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n> > +                                struct object *obj)\n>\n> Neither this step or the next change needs anything but type member\n> of the 'obj' (and 'buf' is coming from oi.content of the result of\n> asking about that same 'obj').\n>\n> I wonder if we should do one of the following:\n>\n>  (1) stop passing \"void *buf\" and instead \"struct expand_data\n>      *data\", and use \"data->content\" to access \"buf\", which would\n>      allow you to access \"data->type\" to perform the added check.\n>\n>  (2) instead of adding \"struct obj *obj\" to the parameters, just add\n>      \"enum object_type type\", as that is the only thing you need.\n>\n> Obviously (2) is with lessor impact, but if it can be done safely\n> without breaking the code [*], (1) would probably be a much more\n> preferrable direction to go in the longer term.\n>\n\nI agree with (1). In future versions of grab_sub_body_contents(), we will\nuse the content of \"data\" more frequently instead of using the\ncrude \"obj\". The type provided by \"obj\" can also be provided by\n\"data\". So yes, I would be very willing to let grab_sub_body_contents()\nonly use \"data\". (delete \"obj\")\n\nE.g.\n\nstatic void grab_sub_body_contents(struct atom_value *val, int deref,\nstruct expand_data *data)\n\nUsing (2), we will need more parameters to pass other object info.\n\n>     Side note [*].  A caller is allowed to choose to feed \"buf\" that\n>     is different from \"oi.content\" (perhaps buf may sometimes want\n>     to be a utf-8 recoded version of oi.content for certain types of\n>     objects) with the current system, but if we pass expand_data\n>     throughout the callchain, such a caller is broken, for example.\n>\n\nJust see the situation in front of us: grab_sub_body_contents()\nhave only one caller: grab_values(). If someone need a function like\ngrab_sub_body_contents() to grab another buf, they can use rewrite\na more universal function interface:\n\nstatic void grab_sub_body_contents(struct atom_value *val, int deref,\nstruct expand_data *data)\n{\n   grab_sub_body_contents_internal(val, deref, data->content,\ndata->size, data->type);\n}\n\nstatic void grab_sub_body_contents_internal(struct atom_value *val,\nint deref, void *buf,\n                                           unsigned long buf_size,\nenum object_type type)\n{\n...\n}\n\nBut for the time being, the above one is sufficient.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426296","messageId":"0961dc61-e785-8e3d-8c06-bdb8f2d6ee6a@gmail.com","threadId":"55818","inReplyTo":"pull.966.git.1622558243.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Bagas Sanjaya","fromEmail":"bagasdotme@gmail.com","sentAt":"2021-06-03T05:11:49Z","receivedAt":"2021-06-03T05:12:12Z","isPatch":true,"sender":{"key":"bagasdotme@gmail.com","avatar":"https://avatars.githubusercontent.com/u/40219486?v=4"},"body":"Hi,\n\nOn 01/06/21 21.37, ZheNing Hu via GitGitGadget wrote:\n> In order to make git cat-file --batch use ref-filter logic, I add %(raw)\n> atom to ref-filter.\n> \n> Change from last version:\n> \n>   1. Change is_empty() logic.\n>   2. Simplify memcasecmp().\n>   3. rebase on zh/ref-filter-atom-type.\n> \n\nI prefer no first-person pronouns (I and we) in patch cover letter and \ncommit message, so better say:\n\n\"Add %(raw) atom to ref-filter to make git cat-file --batch use \nref-filter logic.\"\n\n-- \nAn old man doll... just what I always wanted! - Clara\n"},{"id":"426297","messageId":"CAOLTT8TiyAtJfcrPoBF6SECfaKOTKt24NSOtTmf84JwHrU8g1A@mail.gmail.com","threadId":"55818","inReplyTo":"xmqqsg1z65b8.fsf@gitster.g","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-03T05:36:21Z","receivedAt":"2021-06-03T05:37:47Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年6月3日周四 上午10:38写道：\n>\n>\n> These two hunks\n>\n>  - hoists up the code that sets 'arg' to optional string after\n>    \"<atom>:\" and counts how long the \"<atom>\" is in 'atom_len'; the\n>    change causes the counting done even when the same placeholder is\n>    already used elsewhere (in which case we do not have to do such\n>    counting);\n>\n\nI admit that I am doing repetitive work here.\n\n>  - inserts the early return to reject use of \"raw\" atom when\n>    language specific quoting is used.\n>\n> It probably makes it easier to understand if the former is split\n> into a separate commit, but at the same time a series with too many\n> small steps is harder to manage, so let's keep them in a single\n> change.\n>\n> But I do not think we want to add the new change at this location,\n> at least for two reasons:\n>\n>  * The posted patch checks '!arg' to avoid rejecting \"raw:size\",\n>    which would not scale at all.  What if you wanted to later add\n>    \"raw:upcase\", which you must reject?\n>\n\nYeah, the code here makes \"raw\" lack of scalability. Especially we\nwant to add \"%(raw:textconv)\" and \"%(raw:filter)\" later.\n\n>  * We do have enumerated constants for each atom types, but this\n>    early check and return does string comparison.\n>\n\nNote that at this time we must compare strings... parse_ref_filter_atom()\npasses string form of the atom. Code block A also requires comparing strings.\n\n-------------------\nCode block A:\n\n        for (i = 0; i < used_atom_cnt; i++) {\n               int len = strlen(used_atom[i].name);\n               if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n                       return i;\n       }\n-------------------\n\nAll the following replies are based on such a fact:\nWe will reuse used atoms as much as possible.\n\nThink about this situation:\n\n$ git for-each-ref --format=\"%(raw)\" --sort=\"raw\" --python\n\nSince we specified --sort=\"raw\",`parse_sorting_atom()`\nwill be called in parse_opt_ref_sorting(), but at this time\nwe haven't parsed --<lang> yet. So format->quote_style == 0,\nwe cannot refuse  --<lang> at this time, and a \"%(raw)\" atom\nitem will be added to used_atom, when we use `verify_ref_format()`\nto call `parse_sorting_atom()` for the second time, we already have\nraw atom item in used_atom, in Code Block A we directly returned,\nWe can't refuse --<lang> too after Code Block A. So as a last solution,\nwe refuse --<lang> with \"%(raw)\" before Code Block A.\n\n> Where it belongs is either after \"Is the atom a valid one?\" loop\n> where 'atom_len' is used to locate the placeholder's atom in the\n> table of valid atoms [*], or inside raw_atom_parser().\n>\n>     Side note: If you read the original code, you would notice that\n>     there already is a similar \"this is a valid atom that appear in\n>     the valid_atom[] table, but unallowed in this situation\" check\n>     done with .source != SOURCE_NONE conditional.  One downside is\n>     that until calling raw_atom_parser(), you do not know if\n>     RAW_BARE or RAW_LENGTH is requested.\n>\n\nYes, but we need to pay attention to it is below Code Block A.\n\n> If we do inside raw_atom_parser(), it would probably look like this:\n>\n> +static int raw_atom_parser(const struct ref_format *format, struct used_atom *atom,\n> +                               const char *arg, struct strbuf *err)\n> +{\n> +       if (!arg)\n> +               atom->u.raw_data.option = RAW_BARE;\n> +       else if (!strcmp(arg, \"size\"))\n> +               atom->u.raw_data.option = RAW_LENGTH;\n> +       else\n> +               return strbuf_addf_ret(err, -1, _(\"unrecognized %%(raw) argument: %s\"), arg);\n> +\n> +       if (atom->u.raw_data.option == RAW_BARE && format->quote_style)\n> +               return strbuf_addf_ret(err, -1,\n> +                                      _(\"--format=%%(raw) cannot be used with ...\")...);\n> +\n> +       return 0;\n> +}\n\nIt's same, \"*.parser()\" is below Code Block A.\n\nAfter all, the reason why this must be done here is the ref-filter\noriginal logic\nhas not considered rejecting a format atom and an option.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426298","messageId":"CAOLTT8SudqfYq-UDqOjVpKs4xNpthiDNMA8QduKqrQvz9HaNWw@mail.gmail.com","threadId":"55818","inReplyTo":"0961dc61-e785-8e3d-8c06-bdb8f2d6ee6a@gmail.com","subject":"Re: [PATCH 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-03T05:37:57Z","receivedAt":"2021-06-03T05:39:26Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Bagas Sanjaya <bagasdotme@gmail.com> 于2021年6月3日周四 下午1:11写道：\n>\n> Hi,\n>\n> On 01/06/21 21.37, ZheNing Hu via GitGitGadget wrote:\n> > In order to make git cat-file --batch use ref-filter logic, I add %(raw)\n> > atom to ref-filter.\n> >\n> > Change from last version:\n> >\n> >   1. Change is_empty() logic.\n> >   2. Simplify memcasecmp().\n> >   3. rebase on zh/ref-filter-atom-type.\n> >\n>\n> I prefer no first-person pronouns (I and we) in patch cover letter and\n> commit message, so better say:\n>\n> \"Add %(raw) atom to ref-filter to make git cat-file --batch use\n> ref-filter logic.\"\n>\n\nOk. I will change my way of narrating.\n\n> --\n> An old man doll... just what I always wanted! - Clara\n"},{"id":"426326","messageId":"CAOLTT8QjnFqXHRzAPnJStf4Ra189dZFe9k-Y9COoSCwW47CXiA@mail.gmail.com","threadId":"55818","inReplyTo":"CAOLTT8TiyAtJfcrPoBF6SECfaKOTKt24NSOtTmf84JwHrU8g1A@mail.gmail.com","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-03T14:06:35Z","receivedAt":"2021-06-03T14:07:59Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> 于2021年6月3日周四 下午1:36写道：\n>\n> Junio C Hamano <gitster@pobox.com> 于2021年6月3日周四 上午10:38写道：\n> > But I do not think we want to add the new change at this location,\n> > at least for two reasons:\n> >\n> >  * The posted patch checks '!arg' to avoid rejecting \"raw:size\",\n> >    which would not scale at all.  What if you wanted to later add\n> >    \"raw:upcase\", which you must reject?\n> >\n>\n> Yeah, the code here makes \"raw\" lack of scalability. Especially we\n> want to add \"%(raw:textconv)\" and \"%(raw:filter)\" later.\n>\n\nNow I am building %(raw:textconv) and %(raw:filter), the code will be\nvery difficult to write:\n\n        if (format->quote_style && !strncmp(sp, \"raw\", 3)\n                                && ((!arg) || (!strncmp(arg,\n\":textconv\", 9)) || (!strncmp(arg, \":filter\", 7))))\n                return strbuf_addf_ret(err, -1, _(\"--format=%.*s\ncannot be used with\"\n                                \"--python, --shell, --tcl, --perl\"),\n(int)(ep-atom), atom);\n\nIs there any good way?\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426361","messageId":"xmqq35ty6391.fsf@gitster.g","threadId":"55818","inReplyTo":"CAOLTT8TiyAtJfcrPoBF6SECfaKOTKt24NSOtTmf84JwHrU8g1A@mail.gmail.com","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-06-03T21:35:06Z","receivedAt":"2021-06-03T21:35:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>>  * We do have enumerated constants for each atom types, but this\n>>    early check and return does string comparison.\n>>\n>\n> Note that at this time we must compare strings... parse_ref_filter_atom()\n> passes string form of the atom. Code block A also requires comparing strings.\n>\n> -------------------\n> Code block A:\n>\n>         for (i = 0; i < used_atom_cnt; i++) {\n>                int len = strlen(used_atom[i].name);\n>                if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n>                        return i;\n>        }\n> -------------------\n\nThe point is that you can piggyback existing string comparison\n(which is called \"parsing\") and use the parsed result (i.e. if you\ncan compare with ATOM_RAW instead of adding another strcmp(), that\ncan be a better solution).\n\n> All the following replies are based on such a fact:\n> We will reuse used atoms as much as possible.\n>\n> Think about this situation:\n>\n> $ git for-each-ref --format=\"%(raw)\" --sort=\"raw\" --python\n>\n> Since we specified --sort=\"raw\",`parse_sorting_atom()`\n> will be called in parse_opt_ref_sorting(), but at this time\n> we haven't parsed --<lang> yet.\n\nThat only says using parse_sorting_atom() and relying on the check\nin the function is still too early, and does not necessarily support\nthe posted patch that redundantly runs strcmp().\n\nAfter parsing all the command line options, we have used_atom[]\nfully populated and we know what host language we are quoting the\nresult for---and that makes a good place to check for comflicting\nrequests.\n\n>> +static int raw_atom_parser(const struct ref_format *format, struct used_atom *atom,\n>> +                               const char *arg, struct strbuf *err)\n>> +{\n>> +       if (!arg)\n>> +               atom->u.raw_data.option = RAW_BARE;\n>> +       else if (!strcmp(arg, \"size\"))\n>> +               atom->u.raw_data.option = RAW_LENGTH;\n>> +       else\n>> +               return strbuf_addf_ret(err, -1, _(\"unrecognized %%(raw) argument: %s\"), arg);\n>> +\n>> +       if (atom->u.raw_data.option == RAW_BARE && format->quote_style)\n>> +               return strbuf_addf_ret(err, -1,\n>> +                                      _(\"--format=%%(raw) cannot be used with ...\")...);\n>> +\n>> +       return 0;\n>> +}\n>\n> It's same, \"*.parser()\" is below Code Block A.\n>\n> After all, the reason why this must be done here is the ref-filter\n> original logic\n> has not considered rejecting a format atom and an option.\n\nThat is something you can fix to make the code easier to follow, no?\n"},{"id":"426362","messageId":"xmqqy2bq4omw.fsf@gitster.g","threadId":"55818","inReplyTo":"CAOLTT8QjnFqXHRzAPnJStf4Ra189dZFe9k-Y9COoSCwW47CXiA@mail.gmail.com","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-06-03T21:36:07Z","receivedAt":"2021-06-03T21:36:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> Now I am building %(raw:textconv) and %(raw:filter), the code will be\n> very difficult to write:\n>\n>         if (format->quote_style && !strncmp(sp, \"raw\", 3)\n>                                 && ((!arg) || (!strncmp(arg,\n> \":textconv\", 9)) || (!strncmp(arg, \":filter\", 7))))\n>                 return strbuf_addf_ret(err, -1, _(\"--format=%.*s\n> cannot be used with\"\n>                                 \"--python, --shell, --tcl, --perl\"),\n> (int)(ep-atom), atom);\n>\n> Is there any good way?\n\nThe problem you are having sounds like a natural consequence of\ndoing the check at the wrong place in the code, at least to me.\n"},{"id":"426406","messageId":"CAOLTT8SX-Ae-WrnXkGQPXA4dVdfX1VDKC=411L__DUkJc77d-A@mail.gmail.com","threadId":"55818","inReplyTo":"xmqq35ty6391.fsf@gitster.g","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-04T10:59:02Z","receivedAt":"2021-06-04T11:00:31Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年6月4日周五 上午5:35写道：\n>\n> The point is that you can piggyback existing string comparison\n> (which is called \"parsing\") and use the parsed result (i.e. if you\n> can compare with ATOM_RAW instead of adding another strcmp(), that\n> can be a better solution).\n>\n> That only says using parse_sorting_atom() and relying on the check\n> in the function is still too early, and does not necessarily support\n> the posted patch that redundantly runs strcmp().\n>\n> After parsing all the command line options, we have used_atom[]\n> fully populated and we know what host language we are quoting the\n> result for---and that makes a good place to check for comflicting\n> requests.\n>\n\nAlright, I got it: we can perform related check in verify_ref_format(),\nafter parse_ref_filter_atom(), It is a good checkpoint.\n\n> > After all, the reason why this must be done here is the ref-filter\n> > original logic\n> > has not considered rejecting a format atom and an option.\n>\n> That is something you can fix to make the code easier to follow, no?\n\nYou are right. ;-)\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426411","messageId":"48d256db5c349c1fa0615bb60d74039c78a831fd.1622808751.git.gitgitgadget@gmail.com","threadId":"55818","inReplyTo":"pull.966.v2.git.1622808751.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] [GSOC] ref-filter: add obj-type check in grab contents","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-06-04T12:12:29Z","receivedAt":"2021-06-04T12:12:35Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nOnly tag and commit objects use `grab_sub_body_contents()` to grab\nobject contents in the current codebase.  We want to teach the\nfunction to also handle blobs and trees to get their raw data,\nwithout parsing a blob (whose contents looks like a commit or a tag)\nincorrectly as a commit or a tag.\n\nSkip the block of code that is specific to handling commits and tags\nearly when the given object is of a wrong type to help later\naddition to handle other types of objects in this function.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Hariom Verma <hariom18599@gmail.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n ref-filter.c | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 4db0e40ff4c6..5cee6512fbaf 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1356,11 +1356,12 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n }\n \n /* See grab_values */\n-static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n+static void grab_sub_body_contents(struct atom_value *val, int deref, struct expand_data *data)\n {\n \tint i;\n \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n \tsize_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;\n+\tvoid *buf = data->content;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tstruct used_atom *atom = &used_atom[i];\n@@ -1371,10 +1372,13 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n-\t\tif (strcmp(name, \"body\") &&\n-\t\t    !starts_with(name, \"subject\") &&\n-\t\t    !starts_with(name, \"trailers\") &&\n-\t\t    !starts_with(name, \"contents\"))\n+\n+\t\tif ((data->type != OBJ_TAG &&\n+\t\t     data->type != OBJ_COMMIT) ||\n+\t\t    (strcmp(name, \"body\") &&\n+\t\t     !starts_with(name, \"subject\") &&\n+\t\t     !starts_with(name, \"trailers\") &&\n+\t\t     !starts_with(name, \"contents\")))\n \t\t\tcontinue;\n \t\tif (!subpos)\n \t\t\tfind_subpos(buf,\n@@ -1438,17 +1442,19 @@ static void fill_missing_values(struct atom_value *val)\n  * pointed at by the ref itself; otherwise it is the object the\n  * ref (which is a tag) refers to.\n  */\n-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)\n+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n {\n+\tvoid *buf = data->content;\n+\n \tswitch (obj->type) {\n \tcase OBJ_TAG:\n \t\tgrab_tag_values(val, deref, obj);\n-\t\tgrab_sub_body_contents(val, deref, buf);\n+\t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"tagger\", val, deref, buf);\n \t\tbreak;\n \tcase OBJ_COMMIT:\n \t\tgrab_commit_values(val, deref, obj);\n-\t\tgrab_sub_body_contents(val, deref, buf);\n+\t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"author\", val, deref, buf);\n \t\tgrab_person(\"committer\", val, deref, buf);\n \t\tbreak;\n@@ -1678,7 +1684,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj\n \t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n \t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n \t\t}\n-\t\tgrab_values(ref->value, deref, *obj, oi->content);\n+\t\tgrab_values(ref->value, deref, *obj, oi);\n \t}\n \n \tgrab_common_values(ref->value, deref, oi);\n-- \ngitgitgadget\n\n"},{"id":"426412","messageId":"pull.966.v2.git.1622808751.gitgitgadget@gmail.com","threadId":"55818","inReplyTo":"pull.966.git.1622558243.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-06-04T12:12:28Z","receivedAt":"2021-06-04T12:12:35Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In order to make git cat-file --batch use ref-filter logic, %(raw) atom is\nadding to ref-filter.\n\nChange from last version:\n\n 1. Change --<lang> and --format=%(raw) checkpoint to verify_ref_format(),\n    which make it more scalable.\n 2. Change grab_sub_body_contents() use struct expand_data *data instread of\n    using obj,buf,buf_size to pass object info which can reduce the delivery\n    of function parameters.\n\nZheNing Hu (2):\n  [GSOC] ref-filter: add obj-type check in grab contents\n  [GSOC] ref-filter: add %(raw) atom\n\n Documentation/git-for-each-ref.txt |   9 ++\n ref-filter.c                       | 164 +++++++++++++++++------\n t/t6300-for-each-ref.sh            | 207 +++++++++++++++++++++++++++++\n 3 files changed, 343 insertions(+), 37 deletions(-)\n\n\nbase-commit: 1197f1a46360d3ae96bd9c15908a3a6f8e562207\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-966%2Fadlternative%2Fref-filter-raw-atom-v4-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-966/adlternative/ref-filter-raw-atom-v4-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/966\n\nRange-diff vs v1:\n\n 1:  97955705c22e ! 1:  48d256db5c34 [GSOC] ref-filter: add obj-type check in grab contents\n     @@ ref-filter.c: static void append_lines(struct strbuf *out, const char *buf, unsi\n       \n       /* See grab_values */\n      -static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n     -+static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n     -+\t\t\t\t   struct object *obj)\n     ++static void grab_sub_body_contents(struct atom_value *val, int deref, struct expand_data *data)\n       {\n       \tint i;\n       \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n     + \tsize_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;\n     ++\tvoid *buf = data->content;\n     + \n     + \tfor (i = 0; i < used_atom_cnt; i++) {\n     + \t\tstruct used_atom *atom = &used_atom[i];\n      @@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n       \t\t\tcontinue;\n       \t\tif (deref)\n     @@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int der\n      -\t\t    !starts_with(name, \"trailers\") &&\n      -\t\t    !starts_with(name, \"contents\"))\n      +\n     -+\t\tif ((obj->type != OBJ_TAG &&\n     -+\t\t     obj->type != OBJ_COMMIT) ||\n     ++\t\tif ((data->type != OBJ_TAG &&\n     ++\t\t     data->type != OBJ_COMMIT) ||\n      +\t\t    (strcmp(name, \"body\") &&\n      +\t\t     !starts_with(name, \"subject\") &&\n      +\t\t     !starts_with(name, \"trailers\") &&\n     @@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int der\n       \t\t\tcontinue;\n       \t\tif (!subpos)\n       \t\t\tfind_subpos(buf,\n     -@@ ref-filter.c: static void grab_values(struct atom_value *val, int deref, struct object *obj, v\n     +@@ ref-filter.c: static void fill_missing_values(struct atom_value *val)\n     +  * pointed at by the ref itself; otherwise it is the object the\n     +  * ref (which is a tag) refers to.\n     +  */\n     +-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)\n     ++static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n     + {\n     ++\tvoid *buf = data->content;\n     ++\n       \tswitch (obj->type) {\n       \tcase OBJ_TAG:\n       \t\tgrab_tag_values(val, deref, obj);\n      -\t\tgrab_sub_body_contents(val, deref, buf);\n     -+\t\tgrab_sub_body_contents(val, deref, buf, obj);\n     ++\t\tgrab_sub_body_contents(val, deref, data);\n       \t\tgrab_person(\"tagger\", val, deref, buf);\n       \t\tbreak;\n       \tcase OBJ_COMMIT:\n       \t\tgrab_commit_values(val, deref, obj);\n      -\t\tgrab_sub_body_contents(val, deref, buf);\n     -+\t\tgrab_sub_body_contents(val, deref, buf, obj);\n     ++\t\tgrab_sub_body_contents(val, deref, data);\n       \t\tgrab_person(\"author\", val, deref, buf);\n       \t\tgrab_person(\"committer\", val, deref, buf);\n       \t\tbreak;\n     +@@ ref-filter.c: static int get_object(struct ref_array_item *ref, int deref, struct object **obj\n     + \t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n     + \t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n     + \t\t}\n     +-\t\tgrab_values(ref->value, deref, *obj, oi->content);\n     ++\t\tgrab_values(ref->value, deref, *obj, oi);\n     + \t}\n     + \n     + \tgrab_common_values(ref->value, deref, oi);\n 2:  5a94705cdbc1 ! 2:  0efed9435b59 [GSOC] ref-filter: add %(raw) atom\n     @@ ref-filter.c: static int parse_ref_filter_atom(const struct ref_format *format,\n       \targ = memchr(sp, ':', ep - sp);\n       \tatom_len = (arg ? arg : ep) - sp;\n       \n     -+\tif (format->quote_style && !strncmp(sp, \"raw\", 3) && !arg)\n     -+\t\treturn strbuf_addf_ret(err, -1, _(\"--format=%.*s cannot be used with\"\n     -+\t\t\t\t\"--python, --shell, --tcl, --perl\"), (int)(ep-atom), atom);\n     -+\n      +\t/* Do we have the atom already used elsewhere? */\n      +\tfor (i = 0; i < used_atom_cnt; i++) {\n      +\t\tint len = strlen(used_atom[i].name);\n     @@ ref-filter.c: static int end_atom_handler(struct atom_value *atomv, struct ref_f\n       \t\tstrbuf_swap(&current->output, &s);\n       \t}\n       \tstrbuf_release(&s);\n     -@@ ref-filter.c: static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n     +@@ ref-filter.c: int verify_ref_format(struct ref_format *format)\n     + \t\tat = parse_ref_filter_atom(format, sp + 2, ep, &err);\n     + \t\tif (at < 0)\n     + \t\t\tdie(\"%s\", err.buf);\n     ++\t\tif (format->quote_style && used_atom[at].atom_type == ATOM_RAW &&\n     ++\t\t    used_atom[at].u.raw_data.option == RAW_BARE)\n     ++\t\t\tdie(_(\"--format=%.*s cannot be used with\"\n     ++\t\t\t      \"--python, --shell, --tcl, --perl\"), (int)(ep - sp - 2), sp + 2);\n     + \t\tcp = ep + 1;\n       \n     - /* See grab_values */\n     - static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n     --\t\t\t\t   struct object *obj)\n     -+\t\t\t\t   unsigned long buf_size, struct object *obj)\n     - {\n     - \tint i;\n     + \t\tif (skip_prefix(used_atom[at].name, \"color:\", &color))\n     +@@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n       \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n     -@@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n     + \tsize_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;\n     + \tvoid *buf = data->content;\n     ++\tunsigned long buf_size = data->size;\n     + \n     + \tfor (i = 0; i < used_atom_cnt; i++) {\n       \t\tstruct used_atom *atom = &used_atom[i];\n       \t\tconst char *name = atom->name;\n       \t\tstruct atom_value *v = &val[i];\n     @@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int der\n      +\t\t\tcontinue;\n      +\t\t}\n      +\n     - \t\tif ((obj->type != OBJ_TAG &&\n     - \t\t     obj->type != OBJ_COMMIT) ||\n     + \t\tif ((data->type != OBJ_TAG &&\n     + \t\t     data->type != OBJ_COMMIT) ||\n       \t\t    (strcmp(name, \"body\") &&\n     -@@ ref-filter.c: static void fill_missing_values(struct atom_value *val)\n     -  * pointed at by the ref itself; otherwise it is the object the\n     -  * ref (which is a tag) refers to.\n     -  */\n     --static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)\n     -+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n     - {\n     -+\tvoid *buf = data->content;\n     -+\tunsigned long buf_size = data->size;\n     -+\n     - \tswitch (obj->type) {\n     - \tcase OBJ_TAG:\n     - \t\tgrab_tag_values(val, deref, obj);\n     --\t\tgrab_sub_body_contents(val, deref, buf, obj);\n     -+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n     - \t\tgrab_person(\"tagger\", val, deref, buf);\n     - \t\tbreak;\n     - \tcase OBJ_COMMIT:\n     - \t\tgrab_commit_values(val, deref, obj);\n     --\t\tgrab_sub_body_contents(val, deref, buf, obj);\n     -+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n     - \t\tgrab_person(\"author\", val, deref, buf);\n     - \t\tgrab_person(\"committer\", val, deref, buf);\n     +@@ ref-filter.c: static void grab_values(struct atom_value *val, int deref, struct object *obj, s\n       \t\tbreak;\n       \tcase OBJ_TREE:\n       \t\t/* grab_tree_values(val, deref, obj, buf, sz); */\n     -+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n     ++\t\tgrab_sub_body_contents(val, deref, data);\n       \t\tbreak;\n       \tcase OBJ_BLOB:\n       \t\t/* grab_blob_values(val, deref, obj, buf, sz); */\n     -+\t\tgrab_sub_body_contents(val, deref, buf, buf_size, obj);\n     ++\t\tgrab_sub_body_contents(val, deref, data);\n       \t\tbreak;\n       \tdefault:\n       \t\tdie(\"Eh?  Object of type %d?\", obj->type);\n     -@@ ref-filter.c: static int get_object(struct ref_array_item *ref, int deref, struct object **obj\n     - \t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n     - \t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n     - \t\t}\n     --\t\tgrab_values(ref->value, deref, *obj, oi->content);\n     -+\t\tgrab_values(ref->value, deref, *obj, oi);\n     - \t}\n     - \n     - \tgrab_common_values(ref->value, deref, oi);\n      @@ ref-filter.c: static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n       \t\tint deref = 0;\n       \t\tconst char *refname;\n\n-- \ngitgitgadget\n"},{"id":"426413","messageId":"0efed9435b59098f3ad928acd46c3c7e9f13677d.1622808751.git.gitgitgadget@gmail.com","threadId":"55818","inReplyTo":"pull.966.v2.git.1622808751.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-06-04T12:12:30Z","receivedAt":"2021-06-04T12:13:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nAdd new formatting option `%(raw)`, which will print the raw\nobject data without any changes. It will help further to migrate\nall cat-file formatting logic from cat-file to ref-filter.\n\nThe raw data of blob, tree objects may contain '\\0', but most of\nthe logic in `ref-filter` depands on the output of the atom being\ntext (specifically, no embedded NULs in it).\n\nE.g. `quote_formatting()` use `strbuf_addstr()` or `*._quote_buf()`\nadd the data to the buffer. The raw data of a tree object is\n`100644 one\\0...`, only the `100644 one` will be added to the buffer,\nwhich is incorrect.\n\nTherefore, add a new member in `struct atom_value`: `s_size`, which\ncan record raw object size, it can help us add raw object data to\nthe buffer or compare two buffers which contain raw object data.\n\nBeyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,\n`--tcl`, `--perl` because if our binary raw data is passed to a variable\nin the host language, the host language may not support arbitrary binary\ndata in the variables of its string type.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Hariom Verma <hariom18599@gmail.com>\nHelped-by: Felipe Contreras <felipe.contreras@gmail.com>\nHelped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nBased-on-patch-by: Olga Telezhnaya <olyatelezhnaya@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-for-each-ref.txt |   9 ++\n ref-filter.c                       | 140 +++++++++++++++----\n t/t6300-for-each-ref.sh            | 207 +++++++++++++++++++++++++++++\n 3 files changed, 328 insertions(+), 28 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 2ae2478de706..8f8d8cd1e04f 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -235,6 +235,15 @@ and `date` to extract the named component.  For email fields (`authoremail`,\n without angle brackets, and `:localpart` to get the part before the `@` symbol\n out of the trimmed email.\n \n+The raw data in a object is `raw`.\n+\n+raw:size::\n+\tThe raw data size of the object.\n+\n+Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,\n+`--perl` because the host language may not support arbitrary binary data in the\n+variables of its string type.\n+\n The message in a commit or a tag object is `contents`, from which\n `contents:<part>` can be used to extract various parts out of:\n \ndiff --git a/ref-filter.c b/ref-filter.c\nindex 5cee6512fbaf..46aec291de62 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -144,6 +144,7 @@ enum atom_type {\n \tATOM_BODY,\n \tATOM_TRAILERS,\n \tATOM_CONTENTS,\n+\tATOM_RAW,\n \tATOM_UPSTREAM,\n \tATOM_PUSH,\n \tATOM_SYMREF,\n@@ -189,6 +190,9 @@ static struct used_atom {\n \t\t\tstruct process_trailer_options trailer_opts;\n \t\t\tunsigned int nlines;\n \t\t} contents;\n+\t\tstruct {\n+\t\t\tenum { RAW_BARE, RAW_LENGTH } option;\n+\t\t} raw_data;\n \t\tstruct {\n \t\t\tcmp_status cmp_status;\n \t\t\tconst char *str;\n@@ -426,6 +430,18 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato\n \treturn 0;\n }\n \n+static int raw_atom_parser(const struct ref_format *format, struct used_atom *atom,\n+\t\t\t\tconst char *arg, struct strbuf *err)\n+{\n+\tif (!arg)\n+\t\tatom->u.raw_data.option = RAW_BARE;\n+\telse if (!strcmp(arg, \"size\"))\n+\t\tatom->u.raw_data.option = RAW_LENGTH;\n+\telse\n+\t\treturn strbuf_addf_ret(err, -1, _(\"unrecognized %%(raw) argument: %s\"), arg);\n+\treturn 0;\n+}\n+\n static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,\n \t\t\t   const char *arg, struct strbuf *err)\n {\n@@ -586,6 +602,7 @@ static struct {\n \t[ATOM_BODY] = { \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n \t[ATOM_TRAILERS] = { \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n \t[ATOM_CONTENTS] = { \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n+\t[ATOM_RAW] = { \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n \t[ATOM_UPSTREAM] = { \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t[ATOM_PUSH] = { \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t[ATOM_SYMREF] = { \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n@@ -620,12 +637,15 @@ struct ref_formatting_state {\n \n struct atom_value {\n \tconst char *s;\n+\tsize_t s_size;\n \tint (*handler)(struct atom_value *atomv, struct ref_formatting_state *state,\n \t\t       struct strbuf *err);\n \tuintmax_t value; /* used for sorting when not FIELD_STR */\n \tstruct used_atom *atom;\n };\n \n+#define ATOM_VALUE_S_SIZE_INIT (-1)\n+\n /*\n  * Used to parse format string and sort specifiers\n  */\n@@ -644,13 +664,6 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n \t\treturn strbuf_addf_ret(err, -1, _(\"malformed field name: %.*s\"),\n \t\t\t\t       (int)(ep-atom), atom);\n \n-\t/* Do we have the atom already used elsewhere? */\n-\tfor (i = 0; i < used_atom_cnt; i++) {\n-\t\tint len = strlen(used_atom[i].name);\n-\t\tif (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n-\t\t\treturn i;\n-\t}\n-\n \t/*\n \t * If the atom name has a colon, strip it and everything after\n \t * it off - it specifies the format for this entry, and\n@@ -660,6 +673,13 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n \targ = memchr(sp, ':', ep - sp);\n \tatom_len = (arg ? arg : ep) - sp;\n \n+\t/* Do we have the atom already used elsewhere? */\n+\tfor (i = 0; i < used_atom_cnt; i++) {\n+\t\tint len = strlen(used_atom[i].name);\n+\t\tif (len == ep - atom && !memcmp(used_atom[i].name, atom, len))\n+\t\t\treturn i;\n+\t}\n+\n \t/* Is the atom a valid one? */\n \tfor (i = 0; i < ARRAY_SIZE(valid_atom); i++) {\n \t\tint len = strlen(valid_atom[i].name);\n@@ -709,11 +729,14 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n \treturn at;\n }\n \n-static void quote_formatting(struct strbuf *s, const char *str, int quote_style)\n+static void quote_formatting(struct strbuf *s, const char *str, size_t len, int quote_style)\n {\n \tswitch (quote_style) {\n \tcase QUOTE_NONE:\n-\t\tstrbuf_addstr(s, str);\n+\t\tif (len != ATOM_VALUE_S_SIZE_INIT)\n+\t\t\tstrbuf_add(s, str, len);\n+\t\telse\n+\t\t\tstrbuf_addstr(s, str);\n \t\tbreak;\n \tcase QUOTE_SHELL:\n \t\tsq_quote_buf(s, str);\n@@ -740,9 +763,12 @@ static int append_atom(struct atom_value *v, struct ref_formatting_state *state,\n \t * encountered.\n \t */\n \tif (!state->stack->prev)\n-\t\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\t\tquote_formatting(&state->stack->output, v->s, v->s_size, state->quote_style);\n \telse\n-\t\tstrbuf_addstr(&state->stack->output, v->s);\n+\t\tif (v->s_size != ATOM_VALUE_S_SIZE_INIT)\n+\t\t\tstrbuf_add(&state->stack->output, v->s, v->s_size);\n+\t\telse\n+\t\t\tstrbuf_addstr(&state->stack->output, v->s);\n \treturn 0;\n }\n \n@@ -842,21 +868,23 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state\n \treturn 0;\n }\n \n-static int is_empty(const char *s)\n+static int is_empty(struct strbuf *buf)\n {\n-\twhile (*s != '\\0') {\n-\t\tif (!isspace(*s))\n-\t\t\treturn 0;\n-\t\ts++;\n-\t}\n-\treturn 1;\n-}\n+\tconst char *cur = buf->buf;\n+\tconst char *end = buf->buf + buf->len;\n+\n+\twhile (cur != end && (isspace(*cur)))\n+\t\tcur++;\n+\n+\treturn cur == end;\n+ }\n \n static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,\n \t\t\t     struct strbuf *err)\n {\n \tstruct ref_formatting_stack *cur = state->stack;\n \tstruct if_then_else *if_then_else = NULL;\n+\tsize_t str_len = 0;\n \n \tif (cur->at_end == if_then_else_handler)\n \t\tif_then_else = (struct if_then_else *)cur->at_end_data;\n@@ -867,18 +895,22 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta\n \tif (if_then_else->else_atom_seen)\n \t\treturn strbuf_addf_ret(err, -1, _(\"format: %%(then) atom used after %%(else)\"));\n \tif_then_else->then_atom_seen = 1;\n+\tif (if_then_else->str)\n+\t\tstr_len = strlen(if_then_else->str);\n \t/*\n \t * If the 'equals' or 'notequals' attribute is used then\n \t * perform the required comparison. If not, only non-empty\n \t * strings satisfy the 'if' condition.\n \t */\n \tif (if_then_else->cmp_status == COMPARE_EQUAL) {\n-\t\tif (!strcmp(if_then_else->str, cur->output.buf))\n+\t\tif (str_len == cur->output.len &&\n+\t\t    !memcmp(if_then_else->str, cur->output.buf, cur->output.len))\n \t\t\tif_then_else->condition_satisfied = 1;\n \t} else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {\n-\t\tif (strcmp(if_then_else->str, cur->output.buf))\n+\t\tif (str_len != cur->output.len ||\n+\t\t    memcmp(if_then_else->str, cur->output.buf, cur->output.len))\n \t\t\tif_then_else->condition_satisfied = 1;\n-\t} else if (cur->output.len && !is_empty(cur->output.buf))\n+\t} else if (cur->output.len && !is_empty(&cur->output))\n \t\tif_then_else->condition_satisfied = 1;\n \tstrbuf_reset(&cur->output);\n \treturn 0;\n@@ -924,7 +956,7 @@ static int end_atom_handler(struct atom_value *atomv, struct ref_formatting_stat\n \t * only on the topmost supporting atom.\n \t */\n \tif (!current->prev->prev) {\n-\t\tquote_formatting(&s, current->output.buf, state->quote_style);\n+\t\tquote_formatting(&s, current->output.buf, current->output.len, state->quote_style);\n \t\tstrbuf_swap(&current->output, &s);\n \t}\n \tstrbuf_release(&s);\n@@ -974,6 +1006,10 @@ int verify_ref_format(struct ref_format *format)\n \t\tat = parse_ref_filter_atom(format, sp + 2, ep, &err);\n \t\tif (at < 0)\n \t\t\tdie(\"%s\", err.buf);\n+\t\tif (format->quote_style && used_atom[at].atom_type == ATOM_RAW &&\n+\t\t    used_atom[at].u.raw_data.option == RAW_BARE)\n+\t\t\tdie(_(\"--format=%.*s cannot be used with\"\n+\t\t\t      \"--python, --shell, --tcl, --perl\"), (int)(ep - sp - 2), sp + 2);\n \t\tcp = ep + 1;\n \n \t\tif (skip_prefix(used_atom[at].name, \"color:\", &color))\n@@ -1362,17 +1398,29 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp\n \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n \tsize_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;\n \tvoid *buf = data->content;\n+\tunsigned long buf_size = data->size;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tstruct used_atom *atom = &used_atom[i];\n \t\tconst char *name = atom->name;\n \t\tstruct atom_value *v = &val[i];\n+\t\tenum atom_type atom_type = atom->atom_type;\n \n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n \n+\t\tif (atom_type == ATOM_RAW) {\n+\t\t\tif (atom->u.raw_data.option == RAW_BARE) {\n+\t\t\t\tv->s = xmemdupz(buf, buf_size);\n+\t\t\t\tv->s_size = buf_size;\n+\t\t\t} else if (atom->u.raw_data.option == RAW_LENGTH) {\n+\t\t\t\tv->s = xstrfmt(\"%\"PRIuMAX, (uintmax_t)buf_size);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif ((data->type != OBJ_TAG &&\n \t\t     data->type != OBJ_COMMIT) ||\n \t\t    (strcmp(name, \"body\") &&\n@@ -1460,9 +1508,11 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s\n \t\tbreak;\n \tcase OBJ_TREE:\n \t\t/* grab_tree_values(val, deref, obj, buf, sz); */\n+\t\tgrab_sub_body_contents(val, deref, data);\n \t\tbreak;\n \tcase OBJ_BLOB:\n \t\t/* grab_blob_values(val, deref, obj, buf, sz); */\n+\t\tgrab_sub_body_contents(val, deref, data);\n \t\tbreak;\n \tdefault:\n \t\tdie(\"Eh?  Object of type %d?\", obj->type);\n@@ -1765,7 +1815,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t\tint deref = 0;\n \t\tconst char *refname;\n \t\tstruct branch *branch = NULL;\n-\n+\t\tv->s_size = ATOM_VALUE_S_SIZE_INIT;\n \t\tv->handler = append_atom;\n \t\tv->atom = atom;\n \n@@ -2369,6 +2419,19 @@ static int compare_detached_head(struct ref_array_item *a, struct ref_array_item\n \treturn 0;\n }\n \n+static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n+{\n+\tconst char *s1 = vs1, *s2 = vs2;\n+\tconst char *end = s1 + n;\n+\n+\tfor (; s1 < end; s1++, s2++) {\n+\t\tint diff = tolower(*s1) - tolower(*s2);\n+\t\tif (diff)\n+\t\t\treturn diff;\n+\t}\n+\treturn 0;\n+}\n+\n static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)\n {\n \tstruct atom_value *va, *vb;\n@@ -2389,10 +2452,30 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n \t} else if (s->sort_flags & REF_SORTING_VERSION) {\n \t\tcmp = versioncmp(va->s, vb->s);\n \t} else if (cmp_type == FIELD_STR) {\n-\t\tint (*cmp_fn)(const char *, const char *);\n-\t\tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n-\t\t\t? strcasecmp : strcmp;\n-\t\tcmp = cmp_fn(va->s, vb->s);\n+\t\tif (va->s_size == ATOM_VALUE_S_SIZE_INIT &&\n+\t\t    vb->s_size == ATOM_VALUE_S_SIZE_INIT) {\n+\t\t\tint (*cmp_fn)(const char *, const char *);\n+\t\t\tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n+\t\t\t\t? strcasecmp : strcmp;\n+\t\t\tcmp = cmp_fn(va->s, vb->s);\n+\t\t} else {\n+\t\t\tint (*cmp_fn)(const void *, const void *, size_t);\n+\t\t\tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n+\t\t\t\t? memcasecmp : memcmp;\n+\t\t\tsize_t a_size = va->s_size == ATOM_VALUE_S_SIZE_INIT ?\n+\t\t\t\t\tstrlen(va->s) : va->s_size;\n+\t\t\tsize_t b_size = vb->s_size == ATOM_VALUE_S_SIZE_INIT ?\n+\t\t\t\t\tstrlen(vb->s) : vb->s_size;\n+\n+\t\t\tcmp = cmp_fn(va->s, vb->s, b_size > a_size ?\n+\t\t\t\t     a_size : b_size);\n+\t\t\tif (!cmp) {\n+\t\t\t\tif (a_size > b_size)\n+\t\t\t\t\tcmp = 1;\n+\t\t\t\telse if (a_size < b_size)\n+\t\t\t\t\tcmp = -1;\n+\t\t\t}\n+\t\t}\n \t} else {\n \t\tif (va->value < vb->value)\n \t\t\tcmp = -1;\n@@ -2492,6 +2575,7 @@ int format_ref_array_item(struct ref_array_item *info,\n \t}\n \tif (format->need_color_reset_at_eol) {\n \t\tstruct atom_value resetv;\n+\t\tresetv.s_size = ATOM_VALUE_S_SIZE_INIT;\n \t\tresetv.s = GIT_COLOR_RESET;\n \t\tif (append_atom(&resetv, &state, error_buf)) {\n \t\t\tpop_stack_element(&state.stack);\ndiff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh\nindex 9e0214076b4d..5f66d933ace0 100755\n--- a/t/t6300-for-each-ref.sh\n+++ b/t/t6300-for-each-ref.sh\n@@ -130,6 +130,8 @@ test_atom head parent:short=10 ''\n test_atom head numparent 0\n test_atom head object ''\n test_atom head type ''\n+test_atom head raw \"$(git cat-file commit refs/heads/main)\n+\"\n test_atom head '*objectname' ''\n test_atom head '*objecttype' ''\n test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'\n@@ -221,6 +223,15 @@ test_atom tag contents 'Tagging at 1151968727\n '\n test_atom tag HEAD ' '\n \n+test_expect_success 'basic atom: refs/tags/testtag *raw' '\n+\tgit cat-file commit refs/tags/testtag^{} >expected &&\n+\tgit for-each-ref --format=\"%(*raw)\" refs/tags/testtag >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_expect_success 'Check invalid atoms names are errors' '\n \ttest_must_fail git for-each-ref --format=\"%(INVALID)\" refs/heads\n '\n@@ -686,6 +697,15 @@ test_atom refs/tags/signed-empty contents:body ''\n test_atom refs/tags/signed-empty contents:signature \"$sig\"\n test_atom refs/tags/signed-empty contents \"$sig\"\n \n+test_expect_success 'basic atom: refs/tags/signed-empty raw' '\n+\tgit cat-file tag refs/tags/signed-empty >expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/tags/signed-empty >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_atom refs/tags/signed-short subject 'subject line'\n test_atom refs/tags/signed-short subject:sanitize 'subject-line'\n test_atom refs/tags/signed-short contents:subject 'subject line'\n@@ -695,6 +715,15 @@ test_atom refs/tags/signed-short contents:signature \"$sig\"\n test_atom refs/tags/signed-short contents \"subject line\n $sig\"\n \n+test_expect_success 'basic atom: refs/tags/signed-short raw' '\n+\tgit cat-file tag refs/tags/signed-short >expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/tags/signed-short >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_atom refs/tags/signed-long subject 'subject line'\n test_atom refs/tags/signed-long subject:sanitize 'subject-line'\n test_atom refs/tags/signed-long contents:subject 'subject line'\n@@ -708,6 +737,15 @@ test_atom refs/tags/signed-long contents \"subject line\n body contents\n $sig\"\n \n+test_expect_success 'basic atom: refs/tags/signed-long raw' '\n+\tgit cat-file tag refs/tags/signed-long >expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/tags/signed-long >actual &&\n+\tsanitize_pgp <expected >expected.clean &&\n+\tsanitize_pgp <actual >actual.clean &&\n+\techo \"\" >>expected.clean &&\n+\ttest_cmp expected.clean actual.clean\n+'\n+\n test_expect_success 'set up refs pointing to tree and blob' '\n \tgit update-ref refs/mytrees/first refs/heads/main^{tree} &&\n \tgit update-ref refs/myblobs/first refs/heads/main:one\n@@ -720,6 +758,16 @@ test_atom refs/mytrees/first contents:body \"\"\n test_atom refs/mytrees/first contents:signature \"\"\n test_atom refs/mytrees/first contents \"\"\n \n+test_expect_success 'basic atom: refs/mytrees/first raw' '\n+\tgit cat-file tree refs/mytrees/first >expected &&\n+\techo \"\" >>expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/mytrees/first >actual &&\n+\ttest_cmp expected actual &&\n+\tgit cat-file -s refs/mytrees/first >expected &&\n+\tgit for-each-ref --format=\"%(raw:size)\" refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_atom refs/myblobs/first subject \"\"\n test_atom refs/myblobs/first contents:subject \"\"\n test_atom refs/myblobs/first body \"\"\n@@ -727,6 +775,165 @@ test_atom refs/myblobs/first contents:body \"\"\n test_atom refs/myblobs/first contents:signature \"\"\n test_atom refs/myblobs/first contents \"\"\n \n+test_expect_success 'basic atom: refs/myblobs/first raw' '\n+\tgit cat-file blob refs/myblobs/first >expected &&\n+\techo \"\" >>expected &&\n+\tgit for-each-ref --format=\"%(raw)\" refs/myblobs/first >actual &&\n+\ttest_cmp expected actual &&\n+\tgit cat-file -s refs/myblobs/first >expected &&\n+\tgit for-each-ref --format=\"%(raw:size)\" refs/myblobs/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'set up refs pointing to binary blob' '\n+\tprintf \"%b\" \"a\\0b\\0c\" >blob1 &&\n+\tprintf \"%b\" \"a\\0c\\0b\" >blob2 &&\n+\tprintf \"%b\" \"\\0a\\0b\\0c\" >blob3 &&\n+\tprintf \"%b\" \"abc\" >blob4 &&\n+\tprintf \"%b\" \"\\0 \\0 \\0 \" >blob5 &&\n+\tprintf \"%b\" \"\\0 \\0a\\0 \" >blob6 &&\n+\tprintf \"%b\" \"  \" >blob7 &&\n+\t>blob8 &&\n+\tgit hash-object blob1 -w | xargs git update-ref refs/myblobs/blob1 &&\n+\tgit hash-object blob2 -w | xargs git update-ref refs/myblobs/blob2 &&\n+\tgit hash-object blob3 -w | xargs git update-ref refs/myblobs/blob3 &&\n+\tgit hash-object blob4 -w | xargs git update-ref refs/myblobs/blob4 &&\n+\tgit hash-object blob5 -w | xargs git update-ref refs/myblobs/blob5 &&\n+\tgit hash-object blob6 -w | xargs git update-ref refs/myblobs/blob6 &&\n+\tgit hash-object blob7 -w | xargs git update-ref refs/myblobs/blob7 &&\n+\tgit hash-object blob8 -w | xargs git update-ref refs/myblobs/blob8\n+'\n+\n+test_expect_success 'Verify sorts with raw' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob8\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/myblobs/blob3\n+\trefs/myblobs/blob7\n+\trefs/mytrees/first\n+\trefs/myblobs/first\n+\trefs/myblobs/blob1\n+\trefs/myblobs/blob2\n+\trefs/myblobs/blob4\n+\trefs/heads/main\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname)\" --sort=raw \\\n+\t\trefs/heads/main refs/myblobs/ refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Verify sorts with raw:size' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob8\n+\trefs/myblobs/first\n+\trefs/myblobs/blob7\n+\trefs/heads/main\n+\trefs/myblobs/blob4\n+\trefs/myblobs/blob1\n+\trefs/myblobs/blob2\n+\trefs/myblobs/blob3\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/mytrees/first\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname)\" --sort=raw:size \\\n+\t\trefs/heads/main refs/myblobs/ refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'validate raw atom with %(if:equals)' '\n+\tcat >expected <<-EOF &&\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\trefs/myblobs/blob4\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tnot equals\n+\tEOF\n+\tgit for-each-ref --format=\"%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)\" \\\n+\t\trefs/myblobs/ refs/heads/ >actual &&\n+\ttest_cmp expected actual\n+'\n+test_expect_success 'validate raw atom with %(if:notequals)' '\n+\tcat >expected <<-EOF &&\n+\trefs/heads/ambiguous\n+\trefs/heads/main\n+\trefs/heads/newtag\n+\trefs/myblobs/blob1\n+\trefs/myblobs/blob2\n+\trefs/myblobs/blob3\n+\tequals\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/myblobs/blob7\n+\trefs/myblobs/blob8\n+\trefs/myblobs/first\n+\tEOF\n+\tgit for-each-ref --format=\"%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)\" \\\n+\t\trefs/myblobs/ refs/heads/ >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'empty raw refs with %(if)' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob1 not empty\n+\trefs/myblobs/blob2 not empty\n+\trefs/myblobs/blob3 not empty\n+\trefs/myblobs/blob4 not empty\n+\trefs/myblobs/blob5 not empty\n+\trefs/myblobs/blob6 not empty\n+\trefs/myblobs/blob7 empty\n+\trefs/myblobs/blob8 empty\n+\trefs/myblobs/first not empty\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)\" \\\n+\t\trefs/myblobs/ >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '%(raw) with --python must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --python\n+'\n+\n+test_expect_success '%(raw) with --tcl must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --tcl\n+'\n+\n+test_expect_success '%(raw) with --perl must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --perl\n+'\n+\n+test_expect_success '%(raw) with --shell must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --shell\n+'\n+\n+test_expect_success '%(raw) with --shell and --sort=raw must failed' '\n+\ttest_must_fail git for-each-ref --format=\"%(raw)\" --sort=raw --shell\n+'\n+\n+test_expect_success '%(raw:size) with --shell' '\n+\tgit for-each-ref --format=\"%(raw:size)\" | while read line\n+\tdo\n+\t\techo \"'\\''$line'\\''\" >>expect\n+\tdone &&\n+\tgit for-each-ref --format=\"%(raw:size)\" --shell >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'for-each-ref --format compare with cat-file --batch' '\n+\tgit rev-parse refs/mytrees/first | git cat-file --batch >expected &&\n+\tgit for-each-ref --format=\"%(objectname) %(objecttype) %(objectsize)\n+%(raw)\" refs/mytrees/first >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'set up multiple-sort tags' '\n \tfor when in 100000 200000\n \tdo\n-- \ngitgitgadget\n"},{"id":"426414","messageId":"CAP8UFD0PPU4O+L3p0qJhKpsiB=MvyGJbbtxP6Vu0YeeVQ-Ojmg@mail.gmail.com","threadId":"55818","inReplyTo":"pull.966.v2.git.1622808751.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-06-04T12:53:26Z","receivedAt":"2021-06-04T12:54:54Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"No need to resend as it's a cover letter, but just in case there is\nanother round and you copy things from this cover letter:\n\nOn Fri, Jun 4, 2021 at 2:12 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> In order to make git cat-file --batch use ref-filter logic, %(raw) atom is\n> adding to ref-filter.\n\ns/adding/added/\n\n> Change from last version:\n>\n>  1. Change --<lang> and --format=%(raw) checkpoint to verify_ref_format(),\n>     which make it more scalable.\n\ns/make/makes/\n\n>  2. Change grab_sub_body_contents() use struct expand_data *data instread of\n\ns/use/to use/\ns/instread/instead/\n\n>     using obj,buf,buf_size to pass object info which can reduce the delivery\n>     of function parameters.\n"},{"id":"426417","messageId":"CAP8UFD3m-=4=a9XX6Nu45+-4QdeNYi54rXgqizR7HrOh_qk8OQ@mail.gmail.com","threadId":"55818","inReplyTo":"0efed9435b59098f3ad928acd46c3c7e9f13677d.1622808751.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-06-04T13:23:58Z","receivedAt":"2021-06-04T13:25:23Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Jun 4, 2021 at 2:12 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Add new formatting option `%(raw)`, which will print the raw\n> object data without any changes. It will help further to migrate\n> all cat-file formatting logic from cat-file to ref-filter.\n>\n> The raw data of blob, tree objects may contain '\\0', but most of\n> the logic in `ref-filter` depands on the output of the atom being\n\ns/depands/depends/\n\n> text (specifically, no embedded NULs in it).\n\n> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\n> index 2ae2478de706..8f8d8cd1e04f 100644\n> --- a/Documentation/git-for-each-ref.txt\n> +++ b/Documentation/git-for-each-ref.txt\n> @@ -235,6 +235,15 @@ and `date` to extract the named component.  For email fields (`authoremail`,\n>  without angle brackets, and `:localpart` to get the part before the `@` symbol\n>  out of the trimmed email.\n>\n> +The raw data in a object is `raw`.\n\ns/a object/an object/\n\n> +\n> +raw:size::\n> +       The raw data size of the object.\n> +\n> +Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,\n> +`--perl` because the host language may not support arbitrary binary data in the\n> +variables of its string type.\n\n\n\n\n\n> @@ -1765,7 +1815,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n>                 int deref = 0;\n>                 const char *refname;\n>                 struct branch *branch = NULL;\n> -\n> +               v->s_size = ATOM_VALUE_S_SIZE_INIT;\n\nIt looks like a blank line was removed as you added the above new line.\n\n>                 v->handler = append_atom;\n>                 v->atom = atom;\n>\n"},{"id":"426467","messageId":"CAOLTT8SEXfNcpzKaGzvMXxJ1NS_dw-S--K5a_jQ=d3be5CgpXw@mail.gmail.com","threadId":"55818","inReplyTo":"CAP8UFD0PPU4O+L3p0qJhKpsiB=MvyGJbbtxP6Vu0YeeVQ-Ojmg@mail.gmail.com","subject":"Re: [PATCH v2 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-05T04:34:10Z","receivedAt":"2021-06-05T04:34:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Hi, Christian,\n\nChristian Couder <christian.couder@gmail.com> 于2021年6月4日周五 下午8:53写道：\n>\n> No need to resend as it's a cover letter, but just in case there is\n> another round and you copy things from this cover letter:\n>\n\nSorry, what is the bad place in this cover letter I write? This\ncover letter is also different from the last time ...\n\n> On Fri, Jun 4, 2021 at 2:12 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > In order to make git cat-file --batch use ref-filter logic, %(raw) atom is\n> > adding to ref-filter.\n>\n> s/adding/added/\n>\n> > Change from last version:\n> >\n> >  1. Change --<lang> and --format=%(raw) checkpoint to verify_ref_format(),\n> >     which make it more scalable.\n>\n> s/make/makes/\n>\n> >  2. Change grab_sub_body_contents() use struct expand_data *data instread of\n>\n> s/use/to use/\n> s/instread/instead/\n>\n> >     using obj,buf,buf_size to pass object info which can reduce the delivery\n> >     of function parameters.\n\nThanks for these grammatical corrections.\n--\nZheNing Hu\n"},{"id":"426468","messageId":"CAP8UFD3SxAh1ZW2TTMqn2e6=qP1YDd5qVd15yFR669+cPr481Q@mail.gmail.com","threadId":"55818","inReplyTo":"CAOLTT8SEXfNcpzKaGzvMXxJ1NS_dw-S--K5a_jQ=d3be5CgpXw@mail.gmail.com","subject":"Re: [PATCH v2 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-06-05T04:49:18Z","receivedAt":"2021-06-05T04:49:45Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jun 5, 2021 at 6:34 AM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> Hi, Christian,\n>\n> Christian Couder <christian.couder@gmail.com> 于2021年6月4日周五 下午8:53写道：\n> >\n> > No need to resend as it's a cover letter, but just in case there is\n> > another round and you copy things from this cover letter:\n> >\n>\n> Sorry, what is the bad place in this cover letter I write? This\n> cover letter is also different from the last time ...\n\nI was talking about the grammatical issues below in the cover letter.\nSometimes people copy things, for example a text explaining what the\npatch series is about, from the cover letter of version N to the cover\nletter of version N + 1, so I thought that telling you about\ngrammatical issues in this cover letter could perhaps help you if you\nhave to write another cover letter for another version of this patch\nseries.\n"},{"id":"426469","messageId":"CAOLTT8RP1myY32cOMMvU4Q2_XyKsL=+XERij7RYJYM3We0rP8w@mail.gmail.com","threadId":"55818","inReplyTo":"CAP8UFD3SxAh1ZW2TTMqn2e6=qP1YDd5qVd15yFR669+cPr481Q@mail.gmail.com","subject":"Re: [PATCH v2 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-05T05:42:07Z","receivedAt":"2021-06-05T05:42:39Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年6月5日周六 下午12:49写道：\n>\n> On Sat, Jun 5, 2021 at 6:34 AM ZheNing Hu <adlternative@gmail.com> wrote:\n> >\n> > Hi, Christian,\n> >\n> > Christian Couder <christian.couder@gmail.com> 于2021年6月4日周五 下午8:53写道：\n> > >\n> > > No need to resend as it's a cover letter, but just in case there is\n> > > another round and you copy things from this cover letter:\n> > >\n> >\n> > Sorry, what is the bad place in this cover letter I write? This\n> > cover letter is also different from the last time ...\n>\n> I was talking about the grammatical issues below in the cover letter.\n> Sometimes people copy things, for example a text explaining what the\n> patch series is about, from the cover letter of version N to the cover\n> letter of version N + 1, so I thought that telling you about\n> grammatical issues in this cover letter could perhaps help you if you\n> have to write another cover letter for another version of this patch\n> series.\n\nOk, I get it.\n\nI want to mention another question:\nIf I have a new patch series about %(rest) is based on the current %(raw)\npatch series, should I submit it immediately?\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426472","messageId":"CAP8UFD0xyOseUjSMUJ2fMzHo3d++PN7SOV8eXAvhqTGbkNFwaw@mail.gmail.com","threadId":"55818","inReplyTo":"CAOLTT8RP1myY32cOMMvU4Q2_XyKsL=+XERij7RYJYM3We0rP8w@mail.gmail.com","subject":"Re: [PATCH v2 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-06-05T06:45:27Z","receivedAt":"2021-06-05T06:45:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jun 5, 2021 at 7:42 AM ZheNing Hu <adlternative@gmail.com> wrote:\n\n> I want to mention another question:\n> If I have a new patch series about %(rest) is based on the current %(raw)\n> patch series, should I submit it immediately?\n\nYeah, I think it's ok to send it as long as you explicitly specify\n(using a link for example) the patch series on the mailing list it\ndepends on.\n"},{"id":"426474","messageId":"CAOLTT8ReP+zX-D2sHxODpcFeU8-Vp2C4d+G8jRb6zHTmpANb-Q@mail.gmail.com","threadId":"55818","inReplyTo":"CAP8UFD0xyOseUjSMUJ2fMzHo3d++PN7SOV8eXAvhqTGbkNFwaw@mail.gmail.com","subject":"Re: [PATCH v2 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-05T08:05:45Z","receivedAt":"2021-06-05T08:06:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年6月5日周六 下午2:45写道：\n>\n> On Sat, Jun 5, 2021 at 7:42 AM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> > I want to mention another question:\n> > If I have a new patch series about %(rest) is based on the current %(raw)\n> > patch series, should I submit it immediately?\n>\n> Yeah, I think it's ok to send it as long as you explicitly specify\n> (using a link for example) the patch series on the mailing list it\n> depends on.\n\nOk. Because %(raw:textconv) %(raw:filter) dependent on %(raw) and %(rest),\n%(raw) seems to have experienced a long cycle. These codes about new atoms\nseem to stay in my local git repo for a long time. It may be a good thing to\nsend them earlier. :)\n\nThanks.\n"}]}