From: ZheNing Hu via GitGitGadget <hidden> Date: 2021-05-27 14:43:29
In order to make git cat-file --batch use ref-filter logic, I add %(raw)
atom to ref-filter.
Change from last version:
1. In my discussion with Junio, I came to the conclusion that
--format="%(raw)" should not be used with --python, --perl, --shell,
--tcl. Therefore, die if both --format="%(raw)" and
--language are given in parse_ref_filter_atom(). The reason I don't move
this part to raw_atom_parser() is if I move it to raw_atom_parser(), when we
use:
git --format=%raw --sort=raw --python`
Git will continue to run instead of die because parse_sorting_atom() will
use a dummy ref_format and don't remember --language details, next time
format_ref_array_item() will reuse the used_atom entry of sorting atom in
parse_ref_filter_atom(), This will skip the check in raw_atom_parser(). 2.
Give atom_value.s_size a init value ATOM_VALUE_S_SIZE_INIT (-1), which can
help us distinguish an object whose length is 0 and an object whose s_size
has not been modified after initialization. 3. Add %(header) atom.
ZheNing Hu (2):
[GSOC] ref-filter: add %(raw) atom
[GSOC] ref-filter: add %(header) atom
Documentation/git-for-each-ref.txt | 21 +++
ref-filter.c | 182 ++++++++++++++++++----
t/t6300-for-each-ref.sh | 236 +++++++++++++++++++++++++++++
3 files changed, 412 insertions(+), 27 deletions(-)
base-commit: 5d5b1473453400224ebb126bf3947e0a3276bdf5
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-963%2Fadlternative%2Fref-filter-raw-atom-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-963/adlternative/ref-filter-raw-atom-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/963
--
gitgitgadget
From: ZheNing Hu via GitGitGadget <hidden> Date: 2021-05-27 14:43:30
From: ZheNing Hu <redacted>
Add new formatting option `%(raw)`, which will print the raw
object data without any changes. It will help further to migrate
all cat-file formatting logic from cat-file to ref-filter.
The raw data of blob, tree objects may contain '\0', but most of
the logic in `ref-filter` depands on the output of the atom being
a structured string (end with '\0').
E.g. `quote_formatting()` use `strbuf_addstr()` or `*._quote_buf()`
add the data to the buffer. The raw data of a tree object is
`100644 one\0...`, only the `100644 one` will be added to the buffer,
which is incorrect.
Therefore, add a new member in `struct atom_value`: `s_size`, which
can record raw object size, it can help us add raw object data to
the buffer or compare two buffers which contain raw object data.
Beyond, `--format=%(raw)` should not combine with `--python`, `--shell`,
`--tcl`, `--perl` because if our binary raw data is passed to a variable
in the host language, the host languages may cause escape errors.
Based-on-patch-by: Olga Telezhnaya [off-list ref]
Signed-off-by: ZheNing Hu <redacted>
---
Documentation/git-for-each-ref.txt | 14 +++
ref-filter.c | 156 +++++++++++++++++++----
t/t6300-for-each-ref.sh | 191 +++++++++++++++++++++++++++++
3 files changed, 334 insertions(+), 27 deletions(-)
@@ -235,6 +235,20 @@ and `date` to extract the named component. For email fields (`authoremail`, without angle brackets, and `:localpart` to get the part before the `@` symbol out of the trimmed email.+The raw data in a object is `raw`, For commit and tag objects, `raw` contain+`header` and `contents` two parts, `header` is structured part of raw data, it+composed of "tree XXX", "parent YYY", etc lines in commits , or composed of+"object OOO", "type TTT", etc lines in tags; `contents` is unstructured "free+text" part of raw object data. For blob and tree objects, their raw data don't+have `header` and `contents` parts.++raw:size::+ The raw data size of the object.++Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tcl`,+`--perl` because if our binary raw data is passed to a variable in the host language,+the host languages may cause escape errors.+ The message in a commit or a tag object is `contents`, from which `contents:<part>` can be used to extract various parts out of:
@@ -564,12 +580,15 @@ struct ref_formatting_state {structatom_value{constchar*s;+size_ts_size;int(*handler)(structatom_value*atomv,structref_formatting_state*state,structstrbuf*err);uintmax_tvalue;/* used for sorting when not FIELD_STR */structused_atom*atom;};+#define ATOM_VALUE_S_SIZE_INIT (-1)+/**Usedtoparseformatstringandsortspecifiers*/
@@ -588,6 +607,10 @@ static int parse_ref_filter_atom(const struct ref_format *format,returnstrbuf_addf_ret(err,-1,_("malformed field name: %.*s"),(int)(ep-atom),atom);+if(format->quote_style&&starts_with(sp,"raw"))+returnstrbuf_addf_ret(err,-1,_("--format=%.*s should not combine with"+"--python, --shell, --tcl, --perl"),(int)(ep-atom),atom);+/* Do we have the atom already used elsewhere? */for(i=0;i<used_atom_cnt;i++){intlen=strlen(used_atom[i].name);
@@ -652,11 +675,14 @@ static int parse_ref_filter_atom(const struct ref_format *format,returnat;}-staticvoidquote_formatting(structstrbuf*s,constchar*str,intquote_style)+staticvoidquote_formatting(structstrbuf*s,constchar*str,size_tlen,intquote_style){switch(quote_style){caseQUOTE_NONE:-strbuf_addstr(s,str);+if(len!=ATOM_VALUE_S_SIZE_INIT)+strbuf_add(s,str,len);+else+strbuf_addstr(s,str);break;caseQUOTE_SHELL:sq_quote_buf(s,str);
@@ -810,18 +842,28 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_staif(if_then_else->else_atom_seen)returnstrbuf_addf_ret(err,-1,_("format: %%(then) atom used after %%(else)"));if_then_else->then_atom_seen=1;+if(if_then_else->str)+str_len=strlen(if_then_else->str);/**Ifthe'equals'or'notequals'attributeisusedthen*performtherequiredcomparison.Ifnot,onlynon-empty*stringssatisfythe'if'condition.*/if(if_then_else->cmp_status==COMPARE_EQUAL){-if(!strcmp(if_then_else->str,cur->output.buf))+if(!if_then_else->str)+BUG("when if_then_else->cmp_status == COMPARE_EQUAL,"+"if_then_else->str must not be null");+if(str_len==cur->output.len&&+!memcmp(if_then_else->str,cur->output.buf,cur->output.len))if_then_else->condition_satisfied=1;}elseif(if_then_else->cmp_status==COMPARE_UNEQUAL){-if(strcmp(if_then_else->str,cur->output.buf))+if(!if_then_else->str)+BUG("when if_then_else->cmp_status == COMPARE_UNEQUAL,"+"if_then_else->str must not be null");+if(str_len!=cur->output.len||+memcmp(if_then_else->str,cur->output.buf,cur->output.len))if_then_else->condition_satisfied=1;-}elseif(cur->output.len&&!is_empty(cur->output.buf))+}elseif(cur->output.len&&!is_empty(&cur->output))if_then_else->condition_satisfied=1;strbuf_reset(&cur->output);return0;
@@ -1614,7 +1673,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **objreturnstrbuf_addf_ret(err,-1,_("parse_object_buffer failed on %s for %s"),oid_to_hex(&oi->oid),ref->refname);}-grab_values(ref->value,deref,*obj,oi->content);+grab_values(ref->value,deref,*obj,oi);}grab_common_values(ref->value,deref,oi);
@@ -708,6 +737,15 @@ test_atom refs/tags/signed-long contents "subject line bodycontents$sig"+test_expect_success'basic atom: refs/tags/signed-long raw''+gitcat-filetagrefs/tags/signed-long>expected&&+gitfor-each-ref--format="%(raw)"refs/tags/signed-long>actual&&+sanitize_pgp<expected>expected.clean&&+sanitize_pgp<actual>actual.clean&&+echo"">>expected.clean&&+test_cmpexpected.cleanactual.clean+'+ test_expect_success'set up refs pointing to tree and blob''gitupdate-refrefs/mytrees/firstrefs/heads/main^{tree}&&gitupdate-refrefs/myblobs/firstrefs/heads/main:one
@@ -727,6 +775,149 @@ test_atom refs/myblobs/first contents:body "" test_atomrefs/myblobs/firstcontents:signature"" test_atomrefs/myblobs/firstcontents""+test_expect_success'basic atom: refs/myblobs/first raw''+gitcat-fileblobrefs/myblobs/first>expected&&+echo"">>expected&&+gitfor-each-ref--format="%(raw)"refs/myblobs/first>actual&&+test_cmpexpectedactual&&+gitcat-file-srefs/myblobs/first>expected&&+gitfor-each-ref--format="%(raw:size)"refs/myblobs/first>actual&&+test_cmpexpectedactual+'++test_expect_success'set up refs pointing to binary blob''+printf"%b""a\0b\0c">blob1&&+printf"%b""a\0c\0b">blob2&&+printf"%b""\0a\0b\0c">blob3&&+printf"%b""abc">blob4&&+printf"%b""\0 \0 \0 ">blob5&&+printf"%b""\0 \0a\0 ">blob6&&+>blob7&&+githash-objectblob1-w|xargsgitupdate-refrefs/myblobs/blob1&&+githash-objectblob2-w|xargsgitupdate-refrefs/myblobs/blob2&&+githash-objectblob3-w|xargsgitupdate-refrefs/myblobs/blob3&&+githash-objectblob4-w|xargsgitupdate-refrefs/myblobs/blob4&&+githash-objectblob5-w|xargsgitupdate-refrefs/myblobs/blob5&&+githash-objectblob6-w|xargsgitupdate-refrefs/myblobs/blob6&&+githash-objectblob7-w|xargsgitupdate-refrefs/myblobs/blob7+'++test_expect_success'Verify sorts with raw''+cat>expected<<-EOF&&+refs/myblobs/blob7+refs/myblobs/blob5+refs/myblobs/blob6+refs/myblobs/blob3+refs/mytrees/first+refs/myblobs/first+refs/myblobs/blob1+refs/myblobs/blob2+refs/myblobs/blob4+refs/heads/main+EOF+gitfor-each-ref--format="%(refname)"--sort=raw\+refs/heads/mainrefs/myblobs/refs/mytrees/first>actual&&+test_cmpexpectedactual+'++test_expect_success'Verify sorts with raw:size''+cat>expected<<-EOF&&+refs/myblobs/blob7+refs/myblobs/first+refs/heads/main+refs/myblobs/blob4+refs/myblobs/blob1+refs/myblobs/blob2+refs/myblobs/blob3+refs/myblobs/blob5+refs/myblobs/blob6+refs/mytrees/first+EOF+gitfor-each-ref--format="%(refname)"--sort=raw:size\+refs/heads/mainrefs/myblobs/refs/mytrees/first>actual&&+test_cmpexpectedactual+'++test_expect_success'validate raw atom with %(if:equals)''+cat>expected<<-EOF&&+notequals+notequals+notequals+notequals+notequals+notequals+refs/myblobs/blob4+notequals+notequals+notequals+notequals+EOF+gitfor-each-ref--format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)"\+refs/myblobs/refs/heads/>actual&&+test_cmpexpectedactual+'+test_expect_success'validate raw atom with %(if:notequals)''+cat>expected<<-EOF&&+refs/heads/ambiguous+refs/heads/main+refs/heads/newtag+refs/myblobs/blob1+refs/myblobs/blob2+refs/myblobs/blob3+equals+refs/myblobs/blob5+refs/myblobs/blob6+refs/myblobs/blob7+refs/myblobs/first+EOF+gitfor-each-ref--format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)"\+refs/myblobs/refs/heads/>actual&&+test_cmpexpectedactual+'++test_expect_success'empty raw refs with %(if)''+cat>expected<<-EOF&&+refs/myblobs/blob1notempty+refs/myblobs/blob2notempty+refs/myblobs/blob3notempty+refs/myblobs/blob4notempty+refs/myblobs/blob5empty+refs/myblobs/blob6notempty+refs/myblobs/blob7empty+refs/myblobs/firstnotempty+EOF+gitfor-each-ref--format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)"\+refs/myblobs/>actual&&+test_cmpexpectedactual+'++test_expect_success'%(raw) with --python must failed''+test_must_failgitfor-each-ref--format="%(raw)"--python+'++test_expect_success'%(raw) with --tcl must failed''+test_must_failgitfor-each-ref--format="%(raw)"--tcl+'++test_expect_success'%(raw) with --perl must failed''+test_must_failgitfor-each-ref--format="%(raw)"--perl+'++test_expect_success'%(raw) with --shell must failed''+test_must_failgitfor-each-ref--format="%(raw)"--shell+'++test_expect_success'%(raw) with --shell and --sort=raw must failed''+test_must_failgitfor-each-ref--format="%(raw)"--sort=raw--shell+'++test_expect_success'for-each-ref --format compare with cat-file --batch''+gitrev-parserefs/mytrees/first|gitcat-file--batch>expected&&+gitfor-each-ref--format="%(objectname) %(objecttype) %(objectsize)+%(raw)" refs/mytrees/first >actual &&+test_cmpexpectedactual+'+ test_expect_success'set up multiple-sort tags''forwhenin100000200000do
From: ZheNing Hu via GitGitGadget <hidden> Date: 2021-05-27 14:43:31
From: ZheNing Hu <redacted>
Add new formatting option `%(header)`, which will print the
the structured header part of the raw object data.
In the storage layout of an object: blob and tree only
contains raw data; commit and tag raw data contains two part:
header and contents. The header of tag contains "object OOO",
"type TTT", "tag AAA", "tagger GGG"; The header of commit
contains "tree RRR", "parent PPP", "author UUU", "committer CCC".
Signed-off-by: ZheNing Hu <redacted>
---
Documentation/git-for-each-ref.txt | 7 +++++
ref-filter.c | 26 +++++++++++++++++
t/t6300-for-each-ref.sh | 45 ++++++++++++++++++++++++++++++
3 files changed, 78 insertions(+)
@@ -249,6 +249,13 @@ Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tc `--perl` because if our binary raw data is passed to a variable in the host language, the host languages may cause escape errors.+The structured header part of the raw data in a commit or a tag object is `header`,+it composed of "tree XXX", "parent YYY", etc lines in commits, or composed of+"object OOO", "type TTT", etc lines in tags.++header:size::+ The header size of the object.+ The message in a commit or a tag object is `contents`, from which `contents:<part>` can be used to extract various parts out of:
From: Felipe Contreras <hidden> Date: 2021-05-27 15:40:00
ZheNing Hu via GitGitGadget wrote:
In order to make git cat-file --batch use ref-filter logic, I add %(raw)
atom to ref-filter.
Change from last version:
1. In my discussion with Junio, I came to the conclusion that
--format="%(raw)" should not be used with --python, --perl, --shell,
--tcl. Therefore, die if both --format="%(raw)" and
--language are given in parse_ref_filter_atom(). The reason I don't move
this part to raw_atom_parser() is if I move it to raw_atom_parser(), when we
use:
git --format=%raw --sort=raw --python`
Missing the command I presume (and the other backtick).
--
Felipe Contreras
+ for (i = 0; i < n; i++) {
+ unsigned char u1 = s1[i];
+ unsigned char u2 = s2[i];
There's no need for two entirely new variables...
+ int U1 = toupper (u1);
+ int U2 = toupper (u2);
You can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,
not `foo (x)`).
While we are at it, why keep an extra index from s1, when s1 is never
used again?
We can simply advance both s1 and s2:
s1++, s2++
+ int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
+ : U1 < U2 ? -1 : U2 < U1);
I don't understand what this is supposed to achieve. Both U1 and U2 are
integers, pretty low integers actually.
If we get rid if that complexity we don't even need U1 or U2, just do:
diff = toupper(u1) - toupper(u2);
+ if (diff)
+ return diff;
+ }
+ return 0;
+}
All we have to do is define the end point, and then we don't need i:
static int memcasecmp(const char *s1, const char *s2, size_t n)
{
const char *end = s1 + n;
for (; s1 < end; s1++, s2++) {
int diff = tolower(*s1) - tolower(*s2);
if (diff)
return diff;
}
return 0;
}
(and I personally prefer lower to upper)
Check the following resource for a detailed explanation of why my
modified version is considered good taste:
https://github.com/felipec/linked-list-good-taste
This hurts my eyes. I think the complexity of this chunk warrants a
separate function. Then the logic would be easer to see.
Cheers.
--
Felipe Contreras
From: Junio C Hamano <hidden> Date: 2021-05-28 03:04:07
"ZheNing Hu via GitGitGadget" [off-list ref] writes:
The raw data of blob, tree objects may contain '\0', but most of
the logic in `ref-filter` depands on the output of the atom being
a structured string (end with '\0').
Text, yes, string is also OK. But structured? Probably not.
... being text (specifically, no embedded NULs in it).
E.g. `quote_formatting()` use `strbuf_addstr()` or `*._quote_buf()`
add the data to the buffer. The raw data of a tree object is
`100644 one\0...`, only the `100644 one` will be added to the buffer,
which is incorrect.
Therefore, add a new member in `struct atom_value`: `s_size`, which
can record raw object size, it can help us add raw object data to
the buffer or compare two buffers which contain raw object data.
Other than the phrasing issue around "structured", all of the above
is a good description.
Beyond, `--format=%(raw)` should not combine with `--python`, `--shell`,
`--tcl`, `--perl` because if our binary raw data is passed to a variable
in the host language, the host languages may cause escape errors.
OK. I think at least --perl and possibly --python should be able to
express NULs in the "string" type we use from the host language, but
I am perfectly fine with the decision to leave it to later updates.
After all, the --<lang> feature to write scriptlets via --format and
execute them in the named language interpreter probably is not as
often used as it was originally designed for, so it might be that
nobody will ask for such "later updates".
quoted hunk
@@ -235,6 +235,20 @@ and `date` to extract the named component. For email fields (`authoremail`, without angle brackets, and `:localpart` to get the part before the `@` symbol out of the trimmed email.+The raw data in a object is `raw`, For commit and tag objects, `raw` contain+`header` and `contents` two parts, `header` is structured part of raw data, it+composed of "tree XXX", "parent YYY", etc lines in commits , or composed of+"object OOO", "type TTT", etc lines in tags; `contents` is unstructured "free+text" part of raw object data. For blob and tree objects, their raw data don't+have `header` and `contents` parts.++raw:size::+ The raw data size of the object.++Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tcl`,
"should not combine" -> "cannot be used" would make it read more
naturally (ditto for the phrase used in the proposed log message).
quoted hunk
+`--perl` because if our binary raw data is passed to a variable in the host language,
+the host languages may cause escape errors.
+
The message in a commit or a tag object is `contents`, from which
`contents:<part>` can be used to extract various parts out of:
OK, so everything used to be a C-string that cannot hold NULs in it,
but now it is a counted <ptr, len> string. Good.
quoted hunk
int (*handler)(struct atom_value *atomv, struct ref_formatting_state *state,
struct strbuf *err);
uintmax_t value; /* used for sorting when not FIELD_STR */
struct used_atom *atom;
};
+#define ATOM_VALUE_S_SIZE_INIT (-1)
+
/*
* Used to parse format string and sort specifiers
*/
@@ -588,6 +607,10 @@ static int parse_ref_filter_atom(const struct ref_format *format, return strbuf_addf_ret(err, -1, _("malformed field name: %.*s"), (int)(ep-atom), atom);+ if (format->quote_style && starts_with(sp, "raw"))+ return strbuf_addf_ret(err, -1, _("--format=%.*s should not combine with"+ "--python, --shell, --tcl, --perl"), (int)(ep-atom), atom);
Don't we want to allow "raw:size" that would be a plain text?
I am not sure if this check belongs here in the first place.
Shouldn't it be done in raw_atom_parser() instead?
Another idea is to teach a more generic rule to quote_formatting()
to detect NULs in v->s[0..v->s_size] at runtime and barf, i.e. a
plain-text blob object can be used with "--shell --format=%(raw)"
just fine.
It probably is a good idea to invent a C preprocessor macro for a
named constant to be used when a structure is initialized, but it
would be easier to read if the rule is "len field is negative when
the value is a C-string", e.g.
if (len < 0)
Assuming that we do want to treat NULs the same way as whitespaces,
the updated code works as intended, which is good. But I have no
reason to support that design decision. I do not have a strong
reason to support a design decision that goes the opposite way to
treat a NUL just like we treat an 'X', but at least I can understand
it (i.e. "because we have no reason to special case NUL any more
than 'X' when trying to see if a buffer is 'empty', we don't").
This code on the other hand must be supported with "because we need
to special case NUL for such and such reasons for the purpose of
determining if a buffer is 'empty', we treat them the same way as
whitespaces".
@@ -810,18 +842,28 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta if (if_then_else->else_atom_seen) return strbuf_addf_ret(err, -1, _("format: %%(then) atom used after %%(else)")); if_then_else->then_atom_seen = 1;+ if (if_then_else->str)+ str_len = strlen(if_then_else->str); /* * If the 'equals' or 'notequals' attribute is used then * perform the required comparison. If not, only non-empty * strings satisfy the 'if' condition. */ if (if_then_else->cmp_status == COMPARE_EQUAL) {- if (!strcmp(if_then_else->str, cur->output.buf))+ if (!if_then_else->str)+ BUG("when if_then_else->cmp_status == COMPARE_EQUAL,"+ "if_then_else->str must not be null");
Can the change in this commit violate the invariant that
if_then_else->str cannot be NULL, which seems to have been the case
forever as we see an unchecked strcmp() done in the original?
If so, perhaps you can check the condition upfront, where you
compute str_len above, e.g.
if (!if_then_else->str) {
if (if_then_else->cmp_status == COMPARE_EQUAL ||
if_then_else->cmp_status == COMPARE_UNEQUAL)
BUG(...);
} else
str_len = strlen(...);
If not, then I do not see the point of adding this (and later) check
with BUG to this code.
Or is the invariant that .str must not be NULL could have been
violated without this patch (i.e. the original was buggy in running
strcmp() on .str without checking)? If so, please make it a separate
preliminary change to add such an assert.
+ if (str_len == cur->output.len &&
+ !memcmp(if_then_else->str, cur->output.buf, cur->output.len))
if_then_else->condition_satisfied = 1;
} else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
- if (strcmp(if_then_else->str, cur->output.buf))
+ if (!if_then_else->str)
+ BUG("when if_then_else->cmp_status == COMPARE_UNEQUAL,"
+ "if_then_else->str must not be null");
quoted hunk
/* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
+static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned long buf_size, struct object *obj)
{
int i;
const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
@@ -1307,10 +1349,22 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf) continue; if (deref) name++;- if (strcmp(name, "body") &&- !starts_with(name, "subject") &&- !starts_with(name, "trailers") &&- !starts_with(name, "contents"))++ if (starts_with(name, "raw")) {+ if (atom->u.raw_data.option == RAW_BARE) {+ v->s = xmemdupz(buf, buf_size);+ v->s_size = buf_size;+ } else if (atom->u.raw_data.option == RAW_LENGTH)+ v->s = xstrfmt("%"PRIuMAX, (uintmax_t)buf_size);+ continue;+ }
I can understand that "raw[:<options>]" handling has been inserted
above the existing "from here on, we only deal with log message
components" check. But
I do not see why these new conditions are added. The change is not
justified in the proposed log message, the original did not need
these conditions, and this does not concern the primary point of the
change, which is to start supporting %(raw[:<option>]) placeholder.
If it is needed as a bugfix (e.g. it may be that you consider "if a
blob has contents that looks very similar to 'git cat-file commit
HEAD', %(body) and friends parse these out, even though it is not a
commit" is a bug and the change to add these extra tests is meant as
a fix), that should be done as a preliminary change before adding
the support for a new atom.
@@ -1374,25 +1428,30 @@ static void fill_missing_values(struct atom_value *val) * pointed at by the ref itself; otherwise it is the object the * ref (which is a tag) refers to. */-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data) {+ void *buf = data->content;+ unsigned long buf_size = data->size;+ switch (obj->type) { case OBJ_TAG: grab_tag_values(val, deref, obj);- grab_sub_body_contents(val, deref, buf);+ grab_raw_data(val, deref, buf, buf_size, obj);
It is very strange that a helper that is named to grab raw data can
still process pieces out of a structured data. The original name is
still a far better match to what the function does, even after this
patch teaches it to also honor %(raw) placeholder. It is still
about grabbing various "sub"-pieces out of "body contents", and the
sub-piece the %(raw) grabs just happens to be "the whole thing".
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
+{
+ size_t i;
+ const char *s1 = (const char *)vs1;
+ const char *s2 = (const char *)vs2;
+
+ for (i = 0; i < n; i++) {
+ unsigned char u1 = s1[i];
+ unsigned char u2 = s2[i];
+ int U1 = toupper (u1);
+ int U2 = toupper (u2);
Does toupper('\0') even have a defined meaning?
+ int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
+ : U1 < U2 ? -1 : U2 < U1);
Looks crazy to worry about uchar wider than int. Such a system is
not even standard compliant, is it?
Why not introduce two local temporary variables a_size and b_size
and initialize them upfront like so:
a_size = va->s_size < 0 ? strlen(va->s) : va->s_size;
b_size = vb->s_size < 0 ? strlen(vb->s) : vb->s_size;
Wouldn't that allow you to do without the complex "if both are
counted, do this, if A is counted but B is not, do that, ..."
cascade?
I can sort-of see the point of special casing "both are traditional
C strings" case (i.e. the "if" side of the "else" we are discussing
here) and using strcasecmp/strcmp instead of memcasecmp/memcmp, but
I do not see much point in having the if/elseif/else cascade inside
this "else" clause.
+ for (i = 0; i < n; i++) {
+ unsigned char u1 = s1[i];
+ unsigned char u2 = s2[i];
There's no need for two entirely new variables...
quoted
+ int U1 = toupper (u1);
+ int U2 = toupper (u2);
You can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,
not `foo (x)`).
While we are at it, why keep an extra index from s1, when s1 is never
used again?
We can simply advance both s1 and s2:
s1++, s2++
quoted
+ int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
+ : U1 < U2 ? -1 : U2 < U1);
I don't understand what this is supposed to achieve. Both U1 and U2 are
integers, pretty low integers actually.
If we get rid if that complexity we don't even need U1 or U2, just do:
diff = toupper(u1) - toupper(u2);
quoted
+ if (diff)
+ return diff;
+ }
+ return 0;
+}
All we have to do is define the end point, and then we don't need i:
static int memcasecmp(const char *s1, const char *s2, size_t n)
{
const char *end = s1 + n;
for (; s1 < end; s1++, s2++) {
int diff = tolower(*s1) - tolower(*s2);
if (diff)
return diff;
}
return 0;
}
(and I personally prefer lower to upper)
Sorry for the weird, unclean `memcasecmp()`, I referred to memcmp()
in glibc before, and then I was afraid that my writing was not standard
enough like "UCHAR_MAX <= INT_MAX", I can't consider such an
extreme situation. So I copied it directly from gnulib:
https://github.com/gagern/gnulib/blob/master/lib/memcasecmp.c
From: ZheNing Hu <hidden> Date: 2021-05-28 15:05:23
Junio C Hamano [off-list ref] 于2021年5月28日周五 上午11:04写道:
"ZheNing Hu via GitGitGadget" [off-list ref] writes:
quoted
The raw data of blob, tree objects may contain '\0', but most of
the logic in `ref-filter` depands on the output of the atom being
a structured string (end with '\0').
Text, yes, string is also OK. But structured? Probably not.
... being text (specifically, no embedded NULs in it).
OK.
quoted
Beyond, `--format=%(raw)` should not combine with `--python`, `--shell`,
`--tcl`, `--perl` because if our binary raw data is passed to a variable
in the host language, the host languages may cause escape errors.
OK. I think at least --perl and possibly --python should be able to
express NULs in the "string" type we use from the host language, but
I am perfectly fine with the decision to leave it to later updates.
Yes, for the time being, unified processing --<lang> will be easier.
quoted
+Note that `--format=%(raw)` should not combine with `--python`, `--shell`, `--tcl`,
"should not combine" -> "cannot be used" would make it read more
naturally (ditto for the phrase used in the proposed log message).
OK, so everything used to be a C-string that cannot hold NULs in it,
but now it is a counted <ptr, len> string. Good.
This suddenly reminded me of strbuf...
I don't know if it is worth replacing all s, s_size with strbuf.
quoted
@@ -588,6 +607,10 @@ static int parse_ref_filter_atom(const struct ref_format *format, return strbuf_addf_ret(err, -1, _("malformed field name: %.*s"), (int)(ep-atom), atom);+ if (format->quote_style && starts_with(sp, "raw"))+ return strbuf_addf_ret(err, -1, _("--format=%.*s should not combine with"+ "--python, --shell, --tcl, --perl"), (int)(ep-atom), atom);
Don't we want to allow "raw:size" that would be a plain text?
I am not sure if this check belongs here in the first place.
Shouldn't it be done in raw_atom_parser() instead?
You are right: "raw:size" should be keep, but I can't this check to
raw_atom_parser(), becase "if I move it to raw_atom_parser(), when we
use:
`git ref-filter --format=%raw --sort=raw --python`
Git will continue to run instead of die because parse_sorting_atom() will
use a dummy ref_format and don't remember --language details, next time
format_ref_array_item() will reuse the used_atom entry of sorting atom in
parse_ref_filter_atom(), this will skip the check in raw_atom_parser()."
Another idea is to teach a more generic rule to quote_formatting()
to detect NULs in v->s[0..v->s_size] at runtime and barf, i.e. a
plain-text blob object can be used with "--shell --format=%(raw)"
just fine.
The cost of such a check is not small. Maybe can add an option
such as "--only-text" to do it.
It probably is a good idea to invent a C preprocessor macro for a
named constant to be used when a structure is initialized, but it
would be easier to read if the rule is "len field is negative when
the value is a C-string", e.g.
if (len < 0)
I do not recognize such an approach because we are deal
with "size_t s_size", if (len < 0) will never be established.
I use -1 is because it's equal to 18446744073709551615
and it's impossible to have such a large file in Git.
Assuming that we do want to treat NULs the same way as whitespaces,
the updated code works as intended, which is good. But I have no
reason to support that design decision. I do not have a strong
reason to support a design decision that goes the opposite way to
treat a NUL just like we treat an 'X', but at least I can understand
it (i.e. "because we have no reason to special case NUL any more
than 'X' when trying to see if a buffer is 'empty', we don't").
This code on the other hand must be supported with "because we need
to special case NUL for such and such reasons for the purpose of
determining if a buffer is 'empty', we treat them the same way as
whitespaces".
Something like "\0abc", from the perspective of the string, it is empty;
from the perspective of the memory, it is not empty; I don't know any
absolutely good solutions here.
Can the change in this commit violate the invariant that
if_then_else->str cannot be NULL, which seems to have been the case
forever as we see an unchecked strcmp() done in the original?
If so, perhaps you can check the condition upfront, where you
compute str_len above, e.g.
if (!if_then_else->str) {
if (if_then_else->cmp_status == COMPARE_EQUAL ||
if_then_else->cmp_status == COMPARE_UNEQUAL)
BUG(...);
} else
str_len = strlen(...);
If not, then I do not see the point of adding this (and later) check
with BUG to this code.
Or is the invariant that .str must not be NULL could have been
violated without this patch (i.e. the original was buggy in running
strcmp() on .str without checking)? If so, please make it a separate
preliminary change to add such an assert.
The BUG() here actually acts as an "assert()". ".str must not be NULL" is
right, it point to "xxx" in "%(if:equals=xxx)", so it seems that these BUG()
are somewhat redundant, I will remove them.
quoted
/* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
+static void grab_raw_data(struct atom_value *val, int deref, void *buf, unsigned long buf_size, struct object *obj)
{
int i;
const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
@@ -1307,10 +1349,22 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf) continue; if (deref) name++;- if (strcmp(name, "body") &&- !starts_with(name, "subject") &&- !starts_with(name, "trailers") &&- !starts_with(name, "contents"))++ if (starts_with(name, "raw")) {+ if (atom->u.raw_data.option == RAW_BARE) {+ v->s = xmemdupz(buf, buf_size);+ v->s_size = buf_size;+ } else if (atom->u.raw_data.option == RAW_LENGTH)+ v->s = xstrfmt("%"PRIuMAX, (uintmax_t)buf_size);+ continue;+ }
I can understand that "raw[:<options>]" handling has been inserted
above the existing "from here on, we only deal with log message
components" check. But
I do not see why these new conditions are added. The change is not
justified in the proposed log message, the original did not need
these conditions, and this does not concern the primary point of the
change, which is to start supporting %(raw[:<option>]) placeholder.
If it is needed as a bugfix (e.g. it may be that you consider "if a
blob has contents that looks very similar to 'git cat-file commit
HEAD', %(body) and friends parse these out, even though it is not a
commit" is a bug and the change to add these extra tests is meant as
a fix), that should be done as a preliminary change before adding
the support for a new atom.
Almost what I means: Make a strong guarantee that blob and tree
will never pass the check so that we can don't worry about incorrect
parsing in find_subpos(). The reason I put it in this patch is that only
commit and tag objects will execute `grab_sub_body_contents()` before,
but in the current patch it has changed.
@@ -1374,25 +1428,30 @@ static void fill_missing_values(struct atom_value *val) * pointed at by the ref itself; otherwise it is the object the * ref (which is a tag) refers to. */-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data) {+ void *buf = data->content;+ unsigned long buf_size = data->size;+ switch (obj->type) { case OBJ_TAG: grab_tag_values(val, deref, obj);- grab_sub_body_contents(val, deref, buf);+ grab_raw_data(val, deref, buf, buf_size, obj);
It is very strange that a helper that is named to grab raw data can
still process pieces out of a structured data. The original name is
still a far better match to what the function does, even after this
patch teaches it to also honor %(raw) placeholder. It is still
about grabbing various "sub"-pieces out of "body contents", and the
sub-piece the %(raw) grabs just happens to be "the whole thing".
Well, I can't think of a better name, My original idea was grab_raw_data()
can grab itself, header, contents, It is more general than
grab_sub_body_contents(),
raw data is not a part of "subject" or "body" of "contents"...
quoted
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
+{
+ size_t i;
+ const char *s1 = (const char *)vs1;
+ const char *s2 = (const char *)vs2;
+
+ for (i = 0; i < n; i++) {
+ unsigned char u1 = s1[i];
+ unsigned char u2 = s2[i];
+ int U1 = toupper (u1);
+ int U2 = toupper (u2);
Does toupper('\0') even have a defined meaning?
quoted
+ int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
+ : U1 < U2 ? -1 : U2 < U1);
Looks crazy to worry about uchar wider than int. Such a system is
not even standard compliant, is it?
Forget about this inelegant help function. As I said in my reply to Felipe,
this is copied from gunlib...
Why not introduce two local temporary variables a_size and b_size
and initialize them upfront like so:
a_size = va->s_size < 0 ? strlen(va->s) : va->s_size;
b_size = vb->s_size < 0 ? strlen(vb->s) : vb->s_size;
Wouldn't that allow you to do without the complex "if both are
counted, do this, if A is counted but B is not, do that, ..."
cascade?
Sorry, such code would be really ugly for reading.
I can sort-of see the point of special casing "both are traditional
C strings" case (i.e. the "if" side of the "else" we are discussing
here) and using strcasecmp/strcmp instead of memcasecmp/memcmp, but
I do not see much point in having the if/elseif/else cascade inside
this "else" clause.
I will try to modify its logic.
Thanks.
Your reply is very accurate.
Thanks.
--
ZheNing Hu
From: Felipe Contreras <hidden> Date: 2021-05-28 16:30:14
ZheNing Hu wrote:
Sorry for the weird, unclean `memcasecmp()`, I referred to memcmp()
in glibc before, and then I was afraid that my writing was not standard
enough like "UCHAR_MAX <= INT_MAX", I can't consider such an
extreme situation. So I copied it directly from gnulib:
https://github.com/gagern/gnulib/blob/master/lib/memcasecmp.c
Yeah, I imagined you copied it from somewhere, but when you do that you
need to transform the code to the style of the project. I've seen GNU
code, and in my opinion it's too verbose and redundant. Not a good
style.
But more importantly: at the header of that file you can see the license
is GPLv3, that's incompatible with the license of this project, which is
GPLv2 only (see the note in COPYING).
You can't just copy code like that. You need to be careful.
And if you do copy code--even if allowed by the license--it's something
that should be mentioned in the commit message, preferably with a link
to the original, that way if there's trouble in the future with that
code, we can follow the link and figure out why it was done that way.
Also, it's just nice to give attribution to the people that wrote the
original code.
It is not a standard, it is my personal opinion, which is shared by
Linus Torvalds, and I presume other members of the Git project.
The style is not something that can be standardized, you get a feeling
of it as you read more code of the project, write, and then receive
feedback on what you write.
It's like learning the slang of a new city; it takes a while.
Cheers.
--
Felipe Contreras
From: Felipe Contreras <hidden> Date: 2021-05-28 16:39:06
ZheNing Hu wrote:
Junio C Hamano [off-list ref] 于2021年5月28日周五 上午11:04写道:
quoted
"ZheNing Hu via GitGitGadget" [off-list ref] writes:
quoted
quoted
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
+{
+ size_t i;
+ const char *s1 = (const char *)vs1;
+ const char *s2 = (const char *)vs2;
+
+ for (i = 0; i < n; i++) {
+ unsigned char u1 = s1[i];
+ unsigned char u2 = s2[i];
+ int U1 = toupper (u1);
+ int U2 = toupper (u2);
Does toupper('\0') even have a defined meaning?
Forget about this inelegant help function. As I said in my reply to Felipe,
this is copied from gunlib...
Even if you use my modified version (which hopefully is not so
inelegant), the comment about toupper('\0') still applies.
My reading of `man toupper(3)` is that if c is neither lowercase or
uppercase it is returned as-is. As long as it's unsigned char, which
'\0' is.
So I think the behavior is indeed defined.
--
Felipe Contreras
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
Why void *? We can delcare as char *.
If you look at how this function is used you'll see
int (*cmp_fn)(const void *, const void *, size_t);
cmp_fn = s->sort_flags & REF_SORTING_ICASE
? memcasecmp : memcmp;
So the signature must match memcmp to avoid undefined behavior (a
ternary expression is undefined unless both sides evaluate to the same
type and calling a function through a pointer a different type is
undefined as well)
+ for (i = 0; i < n; i++) {
+ unsigned char u1 = s1[i];
+ unsigned char u2 = s2[i];
There's no need for two entirely new variables...
quoted
+ int U1 = toupper (u1);
+ int U2 = toupper (u2);
You can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,
not `foo (x)`).
While we are at it, why keep an extra index from s1, when s1 is never
used again?
We can simply advance both s1 and s2:
s1++, s2++
quoted
+ int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
+ : U1 < U2 ? -1 : U2 < U1);
I don't understand what this is supposed to achieve. Both U1 and U2 are
integers, pretty low integers actually.
If we get rid if that complexity we don't even need U1 or U2, just do:
diff = toupper(u1) - toupper(u2);
quoted
+ if (diff)
+ return diff;
+ }
+ return 0;
+}
All we have to do is define the end point, and then we don't need i:
static int memcasecmp(const char *s1, const char *s2, size_t n)
{
const char *end = s1 + n;
for (; s1 < end; s1++, s2++) {
int diff = tolower(*s1) - tolower(*s2);
if (diff)
return diff;
}
return 0;
}
(and I personally prefer lower to upper)
From: Felipe Contreras <hidden> Date: 2021-05-29 15:24:28
Phillip Wood wrote:
On 27/05/2021 17:36, Felipe Contreras wrote:
quoted
ZheNing Hu via GitGitGadget wrote:
[...]
quoted
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
Why void *? We can delcare as char *.
If you look at how this function is used you'll see
int (*cmp_fn)(const void *, const void *, size_t);
cmp_fn = s->sort_flags & REF_SORTING_ICASE
? memcasecmp : memcmp;
Yeah, but why?
We know we are comparing two char *. Presumably the reason is that
memcmp and memcasecmp use void *, but that could be remedied with:
cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;
That way the same cmp_fn could be used for the two cases.
Either way I don't care particularly much. It also could be possible to
use void * and do the casting in tolower().
quoted
(and I personally prefer lower to upper)
We should be using tolower() as that is what POSIX specifies for
strcasecmp() [1] which we are trying to emulate and there are cases[2] where
(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
Why void *? We can delcare as char *.
If you look at how this function is used you'll see
int (*cmp_fn)(const void *, const void *, size_t);
cmp_fn = s->sort_flags & REF_SORTING_ICASE
? memcasecmp : memcmp;
Yeah, but why?
We know we are comparing two char *. Presumably the reason is that
memcmp and memcasecmp use void *, but that could be remedied with:
cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;
That way the same cmp_fn could be used for the two cases.
But that is still undefined behavior - the ugly cast just silences any
compiler warning without making the code safe. It calls memcmp using a
pointer of a different type. The type of cmp_fn and the two functions
assigned to it must match.
Best Wishes
Phillip
Either way I don't care particularly much. It also could be possible to
use void * and do the casting in tolower().
quoted
quoted
(and I personally prefer lower to upper)
We should be using tolower() as that is what POSIX specifies for
strcasecmp() [1] which we are trying to emulate and there are cases[2] where
(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))
From: ZheNing Hu <hidden> Date: 2021-05-30 05:37:54
Felipe Contreras [off-list ref] 于2021年5月29日周六 上午12:30写道:
ZheNing Hu wrote:
quoted
Sorry for the weird, unclean `memcasecmp()`, I referred to memcmp()
in glibc before, and then I was afraid that my writing was not standard
enough like "UCHAR_MAX <= INT_MAX", I can't consider such an
extreme situation. So I copied it directly from gnulib:
https://github.com/gagern/gnulib/blob/master/lib/memcasecmp.c
Yeah, I imagined you copied it from somewhere, but when you do that you
need to transform the code to the style of the project. I've seen GNU
code, and in my opinion it's too verbose and redundant. Not a good
style.
But more importantly: at the header of that file you can see the license
is GPLv3, that's incompatible with the license of this project, which is
GPLv2 only (see the note in COPYING).
You can't just copy code like that. You need to be careful.
Now I notice the importance of license in open source project.
And if you do copy code--even if allowed by the license--it's something
that should be mentioned in the commit message, preferably with a link
to the original, that way if there's trouble in the future with that
code, we can follow the link and figure out why it was done that way.
Also, it's just nice to give attribution to the people that wrote the
original code.
It is not a standard, it is my personal opinion, which is shared by
Linus Torvalds, and I presume other members of the Git project.
The style is not something that can be standardized, you get a feeling
of it as you read more code of the project, write, and then receive
feedback on what you write.
Yes it is. Reading and writing Git code has brought me a certain degree
of code style improvement. (this is indeed a kind of edification :) )
It's like learning the slang of a new city; it takes a while.
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
Why void *? We can delcare as char *.
If you look at how this function is used you'll see
int (*cmp_fn)(const void *, const void *, size_t);
cmp_fn = s->sort_flags & REF_SORTING_ICASE
? memcasecmp : memcmp;
So the signature must match memcmp to avoid undefined behavior (a
ternary expression is undefined unless both sides evaluate to the same
type and calling a function through a pointer a different type is
undefined as well)
+ for (i = 0; i < n; i++) {
+ unsigned char u1 = s1[i];
+ unsigned char u2 = s2[i];
There's no need for two entirely new variables...
quoted
+ int U1 = toupper (u1);
+ int U2 = toupper (u2);
You can do toupper(s1[i]) directly (BTW, there's an extra space: `foo(x)`,
not `foo (x)`).
While we are at it, why keep an extra index from s1, when s1 is never
used again?
We can simply advance both s1 and s2:
s1++, s2++
quoted
+ int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
+ : U1 < U2 ? -1 : U2 < U1);
I don't understand what this is supposed to achieve. Both U1 and U2 are
integers, pretty low integers actually.
If we get rid if that complexity we don't even need U1 or U2, just do:
diff = toupper(u1) - toupper(u2);
quoted
+ if (diff)
+ return diff;
+ }
+ return 0;
+}
All we have to do is define the end point, and then we don't need i:
static int memcasecmp(const char *s1, const char *s2, size_t n)
{
const char *end = s1 + n;
for (; s1 < end; s1++, s2++) {
int diff = tolower(*s1) - tolower(*s2);
if (diff)
return diff;
}
return 0;
}
(and I personally prefer lower to upper)
We should be using tolower() as that is what POSIX specifies for
strcasecmp() [1] which we are trying to emulate and there are cases[2] where
(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))
I don’t know if we overlooked a fact: This static `memcasecmp()`
is not a POSIX version. `tolower()` or `toupper()` are in git-compat-util.h,
sane_istest('\0', GIT_ALPHA) == false . So in `sane_case()`, whatever
`tolower()`, `toupper()`, they just return '\0' itself.
From: ZheNing Hu <hidden> Date: 2021-05-30 06:29:42
Felipe Contreras [off-list ref] 于2021年5月29日周六 下午11:24写道:
Phillip Wood wrote:
quoted
On 27/05/2021 17:36, Felipe Contreras wrote:
quoted
ZheNing Hu via GitGitGadget wrote:
[...]
quoted
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
Why void *? We can delcare as char *.
If you look at how this function is used you'll see
int (*cmp_fn)(const void *, const void *, size_t);
cmp_fn = s->sort_flags & REF_SORTING_ICASE
? memcasecmp : memcmp;
Yeah, but why?
We know we are comparing two char *. Presumably the reason is that
memcmp and memcasecmp use void *, but that could be remedied with:
cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;
That way the same cmp_fn could be used for the two cases.
Either way I don't care particularly much. It also could be possible to
use void * and do the casting in tolower().
I agree with Phillip's point of view here:
It would be better for memcasecmp and memcmp to be consistent.
quoted
quoted
(and I personally prefer lower to upper)
We should be using tolower() as that is what POSIX specifies for
strcasecmp() [1] which we are trying to emulate and there are cases[2] where
(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))
That's true.
How about something like this:
static int memcasecmp(const void *vs1, const void *vs2, size_t n)
{
- size_t i;
- const char *s1 = (const char *)vs1;
- const char *s2 = (const char *)vs2;
-
- for (i = 0; i < n; i++) {
- unsigned char u1 = s1[i];
- unsigned char u2 = s2[i];
- int U1 = toupper (u1);
- int U2 = toupper (u2);
- int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
- : U1 < U2 ? -1 : U2 < U1);
+ const char *s1 = (const void *)vs1;
+ const char *s2 = (const void *)vs2;
+ const char *end = s1 + n;
+
+ for (; s1 < end; s1++, s2++) {
+ int diff = tolower(*s1) - tolower(*s2);
if (diff)
return diff;
}
}
From: ZheNing Hu <hidden> Date: 2021-05-30 08:12:09
ZheNing Hu [off-list ref] 于2021年5月28日周五 下午11:04写道:
quoted
Can the change in this commit violate the invariant that
if_then_else->str cannot be NULL, which seems to have been the case
forever as we see an unchecked strcmp() done in the original?
If so, perhaps you can check the condition upfront, where you
compute str_len above, e.g.
if (!if_then_else->str) {
if (if_then_else->cmp_status == COMPARE_EQUAL ||
if_then_else->cmp_status == COMPARE_UNEQUAL)
BUG(...);
} else
str_len = strlen(...);
If not, then I do not see the point of adding this (and later) check
with BUG to this code.
Or is the invariant that .str must not be NULL could have been
violated without this patch (i.e. the original was buggy in running
strcmp() on .str without checking)? If so, please make it a separate
preliminary change to add such an assert.
The BUG() here actually acts as an "assert()". ".str must not be NULL" is
right, it point to "xxx" in "%(if:equals=xxx)", so it seems that these BUG()
are somewhat redundant, I will remove them.
Correct the error: If the atom is "%(if)" instread of
"%(if:equals=xxx)", .str will be NULL.
Without assert() or BUG() is ok, but clang-tidy will give a warning:
"Null pointer
passed to 1st parameter expecting 'nonnull'"
--
ZheNing Hu
From: ZheNing Hu via GitGitGadget <hidden> Date: 2021-05-30 13:02:06
From: ZheNing Hu <redacted>
Only tag and commit will use `grab_sub_body_contents()`
to grab object contents in origin implement. If we want
to make blob, tree can also use `grab_sub_body_contents()`
to get objects' raw data, a blob look like commit or tag
will be wrongly regarded as commit, tag by `find_subpos()`.
So we must add a test before `find_subpos()` to reject
blob, tree objects. This will help us add %(raw) atom
which can grab raw data of four type objects.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: ZheNing Hu <redacted>
---
ref-filter.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
From: ZheNing Hu via GitGitGadget <hidden> Date: 2021-05-30 13:02:19
From: ZheNing Hu <redacted>
Add new formatting option `%(raw)`, which will print the raw
object data without any changes. It will help further to migrate
all cat-file formatting logic from cat-file to ref-filter.
The raw data of blob, tree objects may contain '\0', but most of
the logic in `ref-filter` depands on the output of the atom being
text (specifically, no embedded NULs in it).
E.g. `quote_formatting()` use `strbuf_addstr()` or `*._quote_buf()`
add the data to the buffer. The raw data of a tree object is
`100644 one\0...`, only the `100644 one` will be added to the buffer,
which is incorrect.
Therefore, add a new member in `struct atom_value`: `s_size`, which
can record raw object size, it can help us add raw object data to
the buffer or compare two buffers which contain raw object data.
Beyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,
`--tcl`, `--perl` because if our binary raw data is passed to a variable
in the host language, the host languages may cause escape errors.
Helped-by: Felipe Contreras [off-list ref]
Helped-by: Phillip Wood [off-list ref]
Helped-by: Junio C Hamano [off-list ref]
Based-on-patch-by: Olga Telezhnaya [off-list ref]
Signed-off-by: ZheNing Hu <redacted>
---
Documentation/git-for-each-ref.txt | 14 ++
ref-filter.c | 146 ++++++++++++++++-----
t/t6300-for-each-ref.sh | 200 +++++++++++++++++++++++++++++
3 files changed, 330 insertions(+), 30 deletions(-)
@@ -235,6 +235,20 @@ and `date` to extract the named component. For email fields (`authoremail`, without angle brackets, and `:localpart` to get the part before the `@` symbol out of the trimmed email.+The raw data in a object is `raw`, For commit and tag objects, `raw` contain+`header` and `contents` two parts, `header` is structured part of raw data, it+composed of "tree XXX", "parent YYY", etc lines in commits , or composed of+"object OOO", "type TTT", etc lines in tags; `contents` is unstructured "free+text" part of raw object data. For blob and tree objects, their raw data don't+have `header` and `contents` parts.++raw:size::+ The raw data size of the object.++Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,+`--perl` because if our binary raw data is passed to a variable in the host language,+the host languages may cause escape errors.+ The message in a commit or a tag object is `contents`, from which `contents:<part>` can be used to extract various parts out of:
@@ -564,12 +580,15 @@ struct ref_formatting_state {structatom_value{constchar*s;+size_ts_size;int(*handler)(structatom_value*atomv,structref_formatting_state*state,structstrbuf*err);uintmax_tvalue;/* used for sorting when not FIELD_STR */structused_atom*atom;};+#define ATOM_VALUE_S_SIZE_INIT (-1)+/**Usedtoparseformatstringandsortspecifiers*/
@@ -588,13 +607,6 @@ static int parse_ref_filter_atom(const struct ref_format *format,returnstrbuf_addf_ret(err,-1,_("malformed field name: %.*s"),(int)(ep-atom),atom);-/* Do we have the atom already used elsewhere? */-for(i=0;i<used_atom_cnt;i++){-intlen=strlen(used_atom[i].name);-if(len==ep-atom&&!memcmp(used_atom[i].name,atom,len))-returni;-}-/**Iftheatomnamehasacolon,stripitandeverythingafter*itoff-itspecifiestheformatforthisentry,and
@@ -604,6 +616,17 @@ static int parse_ref_filter_atom(const struct ref_format *format,arg=memchr(sp,':',ep-sp);atom_len=(arg?arg:ep)-sp;+if(format->quote_style&&!strncmp(sp,"raw",3)&&!arg)+returnstrbuf_addf_ret(err,-1,_("--format=%.*s cannot be used with"+"--python, --shell, --tcl, --perl"),(int)(ep-atom),atom);++/* Do we have the atom already used elsewhere? */+for(i=0;i<used_atom_cnt;i++){+intlen=strlen(used_atom[i].name);+if(len==ep-atom&&!memcmp(used_atom[i].name,atom,len))+returni;+}+/* Is the atom a valid one? */for(i=0;i<ARRAY_SIZE(valid_atom);i++){intlen=strlen(valid_atom[i].name);
@@ -652,11 +675,14 @@ static int parse_ref_filter_atom(const struct ref_format *format,returnat;}-staticvoidquote_formatting(structstrbuf*s,constchar*str,intquote_style)+staticvoidquote_formatting(structstrbuf*s,constchar*str,size_tlen,intquote_style){switch(quote_style){caseQUOTE_NONE:-strbuf_addstr(s,str);+if(len!=ATOM_VALUE_S_SIZE_INIT)+strbuf_add(s,str,len);+else+strbuf_addstr(s,str);break;caseQUOTE_SHELL:sq_quote_buf(s,str);
@@ -810,18 +842,22 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_staif(if_then_else->else_atom_seen)returnstrbuf_addf_ret(err,-1,_("format: %%(then) atom used after %%(else)"));if_then_else->then_atom_seen=1;+if(if_then_else->str)+str_len=strlen(if_then_else->str);/**Ifthe'equals'or'notequals'attributeisusedthen*performtherequiredcomparison.Ifnot,onlynon-empty*stringssatisfythe'if'condition.*/if(if_then_else->cmp_status==COMPARE_EQUAL){-if(!strcmp(if_then_else->str,cur->output.buf))+if(str_len==cur->output.len&&+!memcmp(if_then_else->str,cur->output.buf,cur->output.len))if_then_else->condition_satisfied=1;}elseif(if_then_else->cmp_status==COMPARE_UNEQUAL){-if(strcmp(if_then_else->str,cur->output.buf))+if(str_len!=cur->output.len||+memcmp(if_then_else->str,cur->output.buf,cur->output.len))if_then_else->condition_satisfied=1;-}elseif(cur->output.len&&!is_empty(cur->output.buf))+}elseif(cur->output.len&&!is_empty(&cur->output))if_then_else->condition_satisfied=1;strbuf_reset(&cur->output);return0;
@@ -1618,7 +1669,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **objreturnstrbuf_addf_ret(err,-1,_("parse_object_buffer failed on %s for %s"),oid_to_hex(&oi->oid),ref->refname);}-grab_values(ref->value,deref,*obj,oi->content);+grab_values(ref->value,deref,*obj,oi);}grab_common_values(ref->value,deref,oi);
@@ -708,6 +737,15 @@ test_atom refs/tags/signed-long contents "subject line bodycontents$sig"+test_expect_success'basic atom: refs/tags/signed-long raw''+gitcat-filetagrefs/tags/signed-long>expected&&+gitfor-each-ref--format="%(raw)"refs/tags/signed-long>actual&&+sanitize_pgp<expected>expected.clean&&+sanitize_pgp<actual>actual.clean&&+echo"">>expected.clean&&+test_cmpexpected.cleanactual.clean+'+ test_expect_success'set up refs pointing to tree and blob''gitupdate-refrefs/mytrees/firstrefs/heads/main^{tree}&&gitupdate-refrefs/myblobs/firstrefs/heads/main:one
@@ -727,6 +775,158 @@ test_atom refs/myblobs/first contents:body "" test_atomrefs/myblobs/firstcontents:signature"" test_atomrefs/myblobs/firstcontents""+test_expect_success'basic atom: refs/myblobs/first raw''+gitcat-fileblobrefs/myblobs/first>expected&&+echo"">>expected&&+gitfor-each-ref--format="%(raw)"refs/myblobs/first>actual&&+test_cmpexpectedactual&&+gitcat-file-srefs/myblobs/first>expected&&+gitfor-each-ref--format="%(raw:size)"refs/myblobs/first>actual&&+test_cmpexpectedactual+'++test_expect_success'set up refs pointing to binary blob''+printf"%b""a\0b\0c">blob1&&+printf"%b""a\0c\0b">blob2&&+printf"%b""\0a\0b\0c">blob3&&+printf"%b""abc">blob4&&+printf"%b""\0 \0 \0 ">blob5&&+printf"%b""\0 \0a\0 ">blob6&&+>blob7&&+githash-objectblob1-w|xargsgitupdate-refrefs/myblobs/blob1&&+githash-objectblob2-w|xargsgitupdate-refrefs/myblobs/blob2&&+githash-objectblob3-w|xargsgitupdate-refrefs/myblobs/blob3&&+githash-objectblob4-w|xargsgitupdate-refrefs/myblobs/blob4&&+githash-objectblob5-w|xargsgitupdate-refrefs/myblobs/blob5&&+githash-objectblob6-w|xargsgitupdate-refrefs/myblobs/blob6&&+githash-objectblob7-w|xargsgitupdate-refrefs/myblobs/blob7+'++test_expect_success'Verify sorts with raw''+cat>expected<<-EOF&&+refs/myblobs/blob7+refs/myblobs/blob5+refs/myblobs/blob6+refs/myblobs/blob3+refs/mytrees/first+refs/myblobs/first+refs/myblobs/blob1+refs/myblobs/blob2+refs/myblobs/blob4+refs/heads/main+EOF+gitfor-each-ref--format="%(refname)"--sort=raw\+refs/heads/mainrefs/myblobs/refs/mytrees/first>actual&&+test_cmpexpectedactual+'++test_expect_success'Verify sorts with raw:size''+cat>expected<<-EOF&&+refs/myblobs/blob7+refs/myblobs/first+refs/heads/main+refs/myblobs/blob4+refs/myblobs/blob1+refs/myblobs/blob2+refs/myblobs/blob3+refs/myblobs/blob5+refs/myblobs/blob6+refs/mytrees/first+EOF+gitfor-each-ref--format="%(refname)"--sort=raw:size\+refs/heads/mainrefs/myblobs/refs/mytrees/first>actual&&+test_cmpexpectedactual+'++test_expect_success'validate raw atom with %(if:equals)''+cat>expected<<-EOF&&+notequals+notequals+notequals+notequals+notequals+notequals+refs/myblobs/blob4+notequals+notequals+notequals+notequals+EOF+gitfor-each-ref--format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)"\+refs/myblobs/refs/heads/>actual&&+test_cmpexpectedactual+'+test_expect_success'validate raw atom with %(if:notequals)''+cat>expected<<-EOF&&+refs/heads/ambiguous+refs/heads/main+refs/heads/newtag+refs/myblobs/blob1+refs/myblobs/blob2+refs/myblobs/blob3+equals+refs/myblobs/blob5+refs/myblobs/blob6+refs/myblobs/blob7+refs/myblobs/first+EOF+gitfor-each-ref--format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)"\+refs/myblobs/refs/heads/>actual&&+test_cmpexpectedactual+'++test_expect_success'empty raw refs with %(if)''+cat>expected<<-EOF&&+refs/myblobs/blob1notempty+refs/myblobs/blob2notempty+refs/myblobs/blob3notempty+refs/myblobs/blob4notempty+refs/myblobs/blob5empty+refs/myblobs/blob6notempty+refs/myblobs/blob7empty+refs/myblobs/firstnotempty+EOF+gitfor-each-ref--format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)"\+refs/myblobs/>actual&&+test_cmpexpectedactual+'++test_expect_success'%(raw) with --python must failed''+test_must_failgitfor-each-ref--format="%(raw)"--python+'++test_expect_success'%(raw) with --tcl must failed''+test_must_failgitfor-each-ref--format="%(raw)"--tcl+'++test_expect_success'%(raw) with --perl must failed''+test_must_failgitfor-each-ref--format="%(raw)"--perl+'++test_expect_success'%(raw) with --shell must failed''+test_must_failgitfor-each-ref--format="%(raw)"--shell+'++test_expect_success'%(raw) with --shell and --sort=raw must failed''+test_must_failgitfor-each-ref--format="%(raw)"--sort=raw--shell+'++test_expect_success'%(raw:size) with --shell''+gitfor-each-ref--format="%(raw:size)"|whilereadline+do+echo"'\''$line'\''">>expect+done&&+gitfor-each-ref--format="%(raw:size)"--shell>actual&&+test_cmpexpectactual+'++test_expect_success'for-each-ref --format compare with cat-file --batch''+gitrev-parserefs/mytrees/first|gitcat-file--batch>expected&&+gitfor-each-ref--format="%(objectname) %(objecttype) %(objectsize)+%(raw)" refs/mytrees/first >actual &&+test_cmpexpectedactual+'+ test_expect_success'set up multiple-sort tags''forwhenin100000200000do
ZheNing Hu via GitGitGadget wrote:
[...]
All we have to do is define the end point, and then we don't need i:
static int memcasecmp(const char *s1, const char *s2, size_t n)
{
const char *end = s1 + n;
for (; s1 < end; s1++, s2++) {
int diff = tolower(*s1) - tolower(*s2);
if (diff)
return diff;
}
return 0;
}
(and I personally prefer lower to upper)
We should be using tolower() as that is what POSIX specifies for
strcasecmp() [1] which we are trying to emulate and there are cases[2] where
(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))
I don’t know if we overlooked a fact: This static `memcasecmp()`
is not a POSIX version. `tolower()` or `toupper()` are in git-compat-util.h,
sane_istest('\0', GIT_ALPHA) == false . So in `sane_case()`, whatever
`tolower()`, `toupper()`, they just return '\0' itself.
Well spotted, thanks for pointing that out. So memcasecmp() and
strcasecmp() may give different results. I'm not sure if that matters -
as I understand it the main use for the 'raw' atom is with `git cat-file
--batch` which does not support sorting. Also although strcasecmp() uses
the current locale it does a byte-by-byte comparison so it is
effectively ASCII only for UTF-8 anyway.
Best Wishes
Phillip
Felipe Contreras [off-list ref] 于2021年5月29日周六 下午11:24写道:
quoted
Phillip Wood wrote:
quoted
On 27/05/2021 17:36, Felipe Contreras wrote:
quoted
ZheNing Hu via GitGitGadget wrote:
[...]
quoted
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
Why void *? We can delcare as char *.
If you look at how this function is used you'll see
int (*cmp_fn)(const void *, const void *, size_t);
cmp_fn = s->sort_flags & REF_SORTING_ICASE
? memcasecmp : memcmp;
Yeah, but why?
We know we are comparing two char *. Presumably the reason is that
memcmp and memcasecmp use void *, but that could be remedied with:
cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;
That way the same cmp_fn could be used for the two cases.
Either way I don't care particularly much. It also could be possible to
use void * and do the casting in tolower().
I agree with Phillip's point of view here:
It would be better for memcasecmp and memcmp to be consistent.
quoted
quoted
quoted
(and I personally prefer lower to upper)
We should be using tolower() as that is what POSIX specifies for
strcasecmp() [1] which we are trying to emulate and there are cases[2] where
(tolower(c1) == tolower(c2)) != (toupper(c1) == toupper(c2))
That's true.
How about something like this:
static int memcasecmp(const void *vs1, const void *vs2, size_t n)
{
- size_t i;
- const char *s1 = (const char *)vs1;
- const char *s2 = (const char *)vs2;
-
- for (i = 0; i < n; i++) {
- unsigned char u1 = s1[i];
- unsigned char u2 = s2[i];
- int U1 = toupper (u1);
- int U2 = toupper (u2);
- int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
- : U1 < U2 ? -1 : U2 < U1);
+ const char *s1 = (const void *)vs1;
+ const char *s2 = (const void *)vs2;
I think the new version looks fine apart from these casts. vs1 declared
as 'const void *' in the function signature so this cast does not do
anything. You could cast using (const char *) instead if you wanted but
that is not required as you can assign a 'const void *' to 'const
whatever *' without a cast.
Best Wishes
Phillip
From: Junio C Hamano <hidden> Date: 2021-05-31 00:44:57
"ZheNing Hu via GitGitGadget" [off-list ref] writes:
Beyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,
`--tcl`, `--perl` because if our binary raw data is passed to a variable
in the host language, the host languages may cause escape errors.
"may cause escape errors" just says you are not escaping correctly
in your code (implying that this patch is not good enough and with
more effort we should be able to fix it to allow binaries), but the
problem is the host languages may not support binaries
(specifically, anything with a NUL in it) at all, which is
fundamentally unfixable, in which case, rejecting is the only
sensible choice.
... because the host language may not support a NUL in the variables
of its string type.
+The raw data in a object is `raw`, For commit and tag objects, `raw` contain
s/contain/contains/, but more importantly, as we are not introducing
%(header), I do not see why we want to talk about its details. For
commits and tags, just like for trees and blobs, 'raw' is the raw
data in the object, so beyond "The raw data of a object is %(raw)",
I do not think there is anything to talk about.
+`header` and `contents` two parts, `header` is structured part of raw data, it
+composed of "tree XXX", "parent YYY", etc lines in commits , or composed of
+"object OOO", "type TTT", etc lines in tags; `contents` is unstructured "free
+text" part of raw object data. For blob and tree objects, their raw data don't
+have `header` and `contents` parts.
Doesn't this conflict with your own zh/ref-filter-atom-type topic?
Shouldn't one build on top of the other?
Or did we find something fundamentally broken about the other topic
to make us retract it that I do not remember?
Thanks.
Doesn't this conflict with your own zh/ref-filter-atom-type topic?
Shouldn't one build on top of the other?
Or did we find something fundamentally broken about the other topic
to make us retract it that I do not remember?
Thanks.
From: Junio C Hamano <hidden> Date: 2021-05-31 05:34:57
"ZheNing Hu via GitGitGadget" [off-list ref] writes:
From: ZheNing Hu <redacted>
Only tag and commit will use `grab_sub_body_contents()`
to grab object contents in origin implement. If we want
to make blob, tree can also use `grab_sub_body_contents()`
to get objects' raw data, a blob look like commit or tag
will be wrongly regarded as commit, tag by `find_subpos()`.
So we must add a test before `find_subpos()` to reject
blob, tree objects. This will help us add %(raw) atom
which can grab raw data of four type objects.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: ZheNing Hu <redacted>
---
ref-filter.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
Thanks. I'll rephrase the log message while queuing, but the change
as a separate preliminary step does make sense.
ref-filter: add obj-type check in grab contents
Only tag and commit objects use `grab_sub_body_contents()` to grab
object contents in the current codebase. We want to teach the
function to also handle blobs and trees to get their raw data,
without parsing a blob (whose contents looks like a commit or a tag)
incorrectly as a commit or a tag.
Skip the block of code that is specific to handling commits and tags
early when the given object is of a wrong type to help later
addition to handle other types of objects in this function.
static int memcasecmp(const void *vs1, const void *vs2, size_t n)
{
- size_t i;
- const char *s1 = (const char *)vs1;
- const char *s2 = (const char *)vs2;
-
- for (i = 0; i < n; i++) {
- unsigned char u1 = s1[i];
- unsigned char u2 = s2[i];
- int U1 = toupper (u1);
- int U2 = toupper (u2);
- int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
- : U1 < U2 ? -1 : U2 < U1);
+ const char *s1 = (const void *)vs1;
+ const char *s2 = (const void *)vs2;
I think the new version looks fine apart from these casts. vs1 declared
as 'const void *' in the function signature so this cast does not do
anything. You could cast using (const char *) instead if you wanted but
that is not required as you can assign a 'const void *' to 'const
whatever *' without a cast.
Yes, forced conversion in "const char *s1 = (const char *)vs1;" is somewhat
redundant.
From: ZheNing Hu <hidden> Date: 2021-05-31 15:54:16
Junio C Hamano [off-list ref] 于2021年5月31日周一 上午8:44写道:
"ZheNing Hu via GitGitGadget" [off-list ref] writes:
quoted
Beyond, `--format=%(raw)` cannot be used with `--python`, `--shell`,
`--tcl`, `--perl` because if our binary raw data is passed to a variable
in the host language, the host languages may cause escape errors.
"may cause escape errors" just says you are not escaping correctly
in your code (implying that this patch is not good enough and with
more effort we should be able to fix it to allow binaries), but the
problem is the host languages may not support binaries
(specifically, anything with a NUL in it) at all, which is
fundamentally unfixable, in which case, rejecting is the only
sensible choice.
... because the host language may not support a NUL in the variables
of its string type.
I agree. But host language not only support NUL but also some Non-ASCII
character and Non-UTF-8 code:
$ git hash-object a.out -w | xargs git update-ref refs/myblobs/aoutblob
$ git for-each-ref --format="name=%(raw)" refs/myblobs/aoutblob
--python | python2
File "<stdin>", line 1
SyntaxError: Non-ASCII character '\x8b' in file <stdin> on line 2, but
no encoding declared;
see http://python.org/dev/peps/pep-0263/ for details
$ git for-each-ref --format="name=%(raw)" refs/myblobs/aoutblob
--python |python3
SyntaxError: Non-UTF-8 code starting with '\x8b' in file <stdin> on
line 2, but no encoding declared;
see http://python.org/dev/peps/pep-0263/ for details
quoted
+The raw data in a object is `raw`, For commit and tag objects, `raw` contain
s/contain/contains/, but more importantly, as we are not introducing
%(header), I do not see why we want to talk about its details. For
commits and tags, just like for trees and blobs, 'raw' is the raw
data in the object, so beyond "The raw data of a object is %(raw)",
I do not think there is anything to talk about.
Doesn't this conflict with your own zh/ref-filter-atom-type topic?
Shouldn't one build on top of the other?
Or did we find something fundamentally broken about the other topic
to make us retract it that I do not remember?
Thanks.
I am waiting for zh/ref-filter-atom-type to be merged into master. But it
hasn't happened yet. But if I want to base the current topic on
zh/ref-filter-atom-type, GGG will send past patches (zh/ref-filter-atom-type)
repeatedly. If necessary, I will submit the current branch based on
zh/ref-filter-atom-type.
Thanks.
--
ZheNing Hu
From: Felipe Contreras <hidden> Date: 2021-05-31 17:20:12
ZheNing Hu wrote:
Felipe Contreras [off-list ref] 于2021年5月29日周六 下午11:24写道:
quoted
Phillip Wood wrote:
quoted
On 27/05/2021 17:36, Felipe Contreras wrote:
quoted
ZheNing Hu via GitGitGadget wrote:
[...]
quoted
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
Why void *? We can delcare as char *.
If you look at how this function is used you'll see
int (*cmp_fn)(const void *, const void *, size_t);
cmp_fn = s->sort_flags & REF_SORTING_ICASE
? memcasecmp : memcmp;
Yeah, but why?
We know we are comparing two char *. Presumably the reason is that
memcmp and memcasecmp use void *, but that could be remedied with:
cmp_fn = (int (*)(const char *, const char *, size_t))memcmp;
That way the same cmp_fn could be used for the two cases.
Either way I don't care particularly much. It also could be possible to
use void * and do the casting in tolower().
I agree with Phillip's point of view here:
It would be better for memcasecmp and memcmp to be consistent.
Fair enough.
static int memcasecmp(const void *vs1, const void *vs2, size_t n)
{
- size_t i;
- const char *s1 = (const char *)vs1;
- const char *s2 = (const char *)vs2;
-
- for (i = 0; i < n; i++) {
- unsigned char u1 = s1[i];
- unsigned char u2 = s2[i];
- int U1 = toupper (u1);
- int U2 = toupper (u2);
- int diff = (UCHAR_MAX <= INT_MAX ? U1 - U2
- : U1 < U2 ? -1 : U2 < U1);
+ const char *s1 = (const void *)vs1;
+ const char *s2 = (const void *)vs2;
vs1 is already a const void *, and there's not much point in adding
another line:
const char *s1 = vs1, *s2 = vs2;
Cheers.
--
Felipe Contreras
From: Junio C Hamano <hidden> Date: 2021-06-01 08:55:01
ZheNing Hu [off-list ref] writes:
quoted
Doesn't this conflict with your own zh/ref-filter-atom-type topic?
Shouldn't one build on top of the other?
Or did we find something fundamentally broken about the other topic
to make us retract it that I do not remember?
Thanks.
I am waiting for zh/ref-filter-atom-type to be merged into master. But it
As you sent this that conflicts with it, clearly you are doing
something else that conflicts with it _without waiting_ ;-).
hasn't happened yet. But if I want to base the current topic on
zh/ref-filter-atom-type, GGG will send past patches (zh/ref-filter-atom-type)
repeatedly.
I thought GGG lets you say "this is based on that other branch, not
on the 'master' branch" to solve that exact issue?
Is NUL treated the same as a whitespace letter for the purpose of
determining if a line is empty? WHY?
Well, there seems to be no correction here. But is it true that memory
like "\0abc" is considered empty?
That sample has 'a' or 'b' or 'c' that are clearly not part of an
"empty" string and irrelevant. After all, a string " abc" is not
treated as empty in the original implementation, either.
You are treating a block of memory with e.g. " \000 " (SP NUL SP) as
an "empty line" just like you do for " " (SP SP SP), but I think we
should treat it more like " \001 " or " \007 ", i.e. not an empty
string at all.
From: ZheNing Hu <hidden> Date: 2021-06-01 11:00:23
Junio C Hamano [off-list ref] 于2021年6月1日周二 下午4:54写道:
ZheNing Hu [off-list ref] writes:
quoted
quoted
Doesn't this conflict with your own zh/ref-filter-atom-type topic?
Shouldn't one build on top of the other?
Or did we find something fundamentally broken about the other topic
to make us retract it that I do not remember?
Thanks.
I am waiting for zh/ref-filter-atom-type to be merged into master. But it
As you sent this that conflicts with it, clearly you are doing
something else that conflicts with it _without waiting_ ;-).
OK.
quoted
hasn't happened yet. But if I want to base the current topic on
zh/ref-filter-atom-type, GGG will send past patches (zh/ref-filter-atom-type)
repeatedly.
I thought GGG lets you say "this is based on that other branch, not
on the 'master' branch" to solve that exact issue?
From: ZheNing Hu <hidden> Date: 2021-06-01 11:06:13
Junio C Hamano [off-list ref] 于2021年6月1日周二 下午5:54写道:
quoted
Well, there seems to be no correction here. But is it true that memory
like "\0abc" is considered empty?
That sample has 'a' or 'b' or 'c' that are clearly not part of an
"empty" string and irrelevant. After all, a string " abc" is not
treated as empty in the original implementation, either.
In other words, we still need to look at each character of strbuf,
instead of stopping at NUL.
You are treating a block of memory with e.g. " \000 " (SP NUL SP) as
an "empty line" just like you do for " " (SP SP SP), but I think we
should treat it more like " \001 " or " \007 ", i.e. not an empty
string at all.
OK. I understand it now: " \001 " is It’s like a block of space, but it’s
not truly "empty", "SP NUL SP" is same too, So the complete definition of
"empty" here should be: All characters are SP which do not contain NUL
or other characters.
Thanks.
--
ZheNing Hu
From: Johannes Schindelin <hidden> Date: 2021-06-01 13:48:52
Hi,
On Tue, 1 Jun 2021, ZheNing Hu wrote:
Junio C Hamano [off-list ref] 于2021年6月1日周二 下午4:54写道:
quoted
ZheNing Hu [off-list ref] writes:
quoted
[...] But if I want to base the current topic on
zh/ref-filter-atom-type, GGG will send past patches
(zh/ref-filter-atom-type) repeatedly.
I thought GGG lets you say "this is based on that other branch, not on
the 'master' branch" to solve that exact issue?
I'm not sure...I will try it after I rebasing this topic to
zh/ref-filter-atom-type.
Yes, it should be possible to rebase your patch on top of one of the
[a-z][a-z]/* patches Junio publishes at https://github.com/gitster/git
(and which get mirrored automatically to
https://github.com/gitgitgadget/git via a scheduled Azure Pipeline), and
then to change the PR base (simply click the `Edit` button next to the PR
title, as if you wanted to edit the title, and you can also change the
base branch).
https://github.com/gitgitgadget/git/pull/870 seems to be based on
jc/diffcore-rotate all right.
If you are talking about including Junio's patch in v5
(https://lore.kernel.org/git/fb4bfd0f8b162e51e71711fe5503ca684f980d58.1613480198.git.gitgitgadget@gmail.com/#r),
I _think_ that there might have been the simple problem of
jc/diffcore-rotate having been force-pushed just before you sent v5, and
therefore you had a stale branch.
To prevent things like that, it is a good idea to set the upstream of your
local branch accordingly (in this instance, `git branch
--set-upstream-to=gitgitgadget/jc/diffcore-rotate`) and ensure to `git
pull --rebase` before force-pushing and submitting.
Ciao,
Dscho