{"thread":{"id":"55782","subject":"[PATCH 0/2] [GSOC] ref-filter: add %(raw) atom","startedAt":"2021-05-27T14:43:29Z","lastAt":"2021-06-01T13:48:52Z","messageCount":41,"participants":["ZheNing Hu via GitGitGadget","Felipe Contreras","Junio C Hamano","ZheNing Hu","Phillip Wood","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"425682","messageId":"pull.963.git.1622126603.gitgitgadget@gmail.com","threadId":"55782","inReplyTo":null,"subject":"[PATCH 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-05-27T14:43:20Z","receivedAt":"2021-05-27T14:43:29Z","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. In my discussion with Junio, I came to the conclusion that\n    --format=\"%(raw)\" should not be used with --python, --perl, --shell,\n    --tcl. Therefore, die if both --format=\"%(raw)\" and\n\n--language are given in parse_ref_filter_atom(). The reason I don't move\nthis part to raw_atom_parser() is if I move it to raw_atom_parser(), when we\nuse:\n\ngit --format=%raw --sort=raw --python`\n\nGit will continue to run instead of die because parse_sorting_atom() will\nuse a dummy ref_format and don't remember --language details, next time\nformat_ref_array_item() will reuse the used_atom entry of sorting atom in\nparse_ref_filter_atom(), This will skip the check in raw_atom_parser(). 2.\nGive atom_value.s_size a init value ATOM_VALUE_S_SIZE_INIT (-1), which can\nhelp us distinguish an object whose length is 0 and an object whose s_size\nhas not been modified after initialization. 3. Add %(header) atom.\n\nZheNing Hu (2):\n  [GSOC] ref-filter: add %(raw) atom\n  [GSOC] ref-filter: add %(header) atom\n\n Documentation/git-for-each-ref.txt |  21 +++\n ref-filter.c                       | 182 ++++++++++++++++++----\n t/t6300-for-each-ref.sh            | 236 +++++++++++++++++++++++++++++\n 3 files changed, 412 insertions(+), 27 deletions(-)\n\n\nbase-commit: 5d5b1473453400224ebb126bf3947e0a3276bdf5\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-963%2Fadlternative%2Fref-filter-raw-atom-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-963/adlternative/ref-filter-raw-atom-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/963\n-- \ngitgitgadget\n"},{"id":"425684","messageId":"b3848f24f2d3f91fc96f20b5a08cbfbe721acbd6.1622126603.git.gitgitgadget@gmail.com","threadId":"55782","inReplyTo":"pull.963.git.1622126603.gitgitgadget@gmail.com","subject":"[PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-05-27T14:43:21Z","receivedAt":"2021-05-27T14:43:30Z","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\na structured string (end with '\\0').\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)` should not combine with `--python`, `--shell`,\n`--tcl`, `--perl` because if our binary raw data is passed to a variable\nin the host language, the host languages may cause escape errors.\n\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 |  14 +++\n ref-filter.c                       | 156 +++++++++++++++++++----\n t/t6300-for-each-ref.sh            | 191 +++++++++++++++++++++++++++++\n 3 files changed, 334 insertions(+), 27 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 2ae2478de706..f6ae751fd256 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -235,6 +235,20 @@ 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`, For commit and tag objects, `raw` contain\n+`header` and `contents` two parts, `header` is structured part of raw data, it\n+composed of \"tree XXX\", \"parent YYY\", etc lines in commits , or composed of\n+\"object OOO\", \"type TTT\", etc lines in tags; `contents` is unstructured \"free\n+text\" part of raw object data. For blob and tree objects, their raw data don't\n+have `header` and `contents` parts.\n+\n+raw:size::\n+\tThe raw data size of the object.\n+\n+Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tcl`,\n+`--perl` because if our binary raw data is passed to a variable in the host language,\n+the host languages may cause escape errors.\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 97116e12d7c4..c2abf5da7006 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -138,6 +138,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@@ -370,6 +373,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+\t} else 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@@ -530,6 +545,7 @@ static struct {\n \t{ \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n \t{ \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n \t{ \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n+\t{ \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n \t{ \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t{ \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t{ \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n@@ -564,12 +580,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@@ -588,6 +607,10 @@ 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+\tif (format->quote_style && starts_with(sp, \"raw\"))\n+\t\treturn strbuf_addf_ret(err, -1, _(\"--format=%.*s should not combine 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@@ -652,11 +675,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@@ -683,9 +709,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@@ -785,14 +814,16 @@ 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+\tconst char *s = buf->buf;\n+\tsize_t cur_len = 0;\n+\n+\twhile ((cur_len != buf->len) && (isspace(*s) || *s == '\\0')) {\n \t\ts++;\n+\t\tcur_len++;\n \t}\n-\treturn 1;\n+\treturn cur_len == buf->len;\n }\n \n static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,\n@@ -800,6 +831,7 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta\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@@ -810,18 +842,28 @@ 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 (!if_then_else->str)\n+\t\t\tBUG(\"when if_then_else->cmp_status == COMPARE_EQUAL,\"\n+\t\t\t    \"if_then_else->str must not be null\");\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 (!if_then_else->str)\n+\t\t\tBUG(\"when if_then_else->cmp_status == COMPARE_UNEQUAL,\"\n+\t\t\t    \"if_then_else->str must not be null\");\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@@ -867,7 +909,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@@ -1292,7 +1334,7 @@ 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_raw_data(struct atom_value *val, int deref, void *buf, unsigned long buf_size, struct object *obj)\n {\n \tint i;\n \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n@@ -1307,10 +1349,22 @@ 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 (starts_with(name, \"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\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+\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@@ -1374,25 +1428,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);\n+\t\tgrab_raw_data(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);\n+\t\tgrab_raw_data(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_raw_data(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_raw_data(val, deref, buf, buf_size, obj);\n \t\tbreak;\n \tdefault:\n \t\tdie(\"Eh?  Object of type %d?\", obj->type);\n@@ -1614,7 +1673,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@@ -1694,7 +1753,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@@ -2297,6 +2356,25 @@ 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+\tsize_t i;\n+\tconst char *s1 = (const char *)vs1;\n+\tconst char *s2 = (const char *)vs2;\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tunsigned char u1 = s1[i];\n+\t\tunsigned char u2 = s2[i];\n+\t\tint U1 = toupper (u1);\n+\t\tint U2 = toupper (u2);\n+\t\tint diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n+\t\t\t: U1 < U2 ? -1 : U2 < U1);\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@@ -2304,6 +2382,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n \tint cmp_detached_head = 0;\n \tcmp_type cmp_type = used_atom[s->atom].type;\n \tstruct strbuf err = STRBUF_INIT;\n+\tsize_t slen = 0;\n \n \tif (get_ref_atom_value(a, s->atom, &va, &err))\n \t\tdie(\"%s\", err.buf);\n@@ -2317,10 +2396,32 @@ 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+\n+\t\t\tif (va->s_size != ATOM_VALUE_S_SIZE_INIT &&\n+\t\t\t    vb->s_size != ATOM_VALUE_S_SIZE_INIT) {\n+\t\t\t\tcmp = cmp_fn(va->s, vb->s, va->s_size > vb->s_size ?\n+\t\t\t\t       vb->s_size : va->s_size);\n+\t\t\t} else if (va->s_size == ATOM_VALUE_S_SIZE_INIT) {\n+\t\t\t\tslen = strlen(va->s);\n+\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > vb->s_size ?\n+\t\t\t\t\t     vb->s_size : slen);\n+\t\t\t} else {\n+\t\t\t\tslen = strlen(vb->s);\n+\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > va->s_size ?\n+\t\t\t\t\t     slen : va->s_size);\n+\t\t\t}\n+\t\t\tcmp = cmp ? cmp : va->s_size - vb->s_size;\n+\t\t}\n \t} else {\n \t\tif (va->value < vb->value)\n \t\t\tcmp = -1;\n@@ -2420,6 +2521,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..07de4a84d70b 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,149 @@ 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+\t>blob7 &&\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+'\n+\n+test_expect_success 'Verify sorts with raw' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob7\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/myblobs/blob3\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/blob7\n+\trefs/myblobs/first\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+\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/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 empty\n+\trefs/myblobs/blob6 not empty\n+\trefs/myblobs/blob7 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+\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 '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\n"},{"id":"425683","messageId":"aa6d73f3e526f416ee1e4e332e9ca3119efba0e8.1622126603.git.gitgitgadget@gmail.com","threadId":"55782","inReplyTo":"pull.963.git.1622126603.gitgitgadget@gmail.com","subject":"[PATCH 2/2] [GSOC] ref-filter: add %(header) atom","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-05-27T14:43:22Z","receivedAt":"2021-05-27T14:43:31Z","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 `%(header)`, which will print the\nthe structured header part of the raw object data.\n\nIn the storage layout of an object: blob and tree only\ncontains raw data; commit and tag raw data contains two part:\nheader and contents. The header of tag contains \"object OOO\",\n\"type TTT\", \"tag AAA\", \"tagger GGG\"; The header of commit\ncontains \"tree RRR\", \"parent PPP\", \"author UUU\", \"committer CCC\".\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  7 +++++\n ref-filter.c                       | 26 +++++++++++++++++\n t/t6300-for-each-ref.sh            | 45 ++++++++++++++++++++++++++++++\n 3 files changed, 78 insertions(+)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex f6ae751fd256..7827e48cde75 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -249,6 +249,13 @@ Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tc\n `--perl` because if our binary raw data is passed to a variable in the host language,\n the host languages may cause escape errors.\n \n+The structured header part of the raw data in a commit or a tag object is `header`,\n+it composed of \"tree XXX\", \"parent YYY\", etc lines in commits, or composed of\n+\"object OOO\", \"type TTT\", etc lines in tags.\n+\n+header:size::\n+\tThe header size of the object.\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 c2abf5da7006..2f426830f562 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -141,6 +141,9 @@ static struct used_atom {\n \t\tstruct {\n \t\t\tenum { RAW_BARE, RAW_LENGTH } option;\n \t\t} raw_data;\n+\t\tstruct {\n+\t\t\tenum { H_BARE, H_LENGTH } option;\n+\t\t} header;\n \t\tstruct {\n \t\t\tcmp_status cmp_status;\n \t\t\tconst char *str;\n@@ -385,6 +388,18 @@ static int raw_atom_parser(const struct ref_format *format, struct used_atom *at\n \treturn 0;\n }\n \n+static int header_atom_parser(const struct ref_format *format, struct used_atom *atom,\n+\t\t\t      const char *arg, struct strbuf *err)\n+{\n+\tif (!arg)\n+\t\tatom->u.header.option = H_BARE;\n+\telse if (!strcmp(arg, \"size\"))\n+\t\tatom->u.header.option = H_LENGTH;\n+\telse\n+\t\treturn strbuf_addf_ret(err, -1, _(\"unrecognized %%(header) 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@@ -546,6 +561,7 @@ static struct {\n \t{ \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n \t{ \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n \t{ \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n+\t{ \"header\", SOURCE_OBJ, FIELD_STR, header_atom_parser },\n \t{ \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t{ \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t{ \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n@@ -1362,6 +1378,7 @@ static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned\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, \"header\") &&\n \t\t     !starts_with(name, \"subject\") &&\n \t\t     !starts_with(name, \"trailers\") &&\n \t\t     !starts_with(name, \"contents\")))\n@@ -1372,6 +1389,15 @@ static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned\n \t\t\t\t    &bodypos, &bodylen, &nonsiglen,\n \t\t\t\t    &sigpos, &siglen);\n \n+\t\tif (starts_with(name, \"header\")) {\n+\t\t\tsize_t header_len = subpos - (const char *)buf - 1;\n+\t\t\tif (atom->u.header.option == H_BARE) {\n+\t\t\t\tv->s = xmemdupz(buf, header_len);\n+\t\t\t} else if (atom->u.header.option == H_LENGTH)\n+\t\t\t\tv->s = xstrfmt(\"%\"PRIuMAX, (uintmax_t)header_len);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (atom->u.contents.option == C_SUB)\n \t\t\tv->s = copy_subject(subpos, sublen);\n \t\telse if (atom->u.contents.option == C_SUB_SANITIZE) {\ndiff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh\nindex 07de4a84d70b..11fc8fc53649 100755\n--- a/t/t6300-for-each-ref.sh\n+++ b/t/t6300-for-each-ref.sh\n@@ -232,6 +232,35 @@ test_expect_success 'basic atom: refs/tags/testtag *raw' '\n \ttest_cmp expected.clean actual.clean\n '\n \n+test_expect_success 'basic atom: refs/tags/testtag header' '\n+\tcat >expected <<-EOF &&\n+\tobject ea122842f48be4afb2d1fc6a4b96c05885ab7463\n+\ttype commit\n+\ttag testtag\n+\ttagger C O Mitter <committer@example.com> 1151968725 +0200\n+\n+\tEOF\n+\tgit for-each-ref --format=\"%(header)\" refs/tags/testtag >actual &&\n+\ttest_cmp expected actual &&\n+\techo \"131\" >expected &&\n+\tgit for-each-ref --format=\"%(header:size)\" refs/tags/testtag >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'basic atom: refs/heads/main header' '\n+\tcat >expected <<-EOF &&\n+\ttree 8039ce043250c402d62ca312e9596e42ce1c7bb0\n+\tauthor A U Thor <author@example.com> 1151968724 +0200\n+\tcommitter C O Mitter <committer@example.com> 1151968723 +0200\n+\n+\tEOF\n+\tgit for-each-ref --format=\"%(header)\" refs/heads/main >actual &&\n+\ttest_cmp expected actual &&\n+\techo \"162\" >expected &&\n+\tgit for-each-ref --format=\"%(header:size)\" refs/heads/main >actual &&\n+\ttest_cmp expected actual\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@@ -768,6 +797,14 @@ test_expect_success 'basic atom: refs/mytrees/first raw' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'basic atom: refs/mytrees/first header' '\n+\techo \"\" >expected &&\n+\tgit for-each-ref --format=\"%(header)\" refs/mytrees/first >actual &&\n+\ttest_cmp expected actual &&\n+\tgit for-each-ref --format=\"%(header: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@@ -785,6 +822,14 @@ test_expect_success 'basic atom: refs/myblobs/first raw' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'basic atom: refs/myblobs/first header' '\n+\techo \"\" >expected &&\n+\tgit for-each-ref --format=\"%(header)\" refs/myblobs/first >actual &&\n+\ttest_cmp expected actual &&\n+\tgit for-each-ref --format=\"%(header: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-- \ngitgitgadget\n"},{"id":"425686","messageId":"60afbd4b81e44_265302086d@natae.notmuch","threadId":"55782","inReplyTo":"pull.963.git.1622126603.gitgitgadget@gmail.com","subject":"RE: [PATCH 0/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-27T15:39:55Z","receivedAt":"2021-05-27T15:40:00Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"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. In my discussion with Junio, I came to the conclusion that\n>     --format=\"%(raw)\" should not be used with --python, --perl, --shell,\n>     --tcl. Therefore, die if both --format=\"%(raw)\" and\n> \n> --language are given in parse_ref_filter_atom(). The reason I don't move\n> this part to raw_atom_parser() is if I move it to raw_atom_parser(), when we\n> use:\n> \n> git --format=%raw --sort=raw --python`\n\nMissing the command I presume (and the other backtick).\n\n-- \nFelipe Contreras\n"},{"id":"425689","messageId":"60afca827a28f_265302085b@natae.notmuch","threadId":"55782","inReplyTo":"b3848f24f2d3f91fc96f20b5a08cbfbe721acbd6.1622126603.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-27T16:36:18Z","receivedAt":"2021-05-27T16:36:24Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"ZheNing Hu via GitGitGadget wrote:\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> +\t} else if (!strcmp(arg, \"size\"))\n\nNo need for braces.\n\n  if (!arg)\n    ...\n  else\n\n> @@ -1307,10 +1349,22 @@ 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 (starts_with(name, \"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\nI think it's better to be consistent: if you used braces in the if, uses\nbraces in else.\n\n> +\t\t\tcontinue;\n> +\t\t}\n\n> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n\nWhy void *? We can delcare as char *.\n\n> +{\n> +\tsize_t i;\n> +\tconst char *s1 = (const char *)vs1;\n> +\tconst char *s2 = (const char *)vs2;\n\nThen we avoid this extra step.\n\n> +\tfor (i = 0; i < n; i++) {\n> +\t\tunsigned char u1 = s1[i];\n> +\t\tunsigned char u2 = s2[i];\n\nThere's no need for two entirely new variables...\n\n> +\t\tint U1 = toupper (u1);\n> +\t\tint U2 = toupper (u2);\n\nYou can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,\nnot `foo (x)`).\n\nWhile we are at it, why keep an extra index from s1, when s1 is never\nused again?\n\nWe can simply advance both s1 and s2:\n\n  s1++, s2++\n\n> +\t\tint diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> +\t\t\t: U1 < U2 ? -1 : U2 < U1);\n\nI don't understand what this is supposed to achieve. Both U1 and U2 are\nintegers, pretty low integers actually.\n\nIf we get rid if that complexity we don't even need U1 or U2, just do:\n\n  diff = toupper(u1) - toupper(u2);\n\n> +\t\tif (diff)\n> +\t\t\treturn diff;\n> +\t}\n> +\treturn 0;\n> +}\n\nAll we have to do is define the end point, and then we don't need i:\n\n\tstatic int memcasecmp(const char *s1, const char *s2, size_t n)\n\t{\n\t\tconst char *end = s1 + n;\n\t\tfor (; s1 < end; s1++, s2++) {\n\t\t\tint diff = tolower(*s1) - tolower(*s2);\n\t\t\tif (diff)\n\t\t\t\treturn diff;\n\t\t}\n\t\treturn 0;\n\t}\n\n(and I personally prefer lower to upper)\n\nCheck the following resource for a detailed explanation of why my\nmodified version is considered good taste:\n\nhttps://github.com/felipec/linked-list-good-taste\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> @@ -2304,6 +2382,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n>  \tint cmp_detached_head = 0;\n>  \tcmp_type cmp_type = used_atom[s->atom].type;\n>  \tstruct strbuf err = STRBUF_INIT;\n> +\tsize_t slen = 0;\n>  \n>  \tif (get_ref_atom_value(a, s->atom, &va, &err))\n>  \t\tdie(\"%s\", err.buf);\n> @@ -2317,10 +2396,32 @@ 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> +\n> +\t\t\tif (va->s_size != ATOM_VALUE_S_SIZE_INIT &&\n> +\t\t\t    vb->s_size != ATOM_VALUE_S_SIZE_INIT) {\n> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, va->s_size > vb->s_size ?\n> +\t\t\t\t       vb->s_size : va->s_size);\n> +\t\t\t} else if (va->s_size == ATOM_VALUE_S_SIZE_INIT) {\n> +\t\t\t\tslen = strlen(va->s);\n> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > vb->s_size ?\n> +\t\t\t\t\t     vb->s_size : slen);\n> +\t\t\t} else {\n> +\t\t\t\tslen = strlen(vb->s);\n> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > va->s_size ?\n> +\t\t\t\t\t     slen : va->s_size);\n> +\t\t\t}\n> +\t\t\tcmp = cmp ? cmp : va->s_size - vb->s_size;\n> +\t\t}\n\nThis hurts my eyes. I think the complexity of this chunk warrants a\nseparate function. Then the logic would be easer to see.\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"425690","messageId":"60afcada976dc_26530208a1@natae.notmuch","threadId":"55782","inReplyTo":"aa6d73f3e526f416ee1e4e332e9ca3119efba0e8.1622126603.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 2/2] [GSOC] ref-filter: add %(header) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-27T16:37:46Z","receivedAt":"2021-05-27T16:37:53Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"ZheNing Hu via GitGitGadget wrote:\n\n> @@ -1372,6 +1389,15 @@ static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned\n>  \t\t\t\t    &bodypos, &bodylen, &nonsiglen,\n>  \t\t\t\t    &sigpos, &siglen);\n>  \n> +\t\tif (starts_with(name, \"header\")) {\n> +\t\t\tsize_t header_len = subpos - (const char *)buf - 1;\n> +\t\t\tif (atom->u.header.option == H_BARE) {\n> +\t\t\t\tv->s = xmemdupz(buf, header_len);\n> +\t\t\t} else if (atom->u.header.option == H_LENGTH)\n\nNo need for braces in the if.\n\n> +\t\t\t\tv->s = xstrfmt(\"%\"PRIuMAX, (uintmax_t)header_len);\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n>  \t\tif (atom->u.contents.option == C_SUB)\n>  \t\t\tv->s = copy_subject(subpos, sublen);\n>  \t\telse if (atom->u.contents.option == C_SUB_SANITIZE) {\n\n-- \nFelipe Contreras\n"},{"id":"425727","messageId":"xmqq1r9r8spu.fsf@gitster.g","threadId":"55782","inReplyTo":"b3848f24f2d3f91fc96f20b5a08cbfbe721acbd6.1622126603.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-28T03:03:57Z","receivedAt":"2021-05-28T03:04:07Z","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> 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> a structured string (end with '\\0').\n\nText, yes, string is also OK.  But structured?  Probably not.\n\n    ... being text (specifically, no embedded NULs in it).\n\n> E.g. `quote_formatting()` use `strbuf_addstr()` or `*._quote_buf()`\n> add 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,\n> which is incorrect.\n>\n> Therefore, add a new member in `struct atom_value`: `s_size`, which\n> can record raw object size, it can help us add raw object data to\n> the buffer or compare two buffers which contain raw object data.\n\nOther than the phrasing issue around \"structured\", all of the above\nis a good description.\n\n> Beyond, `--format=%(raw)` should not combine with `--python`, `--shell`,\n> `--tcl`, `--perl` because if our binary raw data is passed to a variable\n> in the host language, the host languages may cause escape errors.\n\nOK.  I think at least --perl and possibly --python should be able to\nexpress NULs in the \"string\" type we use from the host language, but\nI am perfectly fine with the decision to leave it to later updates.\n\nAfter all, the --<lang> feature to write scriptlets via --format and\nexecute them in the named language interpreter probably is not as\noften used as it was originally designed for, so it might be that\nnobody will ask for such \"later updates\".\n\n> @@ -235,6 +235,20 @@ 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`, For commit and tag objects, `raw` contain\n> +`header` and `contents` two parts, `header` is structured part of raw data, it\n> +composed of \"tree XXX\", \"parent YYY\", etc lines in commits , or composed of\n> +\"object OOO\", \"type TTT\", etc lines in tags; `contents` is unstructured \"free\n> +text\" part of raw object data. For blob and tree objects, their raw data don't\n> +have `header` and `contents` parts.\n> +\n> +raw:size::\n> +\tThe raw data size of the object.\n> +\n> +Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tcl`,\n\n\"should not combine\" -> \"cannot be used\" would make it read more\nnaturally (ditto for the phrase used in the proposed log message).\n\n> +`--perl` because if our binary raw data is passed to a variable in the host language,\n> +the host languages may cause escape errors.\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>  \n> diff --git a/ref-filter.c b/ref-filter.c\n> index 97116e12d7c4..c2abf5da7006 100644\n> --- a/ref-filter.c\n> +++ b/ref-filter.c\n> @@ -138,6 +138,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> @@ -370,6 +373,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> +\t} else 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> @@ -530,6 +545,7 @@ static struct {\n>  \t{ \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n>  \t{ \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n>  \t{ \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n> +\t{ \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n>  \t{ \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n>  \t{ \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n>  \t{ \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n> @@ -564,12 +580,15 @@ struct ref_formatting_state {\n>  \n>  struct atom_value {\n>  \tconst char *s;\n> +\tsize_t s_size;\n\nOK, so everything used to be a C-string that cannot hold NULs in it,\nbut now it is a counted <ptr, len> string.  Good.\n\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> @@ -588,6 +607,10 @@ 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> +\tif (format->quote_style && starts_with(sp, \"raw\"))\n> +\t\treturn strbuf_addf_ret(err, -1, _(\"--format=%.*s should not combine with\"\n> +\t\t\t\t\"--python, --shell, --tcl, --perl\"), (int)(ep-atom), atom);\n\nDon't we want to allow \"raw:size\" that would be a plain text?\nI am not sure if this check belongs here in the first place.\nShouldn't it be done in raw_atom_parser() instead?\n\nAnother idea is to teach a more generic rule to quote_formatting()\nto detect NULs in v->s[0..v->s_size] at runtime and barf, i.e. a\nplain-text blob object can be used with \"--shell --format=%(raw)\"\njust fine.\n\n> @@ -652,11 +675,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\nIt probably is a good idea to invent a C preprocessor macro for a\nnamed constant to be used when a structure is initialized, but it\nwould be easier to read if the rule is \"len field is negative when\nthe value is a C-string\", e.g.\n\n\t\tif (len < 0)\n\n> @@ -683,9 +709,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> @@ -785,14 +814,16 @@ 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> +\tconst char *s = buf->buf;\n> +\tsize_t cur_len = 0;\n> +\n> +\twhile ((cur_len != buf->len) && (isspace(*s) || *s == '\\0')) {\n>  \t\ts++;\n> +\t\tcur_len++;\n>  \t}\n> -\treturn 1;\n> +\treturn cur_len == buf->len;\n>  }\n\nAssuming that we do want to treat NULs the same way as whitespaces,\nthe updated code works as intended, which is good.  But I have no\nreason to support that design decision.  I do not have a strong\nreason to support a design decision that goes the opposite way to\ntreat a NUL just like we treat an 'X', but at least I can understand\nit (i.e. \"because we have no reason to special case NUL any more\nthan 'X' when trying to see if a buffer is 'empty', we don't\").\nThis code on the other hand must be supported with \"because we need\nto special case NUL for such and such reasons for the purpose of\ndetermining if a buffer is 'empty', we treat them the same way as\nwhitespaces\".\n\n> @@ -800,6 +831,7 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta\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> @@ -810,18 +842,28 @@ 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 (!if_then_else->str)\n> +\t\t\tBUG(\"when if_then_else->cmp_status == COMPARE_EQUAL,\"\n> +\t\t\t    \"if_then_else->str must not be null\");\n\nCan the change in this commit violate the invariant that\nif_then_else->str cannot be NULL, which seems to have been the case\nforever as we see an unchecked strcmp() done in the original?\n\nIf so, perhaps you can check the condition upfront, where you\ncompute str_len above, e.g.\n\n\tif (!if_then_else->str) {\n\t\tif (if_then_else->cmp_status == COMPARE_EQUAL ||\n                    if_then_else->cmp_status == COMPARE_UNEQUAL)\n\t\t\tBUG(...);\n\t} else\n\t\tstr_len = strlen(...);\n\nIf not, then I do not see the point of adding this (and later) check\nwith BUG to this code.\n\nOr is the invariant that .str must not be NULL could have been\nviolated without this patch (i.e. the original was buggy in running\nstrcmp() on .str without checking)?  If so, please make it a separate\npreliminary change to add such an assert.\n\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 (!if_then_else->str)\n> +\t\t\tBUG(\"when if_then_else->cmp_status == COMPARE_UNEQUAL,\"\n> +\t\t\t    \"if_then_else->str must not be null\");\n\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_raw_data(struct atom_value *val, int deref, void *buf, unsigned long buf_size, struct object *obj)\n>  {\n>  \tint i;\n>  \tconst char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n> @@ -1307,10 +1349,22 @@ 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 (starts_with(name, \"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\tcontinue;\n> +\t\t}\n\nI can understand that \"raw[:<options>]\" handling has been inserted\nabove the existing \"from here on, we only deal with log message\ncomponents\" check.  But\n\n> +\t\tif ((obj->type != OBJ_TAG &&\n> +\t\t     obj->type != OBJ_COMMIT) ||\n\nI do not see why these new conditions are added.  The change is not\njustified in the proposed log message, the original did not need\nthese conditions, and this does not concern the primary point of the\nchange, which is to start supporting %(raw[:<option>]) placeholder.\n\nIf it is needed as a bugfix (e.g. it may be that you consider \"if a\nblob has contents that looks very similar to 'git cat-file commit\nHEAD', %(body) and friends parse these out, even though it is not a\ncommit\" is a bug and the change to add these extra tests is meant as\na fix), that should be done as a preliminary change before adding\nthe support for a new atom.\n\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> @@ -1374,25 +1428,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);\n> +\t\tgrab_raw_data(val, deref, buf, buf_size, obj);\n\nIt is very strange that a helper that is named to grab raw data can\nstill process pieces out of a structured data.  The original name is\nstill a far better match to what the function does, even after this\npatch teaches it to also honor %(raw) placeholder.  It is still\nabout grabbing various \"sub\"-pieces out of \"body contents\", and the\nsub-piece the %(raw) grabs just happens to be \"the whole thing\".\n\n> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> +{\n> +\tsize_t i;\n> +\tconst char *s1 = (const char *)vs1;\n> +\tconst char *s2 = (const char *)vs2;\n> +\n> +\tfor (i = 0; i < n; i++) {\n> +\t\tunsigned char u1 = s1[i];\n> +\t\tunsigned char u2 = s2[i];\n> +\t\tint U1 = toupper (u1);\n> +\t\tint U2 = toupper (u2);\n\nDoes toupper('\\0') even have a defined meaning?\n\n> +\t\tint diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> +\t\t\t: U1 < U2 ? -1 : U2 < U1);\n\nLooks crazy to worry about uchar wider than int.  Such a system is\nnot even standard compliant, is it?\n\n> +\t\tif (diff)\n> +\t\t\treturn diff;\n> +\t}\n> +\treturn 0;\n> +}\n\n\n> @@ -2304,6 +2382,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n>  \tint cmp_detached_head = 0;\n>  \tcmp_type cmp_type = used_atom[s->atom].type;\n>  \tstruct strbuf err = STRBUF_INIT;\n> +\tsize_t slen = 0;\n>  \n>  \tif (get_ref_atom_value(a, s->atom, &va, &err))\n>  \t\tdie(\"%s\", err.buf);\n> @@ -2317,10 +2396,32 @@ 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\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\nWhy not introduce two local temporary variables a_size and b_size\nand initialize them upfront like so:\n\n\t\ta_size = va->s_size < 0 ? strlen(va->s) : va->s_size;\n\t\tb_size = vb->s_size < 0 ? strlen(vb->s) : vb->s_size;\n\nWouldn't that allow you to do without the complex \"if both are\ncounted, do this, if A is counted but B is not, do that, ...\"\ncascade?\n\nI can sort-of see the point of special casing \"both are traditional\nC strings\" case (i.e. the \"if\" side of the \"else\" we are discussing\nhere) and using strcasecmp/strcmp instead of memcasecmp/memcmp, but\nI do not see much point in having the if/elseif/else cascade inside\nthis \"else\" clause.\n\n> +\t\t\tif (va->s_size != ATOM_VALUE_S_SIZE_INIT &&\n> +\t\t\t    vb->s_size != ATOM_VALUE_S_SIZE_INIT) {\n> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, va->s_size > vb->s_size ?\n> +\t\t\t\t       vb->s_size : va->s_size);\n> +\t\t\t} else if (va->s_size == ATOM_VALUE_S_SIZE_INIT) {\n> +\t\t\t\tslen = strlen(va->s);\n> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > vb->s_size ?\n> +\t\t\t\t\t     vb->s_size : slen);\n> +\t\t\t} else {\n> +\t\t\t\tslen = strlen(vb->s);\n> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > va->s_size ?\n> +\t\t\t\t\t     slen : va->s_size);\n> +\t\t\t}\n> +\t\t\tcmp = cmp ? cmp : va->s_size - vb->s_size;\n> +\t\t}\n>  \t} else {\n>  \t\tif (va->value < vb->value)\n>  \t\t\tcmp = -1;\n\nThanks.\n"},{"id":"425728","messageId":"xmqqwnrj7e1x.fsf@gitster.g","threadId":"55782","inReplyTo":"aa6d73f3e526f416ee1e4e332e9ca3119efba0e8.1622126603.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(header) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-28T03:06:02Z","receivedAt":"2021-05-28T03:06:12Z","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> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Add new formatting option `%(header)`, which will print the\n> the structured header part of the raw object data.\n>\n> In the storage layout of an object: blob and tree only\n> contains raw data; commit and tag raw data contains two part:\n> header and contents. The header of tag contains \"object OOO\",\n> \"type TTT\", \"tag AAA\", \"tagger GGG\"; The header of commit\n> contains \"tree RRR\", \"parent PPP\", \"author UUU\", \"committer CCC\".\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  Documentation/git-for-each-ref.txt |  7 +++++\n>  ref-filter.c                       | 26 +++++++++++++++++\n>  t/t6300-for-each-ref.sh            | 45 ++++++++++++++++++++++++++++++\n>  3 files changed, 78 insertions(+)\n\nWhile having this may not be wrong, I am not sure who needs it.  Is\nyour \"cat-file --batch\" topic needs this new atom?\n"},{"id":"425734","messageId":"xmqqpmxb79v8.fsf@gitster.g","threadId":"55782","inReplyTo":"aa6d73f3e526f416ee1e4e332e9ca3119efba0e8.1622126603.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(header) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-28T04:36:27Z","receivedAt":"2021-05-28T04:36: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>  \t\tstruct {\n>  \t\t\tenum { RAW_BARE, RAW_LENGTH } option;\n>  \t\t} raw_data;\n> +\t\tstruct {\n> +\t\t\tenum { H_BARE, H_LENGTH } option;\n> +\t\t} header;\n\nRaw does not use R_{BARE,LENGTH} and uses raw_data member.  Header\nshould follow suit unless there is a compelling reason not to, no?\n\n\t\tstruct {\n\t\t\tenum { HEADER_BARE, HEADER_LENGTH } option;\n\t\t} header_data;\n\nperhaps?\n\n> @@ -1372,6 +1389,15 @@ static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned\n>  \t\t\t\t    &bodypos, &bodylen, &nonsiglen,\n>  \t\t\t\t    &sigpos, &siglen);\n>  \n> +\t\tif (starts_with(name, \"header\")) {\n> +\t\t\tsize_t header_len = subpos - (const char *)buf - 1;\n\nHmph, is this correct?  I would expect that the \"header\" part of a\ncommit or a tag object excludes the blank line after the header\nfields.  In other words, the \"header\" would be separated by a blank\nline from the \"body\", and that separating blank line is not part of\n\"header\" or \"body\".\n\nOtherwise, if there is a user of %(header), it needs to be coded to\nignore the last blank line but has to diagnose it as an error if\nthere is a blank line before that.\n\n> +\t\t\tif (atom->u.header.option == H_BARE) {\n> +\t\t\t\tv->s = xmemdupz(buf, header_len);\n> +\t\t\t} else if (atom->u.header.option == H_LENGTH)\n> +\t\t\t\tv->s = xstrfmt(\"%\"PRIuMAX, (uintmax_t)header_len);\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n>  \t\tif (atom->u.contents.option == C_SUB)\n>  \t\t\tv->s = copy_subject(subpos, sublen);\n>  \t\telse if (atom->u.contents.option == C_SUB_SANITIZE) {\n"},{"id":"425800","messageId":"CAOLTT8RbVsZuAwUZ-yn_Wwnvq7qyziA=4z=skMoGdPCTdQ5KPw@mail.gmail.com","threadId":"55782","inReplyTo":"60afca827a28f_265302085b@natae.notmuch","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-28T13:02:23Z","receivedAt":"2021-05-28T13:02:39Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Felipe Contreras <felipe.contreras@gmail.com> 于2021年5月28日周五 上午12:36写道：\n>\n> ZheNing Hu via GitGitGadget wrote:\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>\n> No need for braces.\n>\n>   if (!arg)\n>     ...\n>   else\n>\n\nI sometimes forget this detail, I will pay attention.\n\n> > @@ -1307,10 +1349,22 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n> >                       continue;\n> >               if (deref)\n> >                       name++;\n> > -             if (strcmp(name, \"body\") &&\n> > -                 !starts_with(name, \"subject\") &&\n> > -                 !starts_with(name, \"trailers\") &&\n> > -                 !starts_with(name, \"contents\"))\n> > +\n> > +             if (starts_with(name, \"raw\")) {\n> > +                     if (atom->u.raw_data.option == RAW_BARE) {\n> > +                             v->s = xmemdupz(buf, buf_size);\n> > +                             v->s_size = buf_size;\n> > +                     } else if (atom->u.raw_data.option == RAW_LENGTH)\n> > +                             v->s = xstrfmt(\"%\"PRIuMAX, (uintmax_t)buf_size);\n>\n> I think it's better to be consistent: if you used braces in the if, uses\n> braces in else.\n>\n\nOK.\n\n> > +                     continue;\n> > +             }\n>\n> > +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n>\n> Why void *? We can delcare as char *.\n>\n> > +{\n> > +     size_t i;\n> > +     const char *s1 = (const char *)vs1;\n> > +     const char *s2 = (const char *)vs2;\n>\n> Then we avoid this extra step.\n>\n> > +     for (i = 0; i < n; i++) {\n> > +             unsigned char u1 = s1[i];\n> > +             unsigned char u2 = s2[i];\n>\n> There's no need for two entirely new variables...\n>\n> > +             int U1 = toupper (u1);\n> > +             int U2 = toupper (u2);\n>\n> You can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,\n> not `foo (x)`).\n>\n> While we are at it, why keep an extra index from s1, when s1 is never\n> used again?\n>\n> We can simply advance both s1 and s2:\n>\n>   s1++, s2++\n>\n> > +             int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> > +                     : U1 < U2 ? -1 : U2 < U1);\n>\n> I don't understand what this is supposed to achieve. Both U1 and U2 are\n> integers, pretty low integers actually.\n>\n> If we get rid if that complexity we don't even need U1 or U2, just do:\n>\n>   diff = toupper(u1) - toupper(u2);\n>\n> > +             if (diff)\n> > +                     return diff;\n> > +     }\n> > +     return 0;\n> > +}\n>\n> All we have to do is define the end point, and then we don't need i:\n>\n>         static int memcasecmp(const char *s1, const char *s2, size_t n)\n>         {\n>                 const char *end = s1 + n;\n>                 for (; s1 < end; s1++, s2++) {\n>                         int diff = tolower(*s1) - tolower(*s2);\n>                         if (diff)\n>                                 return diff;\n>                 }\n>                 return 0;\n>         }\n>\n> (and I personally prefer lower to upper)\n>\n\nSorry for the weird, unclean `memcasecmp()`, I referred to memcmp()\nin glibc before, and then I was afraid that my writing was not standard\nenough like \"UCHAR_MAX <= INT_MAX\", I can't consider such an\nextreme situation. So I copied it directly from gnulib:\nhttps://github.com/gagern/gnulib/blob/master/lib/memcasecmp.c\n\n> Check the following resource for a detailed explanation of why my\n> modified version is considered good taste:\n>\n> https://github.com/felipec/linked-list-good-taste\n>\n\nOK. I will gradually standardize my code style.\n\n> >  static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)\n> >  {\n> >       struct atom_value *va, *vb;\n> > @@ -2304,6 +2382,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n> >       int cmp_detached_head = 0;\n> >       cmp_type cmp_type = used_atom[s->atom].type;\n> >       struct strbuf err = STRBUF_INIT;\n> > +     size_t slen = 0;\n> >\n> >       if (get_ref_atom_value(a, s->atom, &va, &err))\n> >               die(\"%s\", err.buf);\n> > @@ -2317,10 +2396,32 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n> >       } else if (s->sort_flags & REF_SORTING_VERSION) {\n> >               cmp = versioncmp(va->s, vb->s);\n> >       } else if (cmp_type == FIELD_STR) {\n> > -             int (*cmp_fn)(const char *, const char *);\n> > -             cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> > -                     ? strcasecmp : strcmp;\n> > -             cmp = cmp_fn(va->s, vb->s);\n> > +             if (va->s_size == ATOM_VALUE_S_SIZE_INIT &&\n> > +                 vb->s_size == ATOM_VALUE_S_SIZE_INIT) {\n> > +                     int (*cmp_fn)(const char *, const char *);\n> > +                     cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> > +                             ? strcasecmp : strcmp;\n> > +                     cmp = cmp_fn(va->s, vb->s);\n> > +             } else {\n> > +                     int (*cmp_fn)(const void *, const void *, size_t);\n> > +                     cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> > +                             ? memcasecmp : memcmp;\n> > +\n> > +                     if (va->s_size != ATOM_VALUE_S_SIZE_INIT &&\n> > +                         vb->s_size != ATOM_VALUE_S_SIZE_INIT) {\n> > +                             cmp = cmp_fn(va->s, vb->s, va->s_size > vb->s_size ?\n> > +                                    vb->s_size : va->s_size);\n> > +                     } else if (va->s_size == ATOM_VALUE_S_SIZE_INIT) {\n> > +                             slen = strlen(va->s);\n> > +                             cmp = cmp_fn(va->s, vb->s, slen > vb->s_size ?\n> > +                                          vb->s_size : slen);\n> > +                     } else {\n> > +                             slen = strlen(vb->s);\n> > +                             cmp = cmp_fn(va->s, vb->s, slen > va->s_size ?\n> > +                                          slen : va->s_size);\n> > +                     }\n> > +                     cmp = cmp ? cmp : va->s_size - vb->s_size;\n> > +             }\n>\n> This hurts my eyes. I think the complexity of this chunk warrants a\n> separate function. Then the logic would be easer to see.\n>\n\nFine. This piece of the situation is a bit complicated...\n\n> Cheers.\n>\n> --\n> Felipe Contreras\n\nThanks.\n--\nZheNing Hu\n"},{"id":"425805","messageId":"CAOLTT8SLLgZnF0SV2FPPBJkB=ybeh8mamTPqc-M6CXQeepooOQ@mail.gmail.com","threadId":"55782","inReplyTo":"xmqq1r9r8spu.fsf@gitster.g","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-28T15:04:59Z","receivedAt":"2021-05-28T15:05:23Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年5月28日周五 上午11:04写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\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> > a structured string (end with '\\0').\n>\n> Text, yes, string is also OK.  But structured?  Probably not.\n>\n>     ... being text (specifically, no embedded NULs in it).\n>\n\nOK.\n\n> > Beyond, `--format=%(raw)` should not combine with `--python`, `--shell`,\n> > `--tcl`, `--perl` because if our binary raw data is passed to a variable\n> > in the host language, the host languages may cause escape errors.\n>\n> OK.  I think at least --perl and possibly --python should be able to\n> express NULs in the \"string\" type we use from the host language, but\n> I am perfectly fine with the decision to leave it to later updates.\n>\n\nYes, for the time being, unified processing --<lang> will be easier.\n\n> > +Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tcl`,\n>\n> \"should not combine\" -> \"cannot be used\" would make it read more\n> naturally (ditto for the phrase used in the proposed log message).\n>\n\nOK, so the wording is better.\n\n> >\n> >  struct atom_value {\n> >       const char *s;\n> > +     size_t s_size;\n>\n> OK, so everything used to be a C-string that cannot hold NULs in it,\n> but now it is a counted <ptr, len> string.  Good.\n>\n\nThis suddenly reminded me of strbuf...\nI don't know if it is worth replacing all s, s_size with strbuf.\n\n> > @@ -588,6 +607,10 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n> >               return strbuf_addf_ret(err, -1, _(\"malformed field name: %.*s\"),\n> >                                      (int)(ep-atom), atom);\n> >\n> > +     if (format->quote_style && starts_with(sp, \"raw\"))\n> > +             return strbuf_addf_ret(err, -1, _(\"--format=%.*s should not combine with\"\n> > +                             \"--python, --shell, --tcl, --perl\"), (int)(ep-atom), atom);\n>\n> Don't we want to allow \"raw:size\" that would be a plain text?\n> I am not sure if this check belongs here in the first place.\n> Shouldn't it be done in raw_atom_parser() instead?\n>\n\nYou are right: \"raw:size\" should be keep, but I can't this check to\nraw_atom_parser(), becase \"if I move it to raw_atom_parser(), when we\nuse:\n\n`git ref-filter --format=%raw --sort=raw --python`\n\nGit will continue to run instead of die because parse_sorting_atom() will\nuse a dummy ref_format and don't remember --language details, next time\nformat_ref_array_item() will reuse the used_atom entry of sorting atom in\nparse_ref_filter_atom(), this will skip the check in raw_atom_parser().\"\n\n> Another idea is to teach a more generic rule to quote_formatting()\n> to detect NULs in v->s[0..v->s_size] at runtime and barf, i.e. a\n> plain-text blob object can be used with \"--shell --format=%(raw)\"\n> just fine.\n>\n\nThe cost of such a check is not small. Maybe can add an option\nsuch as \"--only-text\" to do it.\n\n> > @@ -652,11 +675,14 @@ static int parse_ref_filter_atom(const struct ref_format *format,\n> >       return 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> >       switch (quote_style) {\n> >       case QUOTE_NONE:\n> > -             strbuf_addstr(s, str);\n> > +             if (len != ATOM_VALUE_S_SIZE_INIT)\n> > +                     strbuf_add(s, str, len);\n> > +             else\n> > +                     strbuf_addstr(s, str);\n>\n> It probably is a good idea to invent a C preprocessor macro for a\n> named constant to be used when a structure is initialized, but it\n> would be easier to read if the rule is \"len field is negative when\n> the value is a C-string\", e.g.\n>\n>                 if (len < 0)\n>\n\nI do not recognize such an approach because we are deal\nwith \"size_t s_size\", if (len < 0) will never be established.\nI use -1 is because it's equal to 18446744073709551615\nand it's impossible to have such a large file in Git.\n\n> > @@ -785,14 +814,16 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state\n> >       return 0;\n> >  }\n> >\n> > -static int is_empty(const char *s)\n> > +static int is_empty(struct strbuf *buf)\n> >  {\n> > -     while (*s != '\\0') {\n> > -             if (!isspace(*s))\n> > -                     return 0;\n> > +     const char *s = buf->buf;\n> > +     size_t cur_len = 0;\n> > +\n> > +     while ((cur_len != buf->len) && (isspace(*s) || *s == '\\0')) {\n> >               s++;\n> > +             cur_len++;\n> >       }\n> > -     return 1;\n> > +     return cur_len == buf->len;\n> >  }\n>\n> Assuming that we do want to treat NULs the same way as whitespaces,\n> the updated code works as intended, which is good.  But I have no\n> reason to support that design decision.  I do not have a strong\n> reason to support a design decision that goes the opposite way to\n> treat a NUL just like we treat an 'X', but at least I can understand\n> it (i.e. \"because we have no reason to special case NUL any more\n> than 'X' when trying to see if a buffer is 'empty', we don't\").\n> This code on the other hand must be supported with \"because we need\n> to special case NUL for such and such reasons for the purpose of\n> determining if a buffer is 'empty', we treat them the same way as\n> whitespaces\".\n>\n\nSomething like \"\\0abc\", from the perspective of the string, it is empty;\nfrom the perspective of the memory, it is not empty; I don't know any\nabsolutely good solutions here.\n\n> Can the change in this commit violate the invariant that\n> if_then_else->str cannot be NULL, which seems to have been the case\n> forever as we see an unchecked strcmp() done in the original?\n>\n> If so, perhaps you can check the condition upfront, where you\n> compute str_len above, e.g.\n>\n>         if (!if_then_else->str) {\n>                 if (if_then_else->cmp_status == COMPARE_EQUAL ||\n>                     if_then_else->cmp_status == COMPARE_UNEQUAL)\n>                         BUG(...);\n>         } else\n>                 str_len = strlen(...);\n>\n> If not, then I do not see the point of adding this (and later) check\n> with BUG to this code.\n>\n> Or is the invariant that .str must not be NULL could have been\n> violated without this patch (i.e. the original was buggy in running\n> strcmp() on .str without checking)?  If so, please make it a separate\n> preliminary change to add such an assert.\n>\n\nThe BUG() here actually acts as an \"assert()\". \".str must not be NULL\" is\nright, it point to \"xxx\" in \"%(if:equals=xxx)\", so it seems that these BUG()\nare somewhat redundant, I will remove them.\n\n> >  /* See grab_values */\n> > -static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n> > +static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned long buf_size, struct object *obj)\n> >  {\n> >       int i;\n> >       const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;\n> > @@ -1307,10 +1349,22 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n> >                       continue;\n> >               if (deref)\n> >                       name++;\n> > -             if (strcmp(name, \"body\") &&\n> > -                 !starts_with(name, \"subject\") &&\n> > -                 !starts_with(name, \"trailers\") &&\n> > -                 !starts_with(name, \"contents\"))\n> > +\n> > +             if (starts_with(name, \"raw\")) {\n> > +                     if (atom->u.raw_data.option == RAW_BARE) {\n> > +                             v->s = xmemdupz(buf, buf_size);\n> > +                             v->s_size = buf_size;\n> > +                     } else if (atom->u.raw_data.option == RAW_LENGTH)\n> > +                             v->s = xstrfmt(\"%\"PRIuMAX, (uintmax_t)buf_size);\n> > +                     continue;\n> > +             }\n>\n> I can understand that \"raw[:<options>]\" handling has been inserted\n> above the existing \"from here on, we only deal with log message\n> components\" check.  But\n>\n> > +             if ((obj->type != OBJ_TAG &&\n> > +                  obj->type != OBJ_COMMIT) ||\n>\n> I do not see why these new conditions are added.  The change is not\n> justified in the proposed log message, the original did not need\n> these conditions, and this does not concern the primary point of the\n> change, which is to start supporting %(raw[:<option>]) placeholder.\n>\n> If it is needed as a bugfix (e.g. it may be that you consider \"if a\n> blob has contents that looks very similar to 'git cat-file commit\n> HEAD', %(body) and friends parse these out, even though it is not a\n> commit\" is a bug and the change to add these extra tests is meant as\n> a fix), that should be done as a preliminary change before adding\n> the support for a new atom.\n>\n\nAlmost what I means: Make a strong guarantee that blob and tree\nwill never pass the check so that we can don't worry about incorrect\nparsing in find_subpos(). The reason I put it in this patch is that only\ncommit and tag objects will execute `grab_sub_body_contents()` before,\nbut in the current patch it has changed.\n\n> > +                 (strcmp(name, \"body\") &&\n> > +                  !starts_with(name, \"subject\") &&\n> > +                  !starts_with(name, \"trailers\") &&\n> > +                  !starts_with(name, \"contents\")))\n> >                       continue;\n> >               if (!subpos)\n> >                       find_subpos(buf,\n> > @@ -1374,25 +1428,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> > +     void *buf = data->content;\n> > +     unsigned long buf_size = data->size;\n> > +\n> >       switch (obj->type) {\n> >       case OBJ_TAG:\n> >               grab_tag_values(val, deref, obj);\n> > -             grab_sub_body_contents(val, deref, buf);\n> > +             grab_raw_data(val, deref, buf, buf_size, obj);\n>\n> It is very strange that a helper that is named to grab raw data can\n> still process pieces out of a structured data.  The original name is\n> still a far better match to what the function does, even after this\n> patch teaches it to also honor %(raw) placeholder.  It is still\n> about grabbing various \"sub\"-pieces out of \"body contents\", and the\n> sub-piece the %(raw) grabs just happens to be \"the whole thing\".\n>\n\nWell, I can't think of a better name, My original idea was grab_raw_data()\ncan grab itself, header, contents, It is more general than\ngrab_sub_body_contents(),\nraw data is not a part of \"subject\" or \"body\" of \"contents\"...\n\n> > +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> > +{\n> > +     size_t i;\n> > +     const char *s1 = (const char *)vs1;\n> > +     const char *s2 = (const char *)vs2;\n> > +\n> > +     for (i = 0; i < n; i++) {\n> > +             unsigned char u1 = s1[i];\n> > +             unsigned char u2 = s2[i];\n> > +             int U1 = toupper (u1);\n> > +             int U2 = toupper (u2);\n>\n> Does toupper('\\0') even have a defined meaning?\n>\n> > +             int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> > +                     : U1 < U2 ? -1 : U2 < U1);\n>\n> Looks crazy to worry about uchar wider than int.  Such a system is\n> not even standard compliant, is it?\n>\n\nForget about this inelegant help function. As I said in my reply to Felipe,\nthis is copied from gunlib...\n\n> > @@ -2304,6 +2382,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n> >       int cmp_detached_head = 0;\n> >       cmp_type cmp_type = used_atom[s->atom].type;\n> >       struct strbuf err = STRBUF_INIT;\n> > +     size_t slen = 0;\n> >\n> >       if (get_ref_atom_value(a, s->atom, &va, &err))\n> >               die(\"%s\", err.buf);\n> > @@ -2317,10 +2396,32 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n> >       } else if (s->sort_flags & REF_SORTING_VERSION) {\n> >               cmp = versioncmp(va->s, vb->s);\n> >       } else if (cmp_type == FIELD_STR) {\n> > -             int (*cmp_fn)(const char *, const char *);\n> > -             cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> > -                     ? strcasecmp : strcmp;\n> > -             cmp = cmp_fn(va->s, vb->s);\n>\n> > +             if (va->s_size == ATOM_VALUE_S_SIZE_INIT &&\n> > +                 vb->s_size == ATOM_VALUE_S_SIZE_INIT) {\n> > +                     int (*cmp_fn)(const char *, const char *);\n> > +                     cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> > +                             ? strcasecmp : strcmp;\n> > +                     cmp = cmp_fn(va->s, vb->s);\n> > +             } else {\n> > +                     int (*cmp_fn)(const void *, const void *, size_t);\n> > +                     cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> > +                             ? memcasecmp : memcmp;\n>\n> Why not introduce two local temporary variables a_size and b_size\n> and initialize them upfront like so:\n>\n>                 a_size = va->s_size < 0 ? strlen(va->s) : va->s_size;\n>                 b_size = vb->s_size < 0 ? strlen(vb->s) : vb->s_size;\n>\n> Wouldn't that allow you to do without the complex \"if both are\n> counted, do this, if A is counted but B is not, do that, ...\"\n> cascade?\n>\n\nSorry, such code would be really ugly for reading.\n\n> I can sort-of see the point of special casing \"both are traditional\n> C strings\" case (i.e. the \"if\" side of the \"else\" we are discussing\n> here) and using strcasecmp/strcmp instead of memcasecmp/memcmp, but\n> I do not see much point in having the if/elseif/else cascade inside\n> this \"else\" clause.\n>\n\nI will try to modify its logic.\n\n> Thanks.\n\nYour reply is very accurate.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"425807","messageId":"CAOLTT8SDHK-e6ReErs80bFh+2nQA_v5Z095XeNiXe4dDQEd3-w@mail.gmail.com","threadId":"55782","inReplyTo":"xmqqpmxb79v8.fsf@gitster.g","subject":"Re: [PATCH 2/2] [GSOC] ref-filter: add %(header) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-28T15:19:29Z","receivedAt":"2021-05-28T15:19:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年5月28日周五 下午12:36写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> >               struct {\n> >                       enum { RAW_BARE, RAW_LENGTH } option;\n> >               } raw_data;\n> > +             struct {\n> > +                     enum { H_BARE, H_LENGTH } option;\n> > +             } header;\n>\n> Raw does not use R_{BARE,LENGTH} and uses raw_data member.  Header\n> should follow suit unless there is a compelling reason not to, no?\n>\n>                 struct {\n>                         enum { HEADER_BARE, HEADER_LENGTH } option;\n>                 } header_data;\n>\n> perhaps?\n>\n\nOK.\n\n\n> > @@ -1372,6 +1389,15 @@ static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned\n> >                                   &bodypos, &bodylen, &nonsiglen,\n> >                                   &sigpos, &siglen);\n> >\n> > +             if (starts_with(name, \"header\")) {\n> > +                     size_t header_len = subpos - (const char *)buf - 1;\n>\n> Hmph, is this correct?  I would expect that the \"header\" part of a\n> commit or a tag object excludes the blank line after the header\n> fields.  In other words, the \"header\" would be separated by a blank\n> line from the \"body\", and that separating blank line is not part of\n> \"header\" or \"body\".\n>\n> Otherwise, if there is a user of %(header), it needs to be coded to\n> ignore the last blank line but has to diagnose it as an error if\n> there is a blank line before that.\n>\n\nI am a bit confused, Is there any problem with me doing this?\n\n> > +                     size_t header_len = subpos - (const char *)buf - 1;\n\n\"header\" part starts from \"buf\" and header_len have minus 1 so that\nheader part will not touch the blank line. At the same time, \"contents\"\npart starts from subpos, and it also does not touch the blank line.\n\n> While having this may not be wrong, I am not sure who needs it.  Is\n> your \"cat-file --batch\" topic needs this new atom?\n\nOk, I will remove it from this topic temporarily.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"425813","messageId":"60b11a8e323ce_4b032208ad@natae.notmuch","threadId":"55782","inReplyTo":"CAOLTT8RbVsZuAwUZ-yn_Wwnvq7qyziA=4z=skMoGdPCTdQ5KPw@mail.gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-28T16:30:06Z","receivedAt":"2021-05-28T16:30:14Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"ZheNing Hu wrote:\n> Sorry for the weird, unclean `memcasecmp()`, I referred to memcmp()\n> in glibc before, and then I was afraid that my writing was not standard\n> enough like \"UCHAR_MAX <= INT_MAX\", I can't consider such an\n> extreme situation. So I copied it directly from gnulib:\n> https://github.com/gagern/gnulib/blob/master/lib/memcasecmp.c\n\nYeah, I imagined you copied it from somewhere, but when you do that you\nneed to transform the code to the style of the project. I've seen GNU\ncode, and in my opinion it's too verbose and redundant. Not a good\nstyle.\n\nBut more importantly: at the header of that file you can see the license\nis GPLv3, that's incompatible with the license of this project, which is\nGPLv2 only (see the note in COPYING).\n\nYou can't just copy code like that. You need to be careful.\n\nAnd if you do copy code--even if allowed by the license--it's something\nthat should be mentioned in the commit message, preferably with a link\nto the original, that way if there's trouble in the future with that\ncode, we can follow the link and figure out why it was done that way.\n\nAlso, it's just nice to give attribution to the people that wrote the\noriginal code.\n\n> > Check the following resource for a detailed explanation of why my\n> > modified version is considered good taste:\n> >\n> > https://github.com/felipec/linked-list-good-taste\n> \n> OK. I will gradually standardize my code style.\n\nIt is not a standard, it is my personal opinion, which is shared by\nLinus Torvalds, and I presume other members of the Git project.\n\nThe style is not something that can be standardized, you get a feeling\nof it as you read more code of the project, write, and then receive\nfeedback on what you write.\n\nIt's like learning the slang of a new city; it takes a while.\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"425814","messageId":"60b11ca3bfdb1_4b03220842@natae.notmuch","threadId":"55782","inReplyTo":"CAOLTT8SLLgZnF0SV2FPPBJkB=ybeh8mamTPqc-M6CXQeepooOQ@mail.gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-28T16:38:59Z","receivedAt":"2021-05-28T16:39:06Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"ZheNing Hu wrote:\n> Junio C Hamano <gitster@pobox.com> 于2021年5月28日周五 上午11:04写道：\n> > \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> > > +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> > > +{\n> > > +     size_t i;\n> > > +     const char *s1 = (const char *)vs1;\n> > > +     const char *s2 = (const char *)vs2;\n> > > +\n> > > +     for (i = 0; i < n; i++) {\n> > > +             unsigned char u1 = s1[i];\n> > > +             unsigned char u2 = s2[i];\n> > > +             int U1 = toupper (u1);\n> > > +             int U2 = toupper (u2);\n> >\n> > Does toupper('\\0') even have a defined meaning?\n\n> Forget about this inelegant help function. As I said in my reply to Felipe,\n> this is copied from gunlib...\n\nEven if you use my modified version (which hopefully is not so\ninelegant), the comment about toupper('\\0') still applies.\n\nMy reading of `man toupper(3)` is that if c is neither lowercase or\nuppercase it is returned as-is. As long as it's unsigned char, which\n'\\0' is.\n\nSo I think the behavior is indeed defined.\n\n-- \nFelipe Contreras"},{"id":"425879","messageId":"13c63e79-27fd-58d5-9a4c-6b58c40ef4b8@gmail.com","threadId":"55782","inReplyTo":"60afca827a28f_265302085b@natae.notmuch","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-05-29T13:23:05Z","receivedAt":"2021-05-29T13:24:18Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 27/05/2021 17:36, Felipe Contreras wrote:\n> ZheNing Hu via GitGitGadget wrote:\n> [...]\n>> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> \n> Why void *? We can delcare as char *.\n\nIf you look at how this function is used you'll see\n\tint (*cmp_fn)(const void *, const void *, size_t);\n\tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n\t\t\t? memcasecmp : memcmp;\n\nSo the signature must match memcmp to avoid undefined behavior (a \nternary expression is undefined unless both sides evaluate to the same \ntype and calling a function through a pointer a different type is \nundefined as well)\n\n>> +{\n>> +\tsize_t i;\n>> +\tconst char *s1 = (const char *)vs1;\n>> +\tconst char *s2 = (const char *)vs2;\n> \n> Then we avoid this extra step.\n> \n>> +\tfor (i = 0; i < n; i++) {\n>> +\t\tunsigned char u1 = s1[i];\n>> +\t\tunsigned char u2 = s2[i];\n> \n> There's no need for two entirely new variables...\n> \n>> +\t\tint U1 = toupper (u1);\n>> +\t\tint U2 = toupper (u2);\n> \n> You can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,\n> not `foo (x)`).\n> \n> While we are at it, why keep an extra index from s1, when s1 is never\n> used again?\n> \n> We can simply advance both s1 and s2:\n> \n>    s1++, s2++\n> \n>> +\t\tint diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n>> +\t\t\t: U1 < U2 ? -1 : U2 < U1);\n> \n> I don't understand what this is supposed to achieve. Both U1 and U2 are\n> integers, pretty low integers actually.\n> \n> If we get rid if that complexity we don't even need U1 or U2, just do:\n> \n>    diff = toupper(u1) - toupper(u2);\n> \n>> +\t\tif (diff)\n>> +\t\t\treturn diff;\n>> +\t}\n>> +\treturn 0;\n>> +}\n> \n> All we have to do is define the end point, and then we don't need i:\n> \n> \tstatic int memcasecmp(const char *s1, const char *s2, size_t n)\n> \t{\n> \t\tconst char *end = s1 + n;\n> \t\tfor (; s1 < end; s1++, s2++) {\n> \t\t\tint diff = tolower(*s1) - tolower(*s2);\n> \t\t\tif (diff)\n> \t\t\t\treturn diff;\n> \t\t}\n> \t\treturn 0;\n> \t}\n> \n> (and I personally prefer lower to upper)\n\nWe should be using tolower() as that is what POSIX specifies for \nstrcasecmp() [1] which we are trying to emulate and there are cases[2] where\n\t(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))\n\nBest Wishes\n\nPhillip\n\n[1] https://pubs.opengroup.org/onlinepubs/9699919799/\n[2] https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing\n\n> Check the following resource for a detailed explanation of why my\n> modified version is considered good taste:\n> \n> https://github.com/felipec/linked-list-good-taste\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>> @@ -2304,6 +2382,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n>>   \tint cmp_detached_head = 0;\n>>   \tcmp_type cmp_type = used_atom[s->atom].type;\n>>   \tstruct strbuf err = STRBUF_INIT;\n>> +\tsize_t slen = 0;\n>>   \n>>   \tif (get_ref_atom_value(a, s->atom, &va, &err))\n>>   \t\tdie(\"%s\", err.buf);\n>> @@ -2317,10 +2396,32 @@ 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>> +\n>> +\t\t\tif (va->s_size != ATOM_VALUE_S_SIZE_INIT &&\n>> +\t\t\t    vb->s_size != ATOM_VALUE_S_SIZE_INIT) {\n>> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, va->s_size > vb->s_size ?\n>> +\t\t\t\t       vb->s_size : va->s_size);\n>> +\t\t\t} else if (va->s_size == ATOM_VALUE_S_SIZE_INIT) {\n>> +\t\t\t\tslen = strlen(va->s);\n>> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > vb->s_size ?\n>> +\t\t\t\t\t     vb->s_size : slen);\n>> +\t\t\t} else {\n>> +\t\t\t\tslen = strlen(vb->s);\n>> +\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > va->s_size ?\n>> +\t\t\t\t\t     slen : va->s_size);\n>> +\t\t\t}\n>> +\t\t\tcmp = cmp ? cmp : va->s_size - vb->s_size;\n>> +\t\t}\n> \n> This hurts my eyes. I think the complexity of this chunk warrants a\n> separate function. Then the logic would be easer to see.\n> \n> Cheers.\n> \n\n"},{"id":"425884","messageId":"60b25ca7a01c2_265088208af@natae.notmuch","threadId":"55782","inReplyTo":"13c63e79-27fd-58d5-9a4c-6b58c40ef4b8@gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-29T15:24:23Z","receivedAt":"2021-05-29T15:24:28Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Phillip Wood wrote:\n> On 27/05/2021 17:36, Felipe Contreras wrote:\n> > ZheNing Hu via GitGitGadget wrote:\n> > [...]\n> >> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> > \n> > Why void *? We can delcare as char *.\n> \n> If you look at how this function is used you'll see\n> \tint (*cmp_fn)(const void *, const void *, size_t);\n> \tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n> \t\t\t? memcasecmp : memcmp;\n\nYeah, but why?\n\nWe know we are comparing two char *. Presumably the reason is that\nmemcmp and memcasecmp use void *, but that could be remedied with:\n\n\tcmp_fn = (int (*)(const char *, const char *, size_t))memcmp;\n\nThat way the same cmp_fn could be used for the two cases.\n\nEither way I don't care particularly much. It also could be possible to\nuse void * and do the casting in tolower().\n \n> > (and I personally prefer lower to upper)\n> \n> We should be using tolower() as that is what POSIX specifies for \n> strcasecmp() [1] which we are trying to emulate and there are cases[2] where\n> \t(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))\n\nThat's true.\n\n-- \nFelipe Contreras\n"},{"id":"425888","messageId":"e7db3342-7022-98db-e0d2-a880f874565e@gmail.com","threadId":"55782","inReplyTo":"60b25ca7a01c2_265088208af@natae.notmuch","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-05-29T17:23:20Z","receivedAt":"2021-05-29T17:23:25Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 29/05/2021 16:24, Felipe Contreras wrote:\n> Phillip Wood wrote:\n>> On 27/05/2021 17:36, Felipe Contreras wrote:\n>>> ZheNing Hu via GitGitGadget wrote:\n>>> [...]\n>>>> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n>>>\n>>> Why void *? We can delcare as char *.\n>>\n>> If you look at how this function is used you'll see\n>> \tint (*cmp_fn)(const void *, const void *, size_t);\n>> \tcmp_fn = s->sort_flags & REF_SORTING_ICASE\n>> \t\t\t? memcasecmp : memcmp;\n> \n> Yeah, but why?\n> \n> We know we are comparing two char *. Presumably the reason is that\n> memcmp and memcasecmp use void *, but that could be remedied with:\n> \n> \tcmp_fn = (int (*)(const char *, const char *, size_t))memcmp;\n> \n> That way the same cmp_fn could be used for the two cases.\n\nBut that is still undefined behavior - the ugly cast just silences any \ncompiler warning without making the code safe. It calls memcmp using a \npointer of a different type. The type of cmp_fn and the two functions \nassigned to it must match.\n\nBest Wishes\n\nPhillip\n\n> Either way I don't care particularly much. It also could be possible to\n> use void * and do the casting in tolower().\n>   \n>>> (and I personally prefer lower to upper)\n>>\n>> We should be using tolower() as that is what POSIX specifies for\n>> strcasecmp() [1] which we are trying to emulate and there are cases[2] where\n>> \t(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))\n> \n> That's true.\n> \n\n"},{"id":"425900","messageId":"CAOLTT8Txkb59X33YAmx-TmR=c42Y0=fbmuugaTkK1_GDyEdMMQ@mail.gmail.com","threadId":"55782","inReplyTo":"60b11a8e323ce_4b032208ad@natae.notmuch","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-30T05:37:37Z","receivedAt":"2021-05-30T05:37:54Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Felipe Contreras <felipe.contreras@gmail.com> 于2021年5月29日周六 上午12:30写道：\n>\n> ZheNing Hu wrote:\n> > Sorry for the weird, unclean `memcasecmp()`, I referred to memcmp()\n> > in glibc before, and then I was afraid that my writing was not standard\n> > enough like \"UCHAR_MAX <= INT_MAX\", I can't consider such an\n> > extreme situation. So I copied it directly from gnulib:\n> > https://github.com/gagern/gnulib/blob/master/lib/memcasecmp.c\n>\n> Yeah, I imagined you copied it from somewhere, but when you do that you\n> need to transform the code to the style of the project. I've seen GNU\n> code, and in my opinion it's too verbose and redundant. Not a good\n> style.\n>\n> But more importantly: at the header of that file you can see the license\n> is GPLv3, that's incompatible with the license of this project, which is\n> GPLv2 only (see the note in COPYING).\n>\n> You can't just copy code like that. You need to be careful.\n>\n\nNow I notice the importance of license in open source project.\n\n> And if you do copy code--even if allowed by the license--it's something\n> that should be mentioned in the commit message, preferably with a link\n> to the original, that way if there's trouble in the future with that\n> code, we can follow the link and figure out why it was done that way.\n>\n> Also, it's just nice to give attribution to the people that wrote the\n> original code.\n>\n\nOk.\n\n> > > Check the following resource for a detailed explanation of why my\n> > > modified version is considered good taste:\n> > >\n> > > https://github.com/felipec/linked-list-good-taste\n> >\n> > OK. I will gradually standardize my code style.\n>\n> It is not a standard, it is my personal opinion, which is shared by\n> Linus Torvalds, and I presume other members of the Git project.\n>\n> The style is not something that can be standardized, you get a feeling\n> of it as you read more code of the project, write, and then receive\n> feedback on what you write.\n>\n\nYes it is. Reading and writing Git code has brought me a certain degree\nof code style improvement. (this is indeed a kind of edification :) )\n\n> It's like learning the slang of a new city; it takes a while.\n>\n\nThanks for your guidance.\n\n> Cheers.\n>\n> --\n> Felipe Contreras\n\nThanks!\n--\nZheNing Hu\n"},{"id":"425903","messageId":"CAOLTT8QmByyue25YLTa2-=oQ00K5Cs_eoSD3_y6wRYYToa=oxQ@mail.gmail.com","threadId":"55782","inReplyTo":"13c63e79-27fd-58d5-9a4c-6b58c40ef4b8@gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-30T06:26:20Z","receivedAt":"2021-05-30T06:26:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> 于2021年5月29日周六 下午9:23写道：\n>\n> On 27/05/2021 17:36, Felipe Contreras wrote:\n> > ZheNing Hu via GitGitGadget wrote:\n> > [...]\n> >> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> >\n> > Why void *? We can delcare as char *.\n>\n> If you look at how this function is used you'll see\n>         int (*cmp_fn)(const void *, const void *, size_t);\n>         cmp_fn = s->sort_flags & REF_SORTING_ICASE\n>                         ? memcasecmp : memcmp;\n>\n> So the signature must match memcmp to avoid undefined behavior (a\n> ternary expression is undefined unless both sides evaluate to the same\n> type and calling a function through a pointer a different type is\n> undefined as well)\n>\n\nI agree.\n\n> >> +{\n> >> +    size_t i;\n> >> +    const char *s1 = (const char *)vs1;\n> >> +    const char *s2 = (const char *)vs2;\n> >\n> > Then we avoid this extra step.\n> >\n> >> +    for (i = 0; i < n; i++) {\n> >> +            unsigned char u1 = s1[i];\n> >> +            unsigned char u2 = s2[i];\n> >\n> > There's no need for two entirely new variables...\n> >\n> >> +            int U1 = toupper (u1);\n> >> +            int U2 = toupper (u2);\n> >\n> > You can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,\n> > not `foo (x)`).\n> >\n> > While we are at it, why keep an extra index from s1, when s1 is never\n> > used again?\n> >\n> > We can simply advance both s1 and s2:\n> >\n> >    s1++, s2++\n> >\n> >> +            int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> >> +                    : U1 < U2 ? -1 : U2 < U1);\n> >\n> > I don't understand what this is supposed to achieve. Both U1 and U2 are\n> > integers, pretty low integers actually.\n> >\n> > If we get rid if that complexity we don't even need U1 or U2, just do:\n> >\n> >    diff = toupper(u1) - toupper(u2);\n> >\n> >> +            if (diff)\n> >> +                    return diff;\n> >> +    }\n> >> +    return 0;\n> >> +}\n> >\n> > All we have to do is define the end point, and then we don't need i:\n> >\n> >       static int memcasecmp(const char *s1, const char *s2, size_t n)\n> >       {\n> >               const char *end = s1 + n;\n> >               for (; s1 < end; s1++, s2++) {\n> >                       int diff = tolower(*s1) - tolower(*s2);\n> >                       if (diff)\n> >                               return diff;\n> >               }\n> >               return 0;\n> >       }\n> >\n> > (and I personally prefer lower to upper)\n>\n> We should be using tolower() as that is what POSIX specifies for\n> strcasecmp() [1] which we are trying to emulate and there are cases[2] where\n>         (tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))\n>\n\nI don’t know if we overlooked a fact: This static `memcasecmp()`\nis not a POSIX version. `tolower()` or `toupper()` are in git-compat-util.h,\nsane_istest('\\0', GIT_ALPHA) == false . So in `sane_case()`, whatever\n`tolower()`, `toupper()`, they just return '\\0' itself.\n\n> Best Wishes\n>\n> Phillip\n>\n> [1] https://pubs.opengroup.org/onlinepubs/9699919799/\n> [2] https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing\n>\n\nThanks.\n--\nZhenNing Hu\n"},{"id":"425904","messageId":"CAOLTT8TEkrxAgHvkCVXZDQpn0xSQAe_y7uMF89Q4QWwVd7MDcA@mail.gmail.com","threadId":"55782","inReplyTo":"60b25ca7a01c2_265088208af@natae.notmuch","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-30T06:29:28Z","receivedAt":"2021-05-30T06:29:42Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Felipe Contreras <felipe.contreras@gmail.com> 于2021年5月29日周六 下午11:24写道：\n>\n> Phillip Wood wrote:\n> > On 27/05/2021 17:36, Felipe Contreras wrote:\n> > > ZheNing Hu via GitGitGadget wrote:\n> > > [...]\n> > >> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> > >\n> > > Why void *? We can delcare as char *.\n> >\n> > If you look at how this function is used you'll see\n> >       int (*cmp_fn)(const void *, const void *, size_t);\n> >       cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> >                       ? memcasecmp : memcmp;\n>\n> Yeah, but why?\n>\n> We know we are comparing two char *. Presumably the reason is that\n> memcmp and memcasecmp use void *, but that could be remedied with:\n>\n>         cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;\n>\n> That way the same cmp_fn could be used for the two cases.\n>\n> Either way I don't care particularly much. It also could be possible to\n> use void * and do the casting in tolower().\n>\n\nI agree with Phillip's point of view here:\nIt would be better for memcasecmp and memcmp to be consistent.\n\n> > > (and I personally prefer lower to upper)\n> >\n> > We should be using tolower() as that is what POSIX specifies for\n> > strcasecmp() [1] which we are trying to emulate and there are cases[2] where\n> >       (tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))\n>\n> That's true.\n>\n\nHow about something like this:\n\n static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n {\n-       size_t i;\n-       const char *s1 = (const char *)vs1;\n-       const char *s2 = (const char *)vs2;\n-\n-       for (i = 0; i < n; i++) {\n-               unsigned char u1 = s1[i];\n-               unsigned char u2 = s2[i];\n-               int U1 = toupper (u1);\n-               int U2 = toupper (u2);\n-               int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n-                       : U1 < U2 ? -1 : U2 < U1);\n+       const char *s1 = (const void *)vs1;\n+       const char *s2 = (const void *)vs2;\n+       const char *end = s1 + n;\n+\n+       for (; s1 < end; s1++, s2++) {\n+               int diff = tolower(*s1) - tolower(*s2);\n                if (diff)\n                        return diff;\n        }\n}\n\n> --\n> Felipe Contreras\n\nThanks.\n--\nZheNing Hu\n"},{"id":"425908","messageId":"CAOLTT8QAS5AyUE-9hR7v1bfYdAEf=VJiPZukGMJED+F_4Jt_zg@mail.gmail.com","threadId":"55782","inReplyTo":"CAOLTT8SLLgZnF0SV2FPPBJkB=ybeh8mamTPqc-M6CXQeepooOQ@mail.gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-30T08:11:52Z","receivedAt":"2021-05-30T08:12:09Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> 于2021年5月28日周五 下午11:04写道：\n>\n> > Can the change in this commit violate the invariant that\n> > if_then_else->str cannot be NULL, which seems to have been the case\n> > forever as we see an unchecked strcmp() done in the original?\n> >\n> > If so, perhaps you can check the condition upfront, where you\n> > compute str_len above, e.g.\n> >\n> >         if (!if_then_else->str) {\n> >                 if (if_then_else->cmp_status == COMPARE_EQUAL ||\n> >                     if_then_else->cmp_status == COMPARE_UNEQUAL)\n> >                         BUG(...);\n> >         } else\n> >                 str_len = strlen(...);\n> >\n> > If not, then I do not see the point of adding this (and later) check\n> > with BUG to this code.\n> >\n> > Or is the invariant that .str must not be NULL could have been\n> > violated without this patch (i.e. the original was buggy in running\n> > strcmp() on .str without checking)?  If so, please make it a separate\n> > preliminary change to add such an assert.\n> >\n>\n> The BUG() here actually acts as an \"assert()\". \".str must not be NULL\" is\n> right, it point to \"xxx\" in \"%(if:equals=xxx)\", so it seems that these BUG()\n> are somewhat redundant, I will remove them.\n>\n\nCorrect the error: If the atom is \"%(if)\" instread of\n\"%(if:equals=xxx)\", .str will be NULL.\nWithout assert() or BUG() is ok, but clang-tidy will give a warning:\n\"Null pointer\npassed to 1st parameter expecting 'nonnull'\"\n\n--\nZheNing Hu\n"},{"id":"425916","messageId":"e6c26d19a3f3b4791c435867550da538cfe6e287.1622379718.git.gitgitgadget@gmail.com","threadId":"55782","inReplyTo":"pull.963.v2.git.1622379718.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-05-30T13:01:57Z","receivedAt":"2021-05-30T13:02:06Z","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 will use `grab_sub_body_contents()`\nto grab object contents in origin implement. If we want\nto make blob, tree can also use `grab_sub_body_contents()`\nto get objects' raw data, a blob look like commit or tag\nwill be wrongly regarded as commit, tag by `find_subpos()`.\n\nSo we must add a test before `find_subpos()` to reject\nblob, tree objects. This will help us add %(raw) atom\nwhich can grab raw data of four type objects.\n\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 97116e12d7c4..f6a7b5290d54 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1292,7 +1292,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@@ -1307,10 +1308,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@@ -1379,12 +1383,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":"425917","messageId":"pull.963.v2.git.1622379718.gitgitgadget@gmail.com","threadId":"55782","inReplyTo":"pull.963.git.1622126603.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-05-30T13:01:56Z","receivedAt":"2021-05-30T13:02:06Z","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. Use more elegant memcasecmp().\n 2. Allow %(raw:size) used with --<lang>.\n 3. Remove redundant BUG() in then_atom_handler().\n 4. Roll back to origin function name grab_sub_body_contents().\n 5. Split the check of object type in grab_sub_body_contents() into the\n    previous patch.\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 |  14 ++\n ref-filter.c                       | 158 ++++++++++++++++++-----\n t/t6300-for-each-ref.sh            | 200 +++++++++++++++++++++++++++++\n 3 files changed, 338 insertions(+), 34 deletions(-)\n\n\nbase-commit: 5d5b1473453400224ebb126bf3947e0a3276bdf5\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-963%2Fadlternative%2Fref-filter-raw-atom-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-963/adlternative/ref-filter-raw-atom-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/963\n\nRange-diff vs v1:\n\n -:  ------------ > 1:  e6c26d19a3f3 [GSOC] ref-filter: add obj-type check in grab contents\n 1:  b3848f24f2d3 ! 2:  e44a2ed0db59 [GSOC] ref-filter: add %(raw) atom\n     @@ Commit message\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     -    a structured string (end with '\\0').\n     +    text (specifically, no embedded NULs in it).\n      \n          E.g. `quote_formatting()` use `strbuf_addstr()` or `*._quote_buf()`\n          add the data to the buffer. The raw data of a tree object is\n     @@ Commit message\n          can record raw object size, it can help us add raw object data to\n          the buffer or compare two buffers which contain raw object data.\n      \n     -    Beyond, `--format=%(raw)` should not combine with `--python`, `--shell`,\n     +    Beyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,\n          `--tcl`, `--perl` because if our binary raw data is passed to a variable\n          in the host language, the host languages may cause escape errors.\n      \n     +    Helped-by: Felipe Contreras <felipe.contreras@gmail.com>\n     +    Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n     +    Helped-by: Junio C Hamano <gitster@pobox.com>\n          Based-on-patch-by: Olga Telezhnaya <olyatelezhnaya@gmail.com>\n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n     @@ Documentation/git-for-each-ref.txt: and `date` to extract the named component.\n      +raw:size::\n      +\tThe raw data size of the object.\n      +\n     -+Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tcl`,\n     ++Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,\n      +`--perl` because if our binary raw data is passed to a variable in the host language,\n      +the host languages may cause escape errors.\n      +\n     @@ ref-filter.c: static int contents_atom_parser(const struct ref_format *format, s\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     ++\tif (!arg)\n      +\t\tatom->u.raw_data.option = RAW_BARE;\n     -+\t} else if (!strcmp(arg, \"size\"))\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     @@ ref-filter.c: 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     -+\tif (format->quote_style && starts_with(sp, \"raw\"))\n     -+\t\treturn strbuf_addf_ret(err, -1, _(\"--format=%.*s should not combine with\"\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     +@@ 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     ++\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      @@ ref-filter.c: static int parse_ref_filter_atom(const struct ref_format *format,\n       \treturn at;\n       }\n     @@ ref-filter.c: static int then_atom_handler(struct atom_value *atomv, struct ref_\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 (!if_then_else->str)\n     -+\t\t\tBUG(\"when if_then_else->cmp_status == COMPARE_EQUAL,\"\n     -+\t\t\t    \"if_then_else->str must not be null\");\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 (!if_then_else->str)\n     -+\t\t\tBUG(\"when if_then_else->cmp_status == COMPARE_UNEQUAL,\"\n     -+\t\t\t    \"if_then_else->str must not be null\");\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     @@ ref-filter.c: static int end_atom_handler(struct atom_value *atomv, struct ref_f\n       \t}\n       \tstrbuf_release(&s);\n      @@ ref-filter.c: 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_raw_data(struct atom_value *val, int deref, void *buf, unsigned long buf_size, struct object *obj)\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     -@@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)\n     - \t\t\tcontinue;\n     +@@ ref-filter.c: static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\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     + \n      +\t\tif (starts_with(name, \"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} 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     -+\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     + \t\tif ((obj->type != OBJ_TAG &&\n     + \t\t     obj->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     @@ ref-filter.c: static void fill_missing_values(struct atom_value *val)\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_raw_data(val, deref, buf, buf_size, 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);\n     -+\t\tgrab_raw_data(val, deref, buf, buf_size, 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_raw_data(val, deref, buf, buf_size, obj);\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_raw_data(val, deref, buf, buf_size, obj);\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     @@ ref-filter.c: static int compare_detached_head(struct ref_array_item *a, struct\n       \n      +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n      +{\n     -+\tsize_t i;\n     -+\tconst char *s1 = (const char *)vs1;\n     -+\tconst char *s2 = (const char *)vs2;\n     ++\tconst char *s1 = (const void *)vs1;\n     ++\tconst char *s2 = (const void *)vs2;\n     ++\tconst char *end = s1 + n;\n      +\n     -+\tfor (i = 0; i < n; i++) {\n     -+\t\tunsigned char u1 = s1[i];\n     -+\t\tunsigned char u2 = s2[i];\n     -+\t\tint U1 = toupper (u1);\n     -+\t\tint U2 = toupper (u2);\n     -+\t\tint diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n     -+\t\t\t: U1 < U2 ? -1 : U2 < U1);\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     @@ ref-filter.c: static int compare_detached_head(struct ref_array_item *a, struct\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     -@@ ref-filter.c: static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n     - \tint cmp_detached_head = 0;\n     - \tcmp_type cmp_type = used_atom[s->atom].type;\n     - \tstruct strbuf err = STRBUF_INIT;\n     -+\tsize_t slen = 0;\n     - \n     - \tif (get_ref_atom_value(a, s->atom, &va, &err))\n     - \t\tdie(\"%s\", err.buf);\n      @@ ref-filter.c: 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     @@ ref-filter.c: static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array\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\tif (va->s_size != ATOM_VALUE_S_SIZE_INIT &&\n     -+\t\t\t    vb->s_size != ATOM_VALUE_S_SIZE_INIT) {\n     -+\t\t\t\tcmp = cmp_fn(va->s, vb->s, va->s_size > vb->s_size ?\n     -+\t\t\t\t       vb->s_size : va->s_size);\n     -+\t\t\t} else if (va->s_size == ATOM_VALUE_S_SIZE_INIT) {\n     -+\t\t\t\tslen = strlen(va->s);\n     -+\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > vb->s_size ?\n     -+\t\t\t\t\t     vb->s_size : slen);\n     -+\t\t\t} else {\n     -+\t\t\t\tslen = strlen(vb->s);\n     -+\t\t\t\tcmp = cmp_fn(va->s, vb->s, slen > va->s_size ?\n     -+\t\t\t\t\t     slen : va->s_size);\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\tcmp = cmp ? cmp : va->s_size - vb->s_size;\n      +\t\t}\n       \t} else {\n       \t\tif (va->value < vb->value)\n     @@ t/t6300-for-each-ref.sh: test_atom refs/myblobs/first contents:body \"\"\n      +\trefs/myblobs/first not empty\n      +\tEOF\n      +\tgit for-each-ref --format=\"%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)\" \\\n     -+\trefs/myblobs/ >actual &&\n     ++\t\trefs/myblobs/ >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n     @@ t/t6300-for-each-ref.sh: test_atom refs/myblobs/first contents:body \"\"\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 2:  aa6d73f3e526 < -:  ------------ [GSOC] ref-filter: add %(header) atom\n\n-- \ngitgitgadget\n"},{"id":"425918","messageId":"e44a2ed0db596ab0e0f484c25facb7da7214369e.1622379718.git.gitgitgadget@gmail.com","threadId":"55782","inReplyTo":"pull.963.v2.git.1622379718.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-05-30T13:01:58Z","receivedAt":"2021-05-30T13:02:19Z","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 languages may cause escape errors.\n\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 |  14 ++\n ref-filter.c                       | 146 ++++++++++++++++-----\n t/t6300-for-each-ref.sh            | 200 +++++++++++++++++++++++++++++\n 3 files changed, 330 insertions(+), 30 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 2ae2478de706..42cef4c9617d 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -235,6 +235,20 @@ 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`, For commit and tag objects, `raw` contain\n+`header` and `contents` two parts, `header` is structured part of raw data, it\n+composed of \"tree XXX\", \"parent YYY\", etc lines in commits , or composed of\n+\"object OOO\", \"type TTT\", etc lines in tags; `contents` is unstructured \"free\n+text\" part of raw object data. For blob and tree objects, their raw data don't\n+have `header` and `contents` parts.\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 if our binary raw data is passed to a variable in the host language,\n+the host languages may cause escape errors.\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 f6a7b5290d54..5211fe946b70 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -138,6 +138,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@@ -370,6 +373,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@@ -530,6 +545,7 @@ static struct {\n \t{ \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n \t{ \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n \t{ \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n+\t{ \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n \t{ \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t{ \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n \t{ \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n@@ -564,12 +580,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@@ -588,13 +607,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@@ -604,6 +616,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@@ -652,11 +675,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@@ -683,9 +709,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@@ -785,14 +814,16 @@ 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+\tconst char *s = buf->buf;\n+\tsize_t cur_len = 0;\n+\n+\twhile ((cur_len != buf->len) && (isspace(*s) || *s == '\\0')) {\n \t\ts++;\n+\t\tcur_len++;\n \t}\n-\treturn 1;\n+\treturn cur_len == buf->len;\n }\n \n static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,\n@@ -800,6 +831,7 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta\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@@ -810,18 +842,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@@ -867,7 +903,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@@ -1293,7 +1329,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@@ -1309,6 +1345,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,\n \t\tif (deref)\n \t\t\tname++;\n \n+\t\tif (starts_with(name, \"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@@ -1378,25 +1424,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@@ -1618,7 +1669,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@@ -1698,7 +1749,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@@ -2301,6 +2352,20 @@ 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 = (const void *)vs1;\n+\tconst char *s2 = (const void *)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@@ -2321,10 +2386,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@@ -2424,6 +2509,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..46703d4e512f 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,158 @@ 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+\t>blob7 &&\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+'\n+\n+test_expect_success 'Verify sorts with raw' '\n+\tcat >expected <<-EOF &&\n+\trefs/myblobs/blob7\n+\trefs/myblobs/blob5\n+\trefs/myblobs/blob6\n+\trefs/myblobs/blob3\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/blob7\n+\trefs/myblobs/first\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+\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/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 empty\n+\trefs/myblobs/blob6 not empty\n+\trefs/myblobs/blob7 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":"425919","messageId":"c56734ba-c819-ccfe-f255-6cc890f6feef@gmail.com","threadId":"55782","inReplyTo":"CAOLTT8QmByyue25YLTa2-=oQ00K5Cs_eoSD3_y6wRYYToa=oxQ@mail.gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-05-30T13:02:17Z","receivedAt":"2021-05-30T13:02:22Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi ZheNing\n\nOn 30/05/2021 07:26, ZheNing Hu wrote:\n> Phillip Wood <phillip.wood123@gmail.com> 于2021年5月29日周六 下午9:23写道：\n>>\n>> On 27/05/2021 17:36, Felipe Contreras wrote:\n>>> ZheNing Hu via GitGitGadget wrote:\n>>> [...]\n>>> All we have to do is define the end point, and then we don't need i:\n>>>\n>>>        static int memcasecmp(const char *s1, const char *s2, size_t n)\n>>>        {\n>>>                const char *end = s1 + n;\n>>>                for (; s1 < end; s1++, s2++) {\n>>>                        int diff = tolower(*s1) - tolower(*s2);\n>>>                        if (diff)\n>>>                                return diff;\n>>>                }\n>>>                return 0;\n>>>        }\n>>>\n>>> (and I personally prefer lower to upper)\n>>\n>> We should be using tolower() as that is what POSIX specifies for\n>> strcasecmp() [1] which we are trying to emulate and there are cases[2] where\n>>          (tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))\n>>\n> \n> I don’t know if we overlooked a fact: This static `memcasecmp()`\n> is not a POSIX version. `tolower()` or `toupper()` are in git-compat-util.h,\n> sane_istest('\\0', GIT_ALPHA) == false . So in `sane_case()`, whatever\n> `tolower()`, `toupper()`, they just return '\\0' itself.\n\nWell spotted, thanks for pointing that out. So memcasecmp() and \nstrcasecmp() may give different results. I'm not sure if that matters - \nas I understand it the main use for the 'raw' atom is with `git cat-file \n--batch` which does not support sorting. Also although strcasecmp() uses \nthe current locale it does a byte-by-byte comparison so it is \neffectively ASCII only for UTF-8 anyway.\n\nBest Wishes\n\nPhillip\n\n>> Best Wishes\n>>\n>> Phillip\n>>\n>> [1] https://pubs.opengroup.org/onlinepubs/9699919799/\n>> [2] https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing\n>>\n> \n> Thanks.\n> --\n> ZhenNing Hu\n> \n\n"},{"id":"425920","messageId":"2c28515c-89b1-79dd-35a2-492eac6b7347@gmail.com","threadId":"55782","inReplyTo":"CAOLTT8TEkrxAgHvkCVXZDQpn0xSQAe_y7uMF89Q4QWwVd7MDcA@mail.gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-05-30T13:05:55Z","receivedAt":"2021-05-30T13:06:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi ZheNing\n\nOn 30/05/2021 07:29, ZheNing Hu wrote:\n> Felipe Contreras <felipe.contreras@gmail.com> 于2021年5月29日周六 下午11:24写道：\n>>\n>> Phillip Wood wrote:\n>>> On 27/05/2021 17:36, Felipe Contreras wrote:\n>>>> ZheNing Hu via GitGitGadget wrote:\n>>>> [...]\n>>>>> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n>>>>\n>>>> Why void *? We can delcare as char *.\n>>>\n>>> If you look at how this function is used you'll see\n>>>        int (*cmp_fn)(const void *, const void *, size_t);\n>>>        cmp_fn = s->sort_flags & REF_SORTING_ICASE\n>>>                        ? memcasecmp : memcmp;\n>>\n>> Yeah, but why?\n>>\n>> We know we are comparing two char *. Presumably the reason is that\n>> memcmp and memcasecmp use void *, but that could be remedied with:\n>>\n>>          cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;\n>>\n>> That way the same cmp_fn could be used for the two cases.\n>>\n>> Either way I don't care particularly much. It also could be possible to\n>> use void * and do the casting in tolower().\n>>\n> \n> I agree with Phillip's point of view here:\n> It would be better for memcasecmp and memcmp to be consistent.\n> \n>>>> (and I personally prefer lower to upper)\n>>>\n>>> We should be using tolower() as that is what POSIX specifies for\n>>> strcasecmp() [1] which we are trying to emulate and there are cases[2] where\n>>>        (tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))\n>>\n>> That's true.\n>>\n> \n> How about something like this:\n> \n>   static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n>   {\n> -       size_t i;\n> -       const char *s1 = (const char *)vs1;\n> -       const char *s2 = (const char *)vs2;\n> -\n> -       for (i = 0; i < n; i++) {\n> -               unsigned char u1 = s1[i];\n> -               unsigned char u2 = s2[i];\n> -               int U1 = toupper (u1);\n> -               int U2 = toupper (u2);\n> -               int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> -                       : U1 < U2 ? -1 : U2 < U1);\n> +       const char *s1 = (const void *)vs1;\n> +       const char *s2 = (const void *)vs2;\n\nI think the new version looks fine apart from these casts. vs1 declared \nas 'const void *' in the function signature so this cast does not do \nanything. You could cast using (const char *) instead if you wanted but \nthat is not required as you can assign a 'const void *' to 'const \nwhatever *' without a cast.\n\nBest Wishes\n\nPhillip\n\n> +       const char *end = s1 + n;\n> +\n> +       for (; s1 < end; s1++, s2++) {\n> +               int diff = tolower(*s1) - tolower(*s2);\n>                  if (diff)\n>                          return diff;\n>          }\n> }\n> \n>> --\n>> Felipe Contreras\n> \n> Thanks.\n> --\n> ZheNing Hu\n> \n\n"},{"id":"425949","messageId":"xmqqh7ij20l8.fsf@gitster.g","threadId":"55782","inReplyTo":"e44a2ed0db596ab0e0f484c25facb7da7214369e.1622379718.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-31T00:44:51Z","receivedAt":"2021-05-31T00:44:57Z","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> Beyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,\n> `--tcl`, `--perl` because if our binary raw data is passed to a variable\n> in the host language, the host languages may cause escape errors.\n\n\"may cause escape errors\" just says you are not escaping correctly\nin your code (implying that this patch is not good enough and with\nmore effort we should be able to fix it to allow binaries), but the\nproblem is the host languages may not support binaries\n(specifically, anything with a NUL in it) at all, which is\nfundamentally unfixable, in which case, rejecting is the only\nsensible choice.\n\n    ... because the host language may not support a NUL in the variables\n    of its string type.\n\n> +The raw data in a object is `raw`, For commit and tag objects, `raw` contain\n\ns/contain/contains/, but more importantly, as we are not introducing\n%(header), I do not see why we want to talk about its details.  For\ncommits and tags, just like for trees and blobs, 'raw' is the raw\ndata in the object, so beyond \"The raw data of a object is %(raw)\",\nI do not think there is anything to talk about.\n\n> +`header` and `contents` two parts, `header` is structured part of raw data, it\n> +composed of \"tree XXX\", \"parent YYY\", etc lines in commits , or composed of\n> +\"object OOO\", \"type TTT\", etc lines in tags; `contents` is unstructured \"free\n> +text\" part of raw object data. For blob and tree objects, their raw data don't\n> +have `header` and `contents` parts.\n\n\n> +\tconst char *s = buf->buf;\n> +\tsize_t cur_len = 0;\n> +\n> +\twhile ((cur_len != buf->len) && (isspace(*s) || *s == '\\0')) {\n>  \t\ts++;\n> +\t\tcur_len++;\n\nIs NUL treated the same as a whitespace letter for the purpose of\ndetermining if a line is empty?  WHY?\n\n"},{"id":"425957","messageId":"xmqqtumjy2e4.fsf@gitster.g","threadId":"55782","inReplyTo":"e44a2ed0db596ab0e0f484c25facb7da7214369e.1622379718.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-31T04:04:51Z","receivedAt":"2021-05-31T04:04:59Z","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> @@ -530,6 +545,7 @@ static struct {\n>  \t{ \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n>  \t{ \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n>  \t{ \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n> +\t{ \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n>  \t{ \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n>  \t{ \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n>  \t{ \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n\nDoesn't this conflict with your own zh/ref-filter-atom-type topic?\nShouldn't one build on top of the other?\n\nOr did we find something fundamentally broken about the other topic\nto make us retract it that I do not remember?\n\nThanks.\n"},{"id":"425958","messageId":"xmqqo8cry24t.fsf@gitster.g","threadId":"55782","inReplyTo":"e44a2ed0db596ab0e0f484c25facb7da7214369e.1622379718.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-31T04:10:26Z","receivedAt":"2021-05-31T04:10:36Z","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> @@ -530,6 +545,7 @@ static struct {\n>  \t{ \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n>  \t{ \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n>  \t{ \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n> +\t{ \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n>  \t{ \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n>  \t{ \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n>  \t{ \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n\nDoesn't this conflict with your own zh/ref-filter-atom-type topic?\nShouldn't one build on top of the other?\n\nOr did we find something fundamentally broken about the other topic\nto make us retract it that I do not remember?\n\nThanks.\n"},{"id":"425966","messageId":"xmqqk0nfwjno.fsf@gitster.g","threadId":"55782","inReplyTo":"e6c26d19a3f3b4791c435867550da538cfe6e287.1622379718.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/2] [GSOC] ref-filter: add obj-type check in grab contents","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-31T05:34:51Z","receivedAt":"2021-05-31T05:34:57Z","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> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Only tag and commit will use `grab_sub_body_contents()`\n> to grab object contents in origin implement. If we want\n> to make blob, tree can also use `grab_sub_body_contents()`\n> to get objects' raw data, a blob look like commit or tag\n> will be wrongly regarded as commit, tag by `find_subpos()`.\n>\n> So we must add a test before `find_subpos()` to reject\n> blob, tree objects. This will help us add %(raw) atom\n> which can grab raw data of four type objects.\n>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  ref-filter.c | 18 +++++++++++-------\n>  1 file changed, 11 insertions(+), 7 deletions(-)\n\nThanks.  I'll rephrase the log message while queuing, but the change\nas a separate preliminary step does make sense.\n\n    ref-filter: add obj-type check in grab contents\n    \n    Only tag and commit objects use `grab_sub_body_contents()` to grab\n    object contents in the current codebase.  We want to teach the\n    function to also handle blobs and trees to get their raw data,\n    without parsing a blob (whose contents looks like a commit or a tag)\n    incorrectly as a commit or a tag.\n    \n    Skip the block of code that is specific to handling commits and tags\n    early when the given object is of a wrong type to help later\n    addition to handle other types of objects in this function.\n"},{"id":"426008","messageId":"CAOLTT8QnWru=jHYUWt_vddPnUwzP5+h0dN2s-pcHpOy=ZC1+Sw@mail.gmail.com","threadId":"55782","inReplyTo":"2c28515c-89b1-79dd-35a2-492eac6b7347@gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-31T14:15:34Z","receivedAt":"2021-05-31T15:29:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> 于2021年5月30日周日 下午9:05写道：\n>\n> >   static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> >   {\n> > -       size_t i;\n> > -       const char *s1 = (const char *)vs1;\n> > -       const char *s2 = (const char *)vs2;\n> > -\n> > -       for (i = 0; i < n; i++) {\n> > -               unsigned char u1 = s1[i];\n> > -               unsigned char u2 = s2[i];\n> > -               int U1 = toupper (u1);\n> > -               int U2 = toupper (u2);\n> > -               int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> > -                       : U1 < U2 ? -1 : U2 < U1);\n> > +       const char *s1 = (const void *)vs1;\n> > +       const char *s2 = (const void *)vs2;\n>\n> I think the new version looks fine apart from these casts. vs1 declared\n> as 'const void *' in the function signature so this cast does not do\n> anything. You could cast using (const char *) instead if you wanted but\n> that is not required as you can assign a 'const void *' to 'const\n> whatever *' without a cast.\n>\n\nYes, forced conversion in \"const char *s1 = (const char *)vs1;\" is somewhat\nredundant.\n\n> Best Wishes\n>\n> Phillip\n>\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426014","messageId":"CAOLTT8REM7tF6ojdmnsJt7-RE6a8oX4RFXVcL215YnYTx6b62w@mail.gmail.com","threadId":"55782","inReplyTo":"xmqqh7ij20l8.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-31T14:35:28Z","receivedAt":"2021-05-31T15:54:16Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年5月31日周一 上午8:44写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > Beyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,\n> > `--tcl`, `--perl` because if our binary raw data is passed to a variable\n> > in the host language, the host languages may cause escape errors.\n>\n> \"may cause escape errors\" just says you are not escaping correctly\n> in your code (implying that this patch is not good enough and with\n> more effort we should be able to fix it to allow binaries), but the\n> problem is the host languages may not support binaries\n> (specifically, anything with a NUL in it) at all, which is\n> fundamentally unfixable, in which case, rejecting is the only\n> sensible choice.\n>\n>     ... because the host language may not support a NUL in the variables\n>     of its string type.\n>\n\nI agree. But host language not only support NUL but also some Non-ASCII\ncharacter and Non-UTF-8 code:\n\n$ git hash-object a.out -w | xargs git update-ref refs/myblobs/aoutblob\n$ git for-each-ref --format=\"name=%(raw)\" refs/myblobs/aoutblob\n--python | python2\n  File \"<stdin>\", line 1\nSyntaxError: Non-ASCII character '\\x8b' in file <stdin> on line 2, but\nno encoding declared;\n see http://python.org/dev/peps/pep-0263/ for details\n\n$ git for-each-ref --format=\"name=%(raw)\" refs/myblobs/aoutblob\n--python |python3\nSyntaxError: Non-UTF-8 code starting with '\\x8b' in file <stdin> on\nline 2, but no encoding declared;\n see http://python.org/dev/peps/pep-0263/ for details\n\n> > +The raw data in a object is `raw`, For commit and tag objects, `raw` contain\n>\n> s/contain/contains/, but more importantly, as we are not introducing\n> %(header), I do not see why we want to talk about its details.  For\n> commits and tags, just like for trees and blobs, 'raw' is the raw\n> data in the object, so beyond \"The raw data of a object is %(raw)\",\n> I do not think there is anything to talk about.\n>\n\nOk, I will delete this part.\n\n> > +     const char *s = buf->buf;\n> > +     size_t cur_len = 0;\n> > +\n> > +     while ((cur_len != buf->len) && (isspace(*s) || *s == '\\0')) {\n> >               s++;\n> > +             cur_len++;\n>\n> Is NUL treated the same as a whitespace letter for the purpose of\n> determining if a line is empty?  WHY?\n>\n\nWell, there seems to be no correction here. But is it true that memory\nlike \"\\0abc\" is considered empty?\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426016","messageId":"CAOLTT8QFW4UrwZ+OboYqkoT-oKo1Sz3JJ-Fp8b2RWugMOy5Cug@mail.gmail.com","threadId":"55782","inReplyTo":"xmqqtumjy2e4.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-05-31T14:40:22Z","receivedAt":"2021-05-31T16:04:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年5月31日周一 下午12:04写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > @@ -530,6 +545,7 @@ static struct {\n> >       { \"body\", SOURCE_OBJ, FIELD_STR, body_atom_parser },\n> >       { \"trailers\", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },\n> >       { \"contents\", SOURCE_OBJ, FIELD_STR, contents_atom_parser },\n> > +     { \"raw\", SOURCE_OBJ, FIELD_STR, raw_atom_parser },\n> >       { \"upstream\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n> >       { \"push\", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },\n> >       { \"symref\", SOURCE_NONE, FIELD_STR, refname_atom_parser },\n>\n> Doesn't this conflict with your own zh/ref-filter-atom-type topic?\n> Shouldn't one build on top of the other?\n>\n> Or did we find something fundamentally broken about the other topic\n> to make us retract it that I do not remember?\n>\n> Thanks.\n\nI am waiting for zh/ref-filter-atom-type to be merged into master. But it\nhasn't happened yet. But if I want to base the current topic on\nzh/ref-filter-atom-type, GGG will send past patches (zh/ref-filter-atom-type)\nrepeatedly. If necessary, I will submit the current branch based on\nzh/ref-filter-atom-type.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426022","messageId":"60b50249ae62a_24d2820893@natae.notmuch","threadId":"55782","inReplyTo":"CAOLTT8TEkrxAgHvkCVXZDQpn0xSQAe_y7uMF89Q4QWwVd7MDcA@mail.gmail.com","subject":"Re: [PATCH 1/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-31T15:35:37Z","receivedAt":"2021-05-31T17:20:12Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"ZheNing Hu wrote:\n> Felipe Contreras <felipe.contreras@gmail.com> 于2021年5月29日周六 下午11:24写道：\n> >\n> > Phillip Wood wrote:\n> > > On 27/05/2021 17:36, Felipe Contreras wrote:\n> > > > ZheNing Hu via GitGitGadget wrote:\n> > > > [...]\n> > > >> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> > > >\n> > > > Why void *? We can delcare as char *.\n> > >\n> > > If you look at how this function is used you'll see\n> > >       int (*cmp_fn)(const void *, const void *, size_t);\n> > >       cmp_fn = s->sort_flags & REF_SORTING_ICASE\n> > >                       ? memcasecmp : memcmp;\n> >\n> > Yeah, but why?\n> >\n> > We know we are comparing two char *. Presumably the reason is that\n> > memcmp and memcasecmp use void *, but that could be remedied with:\n> >\n> >         cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;\n> >\n> > That way the same cmp_fn could be used for the two cases.\n> >\n> > Either way I don't care particularly much. It also could be possible to\n> > use void * and do the casting in tolower().\n> >\n> \n> I agree with Phillip's point of view here:\n> It would be better for memcasecmp and memcmp to be consistent.\n\nFair enough.\n\n>  static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n>  {\n> -       size_t i;\n> -       const char *s1 = (const char *)vs1;\n> -       const char *s2 = (const char *)vs2;\n> -\n> -       for (i = 0; i < n; i++) {\n> -               unsigned char u1 = s1[i];\n> -               unsigned char u2 = s2[i];\n> -               int U1 = toupper (u1);\n> -               int U2 = toupper (u2);\n> -               int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2\n> -                       : U1 < U2 ? -1 : U2 < U1);\n> +       const char *s1 = (const void *)vs1;\n> +       const char *s2 = (const void *)vs2;\n\nvs1 is already a const void *, and there's not much point in adding\nanother line:\n\n\tconst char *s1 = vs1, *s2 = vs2;\n\nCheers.\n\n-- \nFelipe Contreras"},{"id":"426023","messageId":"60b503ab859d_24d282087@natae.notmuch","threadId":"55782","inReplyTo":"e44a2ed0db596ab0e0f484c25facb7da7214369e.1622379718.git.gitgitgadget@gmail.com","subject":"RE: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-31T15:41:31Z","receivedAt":"2021-05-31T17:21:03Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"ZheNing Hu via GitGitGadget wrote:\n> From: ZheNing Hu <adlternative@gmail.com>\n\n> +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> +{\n> +\tconst char *s1 = (const void *)vs1;\n> +\tconst char *s2 = (const void *)vs2;\n\nAs I explained in another mail, I think this is better:\n\n\tconst char *s1 = vs1, *s2 = vs2;\n\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-- \nFelipe Contreras\n"},{"id":"426101","messageId":"xmqqlf7ut15w.fsf@gitster.g","threadId":"55782","inReplyTo":"CAOLTT8QFW4UrwZ+OboYqkoT-oKo1Sz3JJ-Fp8b2RWugMOy5Cug@mail.gmail.com","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-06-01T08:54:51Z","receivedAt":"2021-06-01T08:55:01Z","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>> Doesn't this conflict with your own zh/ref-filter-atom-type topic?\n>> Shouldn't one build on top of the other?\n>>\n>> Or did we find something fundamentally broken about the other topic\n>> to make us retract it that I do not remember?\n>>\n>> Thanks.\n>\n> I am waiting for zh/ref-filter-atom-type to be merged into master. But it\n\nAs you sent this that conflicts with it, clearly you are doing\nsomething else that conflicts with it _without waiting_ ;-).\n\n> hasn't happened yet. But if I want to base the current topic on\n> zh/ref-filter-atom-type, GGG will send past patches (zh/ref-filter-atom-type)\n> repeatedly.\n\nI thought GGG lets you say \"this is based on that other branch, not\non the 'master' branch\" to solve that exact issue?\n"},{"id":"426106","messageId":"xmqqa6o9ucyc.fsf@gitster.g","threadId":"55782","inReplyTo":"CAOLTT8REM7tF6ojdmnsJt7-RE6a8oX4RFXVcL215YnYTx6b62w@mail.gmail.com","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-06-01T09:54:51Z","receivedAt":"2021-06-01T09:54:55Z","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>>     ... because the host language may not support a NUL in the variables\n>>     of its string type.\n>>\n>\n> I agree. But host language not only support NUL but also some Non-ASCII\n> character and Non-UTF-8 code:\n\nYup, rephase with \"a NUL\" with \"arbitrary binary data\" and you got\nwhat I meant.  Thanks.\n\n>> > +     const char *s = buf->buf;\n>> > +     size_t cur_len = 0;\n>> > +\n>> > +     while ((cur_len != buf->len) && (isspace(*s) || *s == '\\0')) {\n>> >               s++;\n>> > +             cur_len++;\n>>\n>> Is NUL treated the same as a whitespace letter for the purpose of\n>> determining if a line is empty?  WHY?\n>\n> Well, there seems to be no correction here. But is it true that memory\n> like \"\\0abc\" is considered empty?\n\nThat sample has 'a' or 'b' or 'c' that are clearly not part of an\n\"empty\" string and irrelevant.  After all, a string \" abc\" is not\ntreated as empty in the original implementation, either.\n\nYou are treating a block of memory with e.g. \" \\000 \" (SP NUL SP) as\nan \"empty line\" just like you do for \"   \" (SP SP SP), but I think we\nshould treat it more like \" \\001 \" or \" \\007 \", i.e. not an empty\nstring at all.\n"},{"id":"426109","messageId":"CAOLTT8SXYGVxHfZVu9gp6ZSE0xNY+kM9Lq4ifApOr3JixsScDQ@mail.gmail.com","threadId":"55782","inReplyTo":"60b503ab859d_24d282087@natae.notmuch","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-01T10:37:15Z","receivedAt":"2021-06-01T10:37:37Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Felipe Contreras <felipe.contreras@gmail.com> 于2021年5月31日周一 下午11:41写道：\n>\n> ZheNing Hu via GitGitGadget wrote:\n> > From: ZheNing Hu <adlternative@gmail.com>\n>\n> > +static int memcasecmp(const void *vs1, const void *vs2, size_t n)\n> > +{\n> > +     const char *s1 = (const void *)vs1;\n> > +     const char *s2 = (const void *)vs2;\n>\n> As I explained in another mail, I think this is better:\n>\n>         const char *s1 = vs1, *s2 = vs2;\n>\n\nOK, I understand it now.\n\n> > +     const char *end = s1 + n;\n> > +\n> > +     for (; s1 < end; s1++, s2++) {\n> > +             int diff = tolower(*s1) - tolower(*s2);\n> > +             if (diff)\n> > +                     return diff;\n> > +     }\n> > +     return 0;\n> > +}\n>\n> --\n> Felipe Contreras\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426113","messageId":"CAOLTT8STkD_2vhL7uCt1umgmfGBRKcuF7nWv+DpQ4H0dBSLJ0A@mail.gmail.com","threadId":"55782","inReplyTo":"xmqqlf7ut15w.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-01T11:00:09Z","receivedAt":"2021-06-01T11:00:23Z","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月1日周二 下午4:54写道：\n\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> >> Doesn't this conflict with your own zh/ref-filter-atom-type topic?\n> >> Shouldn't one build on top of the other?\n> >>\n> >> Or did we find something fundamentally broken about the other topic\n> >> to make us retract it that I do not remember?\n> >>\n> >> Thanks.\n> >\n> > I am waiting for zh/ref-filter-atom-type to be merged into master. But it\n>\n> As you sent this that conflicts with it, clearly you are doing\n> something else that conflicts with it _without waiting_ ;-).\n>\n\nOK.\n\n> > hasn't happened yet. But if I want to base the current topic on\n> > zh/ref-filter-atom-type, GGG will send past patches (zh/ref-filter-atom-type)\n> > repeatedly.\n>\n> I thought GGG lets you say \"this is based on that other branch, not\n> on the 'master' branch\" to solve that exact issue?\n\nI'm not sure...I will try it after I rebasing this topic to\nzh/ref-filter-atom-type.\nI just remember that it looked like something went wrong with my base on\nyour patch last time.[1]\n\n[1] https://lore.kernel.org/git/pull.870.v6.git.1613739235241.gitgitgadget@gmail.com/\n"},{"id":"426114","messageId":"CAOLTT8TP0kxp=MJw_kBifWjf9n69FWS8Mcxn0EZjsM69MdidHw@mail.gmail.com","threadId":"55782","inReplyTo":"xmqqa6o9ucyc.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-06-01T11:05:58Z","receivedAt":"2021-06-01T11:06:13Z","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月1日周二 下午5:54写道：\n>\n> > Well, there seems to be no correction here. But is it true that memory\n> > like \"\\0abc\" is considered empty?\n>\n> That sample has 'a' or 'b' or 'c' that are clearly not part of an\n> \"empty\" string and irrelevant.  After all, a string \" abc\" is not\n> treated as empty in the original implementation, either.\n>\n\nIn other words, we still need to look at each character of strbuf,\ninstead of stopping at NUL.\n\n> You are treating a block of memory with e.g. \" \\000 \" (SP NUL SP) as\n> an \"empty line\" just like you do for \"   \" (SP SP SP), but I think we\n> should treat it more like \" \\001 \" or \" \\007 \", i.e. not an empty\n> string at all.\n\nOK. I understand it now: \" \\001 \" is It’s like a block of space, but it’s\nnot truly \"empty\", \"SP NUL SP\" is same too, So the complete definition of\n\"empty\" here should be: All characters are SP which do not contain NUL\nor other characters.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"426125","messageId":"nycvar.QRO.7.76.6.2106011541140.55@tvgsbejvaqbjf.bet","threadId":"55782","inReplyTo":"CAOLTT8STkD_2vhL7uCt1umgmfGBRKcuF7nWv+DpQ4H0dBSLJ0A@mail.gmail.com","subject":"Re: [PATCH v2 2/2] [GSOC] ref-filter: add %(raw) atom","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-06-01T13:48:44Z","receivedAt":"2021-06-01T13:48:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 1 Jun 2021, ZheNing Hu wrote:\n\n> Junio C Hamano <gitster@pobox.com> 于2021年6月1日周二 下午4:54写道：\n>\n> > ZheNing Hu <adlternative@gmail.com> writes:\n> >\n> > > [...] But if I want to base the current topic on\n> > > zh/ref-filter-atom-type, GGG will send past patches\n> > > (zh/ref-filter-atom-type) repeatedly.\n> >\n> > I thought GGG lets you say \"this is based on that other branch, not on\n> > the 'master' branch\" to solve that exact issue?\n>\n> I'm not sure...I will try it after I rebasing this topic to\n> zh/ref-filter-atom-type.\n\nYes, it should be possible to rebase your patch on top of one of the\n[a-z][a-z]/* patches Junio publishes at https://github.com/gitster/git\n(and which get mirrored automatically to\nhttps://github.com/gitgitgadget/git via a scheduled Azure Pipeline), and\nthen to change the PR base (simply click the `Edit` button next to the PR\ntitle, as if you wanted to edit the title, and you can also change the\nbase branch).\n\n> I just remember that it looked like something went wrong with my base on\n> your patch last time.[1]\n>\n> [1] https://lore.kernel.org/git/pull.870.v6.git.1613739235241.gitgitgadget@gmail.com/\n\nhttps://github.com/gitgitgadget/git/pull/870 seems to be based on\njc/diffcore-rotate all right.\n\nIf you are talking about including Junio's patch in v5\n(https://lore.kernel.org/git/fb4bfd0f8b162e51e71711fe5503ca684f980d58.1613480198.git.gitgitgadget@gmail.com/#r),\nI _think_ that there might have been the simple problem of\njc/diffcore-rotate having been force-pushed just before you sent v5, and\ntherefore you had a stale branch.\n\nTo prevent things like that, it is a good idea to set the upstream of your\nlocal branch accordingly (in this instance, `git branch\n--set-upstream-to=gitgitgadget/jc/diffcore-rotate`) and ensure to `git\npull --rebase` before force-pushing and submitting.\n\nCiao,\nDscho\n"}]}