Skip to contentSkip to conversation

cat-file: reuse ref-filter logic

PATCH In review ZheNing Hu via GitGitGadget 6 participants 39 messages Jul 1, 2021 – Jul 5, 2021 Documentation

lore
Share & tools
lore
#1 lore raw

PATCH 1/15 ref-filter: add obj-type check in grab contents

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

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.

---
 ref-filter.c | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)
ref-filter.c +15 -9
index 4db0e40ff4c..5cee6512fba 100644
@@ -1356,11 +1356,12 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size
 }
 
 /* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct expand_data *data)
 {
 	int i;
 	const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
 	size_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
+	void *buf = data->content;
 
 	for (i = 0; i < used_atom_cnt; i++) {
 		struct used_atom *atom = &used_atom[i];
@@ -1371,10 +1372,13 @@ 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 ((data->type != OBJ_TAG &&
+		     data->type != OBJ_COMMIT) ||
+		    (strcmp(name, "body") &&
+		     !starts_with(name, "subject") &&
+		     !starts_with(name, "trailers") &&
+		     !starts_with(name, "contents")))
 			continue;
 		if (!subpos)
 			find_subpos(buf,
@@ -1438,17 +1442,19 @@ 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;
+
 	switch (obj->type) {
 	case OBJ_TAG:
 		grab_tag_values(val, deref, obj);
-		grab_sub_body_contents(val, deref, buf);
+		grab_sub_body_contents(val, deref, data);
 		grab_person("tagger", val, deref, buf);
 		break;
 	case OBJ_COMMIT:
 		grab_commit_values(val, deref, obj);
-		grab_sub_body_contents(val, deref, buf);
+		grab_sub_body_contents(val, deref, data);
 		grab_person("author", val, deref, buf);
 		grab_person("committer", val, deref, buf);
 		break;
@@ -1678,7 +1684,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
 			return strbuf_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);
Signature · 1 line
gitgitgadget
#0 lore raw

cover cat-file: reuse ref-filter logic

This patch series make cat-file reuse ref-filter logic.

Change from last version:

1. Amend part of the description of git for-each-ref.txt.
2. Modify commit messages about why cat-file reject some atoms.
3. Resolve conflicts with the 'seen' branch, rebase this patch on (bb9a3a8f77 Merge branch 'zh/cat-file-batch-fix' into jch).

ZheNing Hu (15):
[GSOC] ref-filter: add obj-type check in grab contents [GSOC] ref-filter: add %(raw) atom
[GSOC] ref-filter: --format=%(raw) re-support --perl [GSOC] ref-filter: use non-const ref_format in *_atom_parser() [GSOC] ref-filter: add %(rest) atom
[GSOC] ref-filter: pass get_object() return value to their callers [GSOC] ref-filter: introduce free_ref_array_item_value() function [GSOC] ref-filter: add cat_file_mode in struct ref_format [GSOC] ref-filter: modify the error message and value in get_object [GSOC] cat-file: add has_object_file() check
[GSOC] cat-file: change batch_objects parameter name [GSOC] cat-file: reuse ref-filter logic
[GSOC] cat-file: reuse err buf in batch_object_write() [GSOC] cat-file: re-implement --textconv, --filters options [GSOC] ref-filter: remove grab_oid() function

 Documentation/git-cat-file.txt     |   6 +
 Documentation/git-for-each-ref.txt |   9 +
 builtin/cat-file.c                 | 275 ++++++----------------
 builtin/tag.c                      |   2 +-
 quote.c                            |  17 ++
 quote.h                            |   1 +
 ref-filter.c                       | 357 ++++++++++++++++++++++-------
 ref-filter.h                       |  14 +-
 t/t1006-cat-file.sh                | 251 ++++++++++++++++++++
 t/t3203-branch-output.sh           |   4 +
 t/t6300-for-each-ref.sh            | 235 +++++++++++++++++++
 t/t6301-for-each-ref-errors.sh     |   2 +-
 t/t7004-tag.sh                     |   4 +
 t/t7030-verify-tag.sh              |   4 +
 14 files changed, 887 insertions(+), 294 deletions(-)

base-commit: bb9a3a8f77e13b7a414cf64bf294c3f59f2444c9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-989%2Fadlternative%2Fcat-file-batch-refactor-rebase-version-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-989/adlternative/cat-file-batch-refactor-rebase-version-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/989

Signature · 1 line
gitgitgadget
#5 lore raw

PATCH 3/15 ref-filter: --format=%(raw) re-support --perl

From: ZheNing Hu <[email protected]>

Because the perl language can handle binary data correctly, add the function perl_quote_buf_with_len(), which can specify the length of the data and prevent the data from being truncated at '\0' to help --format="%(raw)" re-support --perl.

---
 Documentation/git-for-each-ref.txt |  4 ++--
 quote.c                            | 17 +++++++++++++++++
 quote.h                            |  1 +
 ref-filter.c                       | 15 +++++++++++----
 t/t6300-for-each-ref.sh            | 19 +++++++++++++++++--
 5 files changed, 48 insertions(+), 8 deletions(-)
Documentation/git-for-each-ref.txt +2 -2
index cbb6f87d13f..6da899c6296 100644
@@ -241,8 +241,8 @@ raw:size::
 	The raw data size of the object.
 
 Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,
-`--perl` because such language may not support arbitrary binary data in their
-string variable type.
+because such language may not support arbitrary binary data in their string
+variable type.
 
 The message in a commit or a tag object is `contents`, from which
 `contents:<part>` can be used to extract various parts out of:
quote.c +17 -0
index 8a3a5e39eb1..26719d21d1e 100644
@@ -471,6 +471,23 @@ void perl_quote_buf(struct strbuf *sb, const char *src)
 	strbuf_addch(sb, sq);
 }
 
+void perl_quote_buf_with_len(struct strbuf *sb, const char *src, size_t len)
+{
+	const char sq = '\'';
+	const char bq = '\\';
+	const char *c = src;
+	const char *end = src + len;
+
+	strbuf_addch(sb, sq);
+	while (c != end) {
+		if (*c == sq || *c == bq)
+			strbuf_addch(sb, bq);
+		strbuf_addch(sb, *c);
+		c++;
+	}
+	strbuf_addch(sb, sq);
+}
+
 void python_quote_buf(struct strbuf *sb, const char *src)
 {
 	const char sq = '\'';
quote.h +1 -0
index 768cc6338e2..0fe69e264b0 100644
@@ -94,6 +94,7 @@ char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigne
 
 /* quoting as a string literal for other languages */
 void perl_quote_buf(struct strbuf *sb, const char *src);
+void perl_quote_buf_with_len(struct strbuf *sb, const char *src, size_t len);
 void python_quote_buf(struct strbuf *sb, const char *src);
 void tcl_quote_buf(struct strbuf *sb, const char *src);
 void basic_regex_quote_buf(struct strbuf *sb, const char *src);
ref-filter.c +11 -4
index 7822be90307..797b20ffa61 100644
@@ -742,7 +742,10 @@ static void quote_formatting(struct strbuf *s, const char *str, size_t len, int
 		sq_quote_buf(s, str);
 		break;
 	case QUOTE_PERL:
-		perl_quote_buf(s, str);
+		if (len != ATOM_VALUE_S_SIZE_INIT)
+			perl_quote_buf_with_len(s, str, len);
+		else
+			perl_quote_buf(s, str);
 		break;
 	case QUOTE_PYTHON:
 		python_quote_buf(s, str);
@@ -1006,10 +1009,14 @@ int verify_ref_format(struct ref_format *format)
 		at = parse_ref_filter_atom(format, sp + 2, ep, &err);
 		if (at < 0)
 			die("%s", err.buf);
-		if (format->quote_style && used_atom[at].atom_type == ATOM_RAW &&
-		    used_atom[at].u.raw_data.option == RAW_BARE)
+
+		if ((format->quote_style == QUOTE_PYTHON ||
+		     format->quote_style == QUOTE_SHELL ||
+		     format->quote_style == QUOTE_TCL) &&
+		     used_atom[at].atom_type == ATOM_RAW &&
+		     used_atom[at].u.raw_data.option == RAW_BARE)
 			die(_("--format=%.*s cannot be used with"
-			      "--python, --shell, --tcl, --perl"), (int)(ep - sp - 2), sp + 2);
+			      "--python, --shell, --tcl"), (int)(ep - sp - 2), sp + 2);
 		cp = ep + 1;
 
 		if (skip_prefix(used_atom[at].name, "color:", &color))
t/t6300-for-each-ref.sh +17 -2
index 18554f62d94..0b66e743c58 100755
@@ -915,8 +915,23 @@ test_expect_success '%(raw) with --tcl must fail' '
 	test_must_fail git for-each-ref --format="%(raw)" --tcl
 '
 
-test_expect_success '%(raw) with --perl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --perl
+test_expect_success '%(raw) with --perl' '
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl > actual &&
+	cmp blob1 actual &&
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl > actual &&
+	cmp blob3 actual &&
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl > actual &&
+	cmp blob8 actual &&
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl > actual &&
+	cmp one actual &&
+	git cat-file tree refs/mytrees/first > expected &&
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl > actual &&
+	cmp expected actual
 '
 
 test_expect_success '%(raw) with --shell must fail' '
Signature · 1 line
gitgitgadget
#2 lore raw

PATCH 2/15 ref-filter: add %(raw) atom

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

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 depends 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, we need to find a way to record the length of the atom_value's member s. Although strbuf can already record the string and its length, if we want to replace the type of atom_value's member s with strbuf, many places in ref-filter that are filled with dynamically allocated mermory in v->s are not easy to replace. At the same time, we need to check if v->s == NULL in populate_value(), and strbuf cannot easily distinguish NULL and empty strings, but c-style "const char *" can do it. So 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.

Note that --format=%(raw) cannot be used with --python, --shell, --tcl, and --perl because if the binary raw data is passed to a variable in such languages, these may not support arbitrary binary data in their string variable type.

---
 Documentation/git-for-each-ref.txt |   9 ++
 ref-filter.c                       | 139 +++++++++++++++----
 t/t6300-for-each-ref.sh            | 216 +++++++++++++++++++++++++++++
 3 files changed, 337 insertions(+), 27 deletions(-)
Documentation/git-for-each-ref.txt +9 -0
index 2ae2478de70..cbb6f87d13f 100644
@@ -235,6 +235,15 @@ 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 an object is `raw`.
+
+raw:size::
+	The raw data size of the object.
+
+Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,
+`--perl` because such language may not support arbitrary binary data in their
+string variable type.
+
 The message in a commit or a tag object is `contents`, from which
 `contents:<part>` can be used to extract various parts out of:
 
ref-filter.c +112 -27

293 lines not on this page · raw message

t/t6300-for-each-ref.sh +216 -0

258 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#9 lore raw

PATCH 5/15 ref-filter: add %(rest) atom

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

In order to let "cat-file --batch=%(rest)" use the ref-filter interface, add %(rest) atom for ref-filter. "git for-each-ref", "git branch", "git tag" and "git verify-tag" will reject %(rest) by default.

---
 ref-filter.c             | 21 +++++++++++++++++++++
 ref-filter.h             |  5 ++++-
 t/t3203-branch-output.sh |  4 ++++
 t/t6300-for-each-ref.sh  |  4 ++++
 t/t7004-tag.sh           |  4 ++++
 t/t7030-verify-tag.sh    |  4 ++++
 6 files changed, 41 insertions(+), 1 deletion(-)
ref-filter.c +21 -0
index d01a0266fb8..10c78de9cfa 100644
@@ -157,6 +157,7 @@ enum atom_type {
 	ATOM_IF,
 	ATOM_THEN,
 	ATOM_ELSE,
+	ATOM_REST,
 };
 
 /*
@@ -559,6 +560,15 @@ static int if_atom_parser(struct ref_format *format, struct used_atom *atom,
 	return 0;
 }
 
+static int rest_atom_parser(struct ref_format *format, struct used_atom *atom,
+			    const char *arg, struct strbuf *err)
+{
+	if (arg)
+		return strbuf_addf_ret(err, -1, _("%%(rest) does not take arguments"));
+	format->use_rest = 1;
+	return 0;
+}
+
 static int head_atom_parser(struct ref_format *format, struct used_atom *atom,
 			    const char *arg, struct strbuf *unused_err)
 {
@@ -615,6 +625,7 @@ static struct {
 	[ATOM_IF] = { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
 	[ATOM_THEN] = { "then", SOURCE_NONE },
 	[ATOM_ELSE] = { "else", SOURCE_NONE },
+	[ATOM_REST] = { "rest", SOURCE_NONE, FIELD_STR, rest_atom_parser },
 	/*
 	 * Please update $__git_ref_fieldlist in git-completion.bash
 	 * when you add new atoms
@@ -1010,6 +1021,9 @@ int verify_ref_format(struct ref_format *format)
 		if (at < 0)
 			die("%s", err.buf);
 
+		if (used_atom[at].atom_type == ATOM_REST)
+			die("this command reject atom %%(%.*s)", (int)(ep - sp - 2), sp + 2);
+
 		if ((format->quote_style == QUOTE_PYTHON ||
 		     format->quote_style == QUOTE_SHELL ||
 		     format->quote_style == QUOTE_TCL) &&
@@ -1927,6 +1941,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
 			v->handler = else_atom_handler;
 			v->s = xstrdup("");
 			continue;
+		} else if (atom_type == ATOM_REST) {
+			if (ref->rest)
+				v->s = xstrdup(ref->rest);
+			else
+				v->s = xstrdup("");
+			continue;
 		} else
 			continue;
 
@@ -2144,6 +2164,7 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
 
 	FLEX_ALLOC_STR(ref, refname, refname);
 	oidcpy(&ref->objectname, oid);
+	ref->rest = NULL;
 
 	return ref;
 }
ref-filter.h +4 -1
index 74fb423fc89..c15dee8d6b9 100644
@@ -38,6 +38,7 @@ struct ref_sorting {
 
 struct ref_array_item {
 	struct object_id objectname;
+	const char *rest;
 	int flag;
 	unsigned int kind;
 	const char *symref;
@@ -76,14 +77,16 @@ struct ref_format {
 	 * verify_ref_format() afterwards to finalize.
 	 */
 	const char *format;
+	const char *rest;
 	int quote_style;
+	int use_rest;
 	int use_color;
 
 	/* Internal state to ref-filter */
 	int need_color_reset_at_eol;
 };
 
-#define REF_FORMAT_INIT { NULL, 0, -1 }
+#define REF_FORMAT_INIT { .use_color = -1 }
 
 /*  Macros for checking --merged and --no-merged options */
 #define _OPT_MERGED_NO_MERGED(option, filter, h) \
t/t3203-branch-output.sh +4 -0
index 5325b9f67a0..6e94c6db7b5 100755
@@ -340,6 +340,10 @@ test_expect_success 'git branch --format option' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git branch with --format=%(rest) must fail' '
+	test_must_fail git branch --format="%(rest)" >actual
+'
+
 test_expect_success 'worktree colors correct' '
 	cat >expect <<-EOF &&
 	* <GREEN>(HEAD detached from fromtag)<RESET>
t/t6300-for-each-ref.sh +4 -0
index 0b66e743c58..6ca5c2cc19c 100755
@@ -1211,6 +1211,10 @@ test_expect_success 'basic atom: head contents:trailers' '
 	test_cmp expect actual.clean
 '
 
+test_expect_success 'basic atom: rest must fail' '
+	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
+'
+
 test_expect_success 'trailer parsing not fooled by --- line' '
 	git commit --allow-empty -F - <<-\EOF &&
 	this is the subject
t/t7004-tag.sh +4 -0
index 2f72c5c6883..082be85dffc 100755
@@ -1998,6 +1998,10 @@ test_expect_success '--format should list tags as per format given' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git tag -l with --format="%(rest)" must fail' '
+	test_must_fail git tag -l --format="%(rest)" "v1*"
+'
+
 test_expect_success "set up color tests" '
 	echo "<RED>v1.0<RESET>" >expect.color &&
 	echo "v1.0" >expect.bare &&
t/t7030-verify-tag.sh +4 -0
index 3cefde9602b..10faa645157 100755
@@ -194,6 +194,10 @@ test_expect_success GPG 'verifying tag with --format' '
 	test_cmp expect actual
 '
 
+test_expect_success GPG 'verifying tag with --format="%(rest)" must fail' '
+	test_must_fail git verify-tag --format="%(rest)" "fourth-signed"
+'
+
 test_expect_success GPG 'verifying a forged tag with --format should fail silently' '
 	test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged &&
 	test_must_be_empty actual-forged
Signature · 1 line
gitgitgadget
#8 lore raw

PATCH 4/15 ref-filter: use non-const ref_format in *_atom_parser()

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Use non-const ref_format in *_atom_parser(), which can help us modify the members of ref_format in *_atom_parser().

---
 builtin/tag.c |  2 +-
 ref-filter.c  | 44 ++++++++++++++++++++++----------------------
 ref-filter.h  |  4 ++--
 3 files changed, 25 insertions(+), 25 deletions(-)
builtin/tag.c +1 -1
index 82fcfc09824..452558ec957 100644
@@ -146,7 +146,7 @@ static int verify_tag(const char *name, const char *ref,
 		      const struct object_id *oid, void *cb_data)
 {
 	int flags;
-	const struct ref_format *format = cb_data;
+	struct ref_format *format = cb_data;
 	flags = GPG_VERIFY_VERBOSE;
 
 	if (format->format)
ref-filter.c +22 -22

164 lines not on this page · raw message

ref-filter.h +2 -2
index baf72a71896..74fb423fc89 100644
@@ -116,7 +116,7 @@ void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
 void ref_sorting_set_sort_flags_all(struct ref_sorting *sorting, unsigned int mask, int on);
 /*  Based on the given format and quote_style, fill the strbuf */
 int format_ref_array_item(struct ref_array_item *info,
-			  const struct ref_format *format,
+			  struct ref_format *format,
 			  struct strbuf *final_buf,
 			  struct strbuf *error_buf);
 /*  Parse a single sort specifier and add it to the list */
@@ -137,7 +137,7 @@ void setup_ref_filter_porcelain_msg(void);
  * name must be a fully qualified refname.
  */
 void pretty_print_ref(const char *name, const struct object_id *oid,
-		      const struct ref_format *format);
+		      struct ref_format *format);
 
 /*
  * Push a single ref onto the array; this can be used to construct your own
Signature · 1 line
gitgitgadget
#10 lore raw

PATCH 6/15 ref-filter: pass get_object() return value to their callers

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Because in the refactor of git cat-file --batch later, oid_object_info_extended() in get_object() will be used to obtain the info of an object with it's oid. When the object cannot be obtained in the git repository, cat-file --batch expects to output "<oid> missing" and continue the next oid query instead of letting Git exit. In other error conditions, Git should exit normally. So we can achieve this function by passing the return value of get_object().

---
 ref-filter.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)
ref-filter.c +11 -6
index 10c78de9cfa..e4988aa8a24 100644
@@ -1816,6 +1816,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
 {
 	struct object *obj;
 	int i;
+	int ret;
 	struct object_info empty = OBJECT_INFO_INIT;
 
 	CALLOC_ARRAY(ref->value, used_atom_cnt);
@@ -1972,8 +1973,9 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
 
 
 	oi.oid = ref->objectname;
-	if (get_object(ref, 0, &obj, &oi, err))
-		return -1;
+	ret = get_object(ref, 0, &obj, &oi, err);
+	if (ret)
+		return ret;
 
 	/*
 	 * If there is no atom that wants to know about tagged
@@ -2005,8 +2007,10 @@ static int get_ref_atom_value(struct ref_array_item *ref, int atom,
 			      struct atom_value **v, struct strbuf *err)
 {
 	if (!ref->value) {
-		if (populate_value(ref, err))
-			return -1;
+		int ret = populate_value(ref, err);
+
+		if (ret)
+			return ret;
 		fill_missing_values(ref->value);
 	}
 	*v = &ref->value[atom];
@@ -2580,6 +2584,7 @@ int format_ref_array_item(struct ref_array_item *info,
 {
 	const char *cp, *sp, *ep;
 	struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
+	int ret;
 
 	state.quote_style = format->quote_style;
 	push_stack_element(&state.stack);
@@ -2592,10 +2597,10 @@ int format_ref_array_item(struct ref_array_item *info,
 		if (cp < sp)
 			append_literal(cp, sp, &state);
 		pos = parse_ref_filter_atom(format, sp + 2, ep, error_buf);
-		if (pos < 0 || get_ref_atom_value(info, pos, &atomv, error_buf) ||
+		if (pos < 0 || (ret = get_ref_atom_value(info, pos, &atomv, error_buf)) ||
 		    atomv->handler(atomv, &state, error_buf)) {
 			pop_stack_element(&state.stack);
-			return -1;
+			return ret ? ret : -1;
 		}
 	}
 	if (*cp) {
Signature · 1 line
gitgitgadget
#12 lore raw

PATCH 8/15 ref-filter: add cat_file_mode in struct ref_format

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add cat_file_mode member in struct ref_format, when cat-file --batch use ref-filter logic later, it can help us reject atoms in verify_ref_format() which cat-file cannot use, e.g. %(refname), %(push), %(upstream)...

---
 ref-filter.c | 11 +++++++++--
 ref-filter.h |  1 +
 2 files changed, 10 insertions(+), 2 deletions(-)
ref-filter.c +9 -2
index 731e596eaa6..45122959eef 100644
@@ -1021,8 +1021,15 @@ int verify_ref_format(struct ref_format *format)
 		if (at < 0)
 			die("%s", err.buf);
 
-		if (used_atom[at].atom_type == ATOM_REST)
-			die("this command reject atom %%(%.*s)", (int)(ep - sp - 2), sp + 2);
+		if ((!format->cat_file_mode && used_atom[at].atom_type == ATOM_REST) ||
+		    (format->cat_file_mode && (used_atom[at].atom_type == ATOM_FLAG ||
+					       used_atom[at].atom_type == ATOM_HEAD ||
+					       used_atom[at].atom_type == ATOM_PUSH ||
+					       used_atom[at].atom_type == ATOM_REFNAME ||
+					       used_atom[at].atom_type == ATOM_SYMREF ||
+					       used_atom[at].atom_type == ATOM_UPSTREAM ||
+					       used_atom[at].atom_type == ATOM_WORKTREEPATH)))
+			die(_("this command reject atom %%(%.*s)"), (int)(ep - sp - 2), sp + 2);
 
 		if ((format->quote_style == QUOTE_PYTHON ||
 		     format->quote_style == QUOTE_SHELL ||
ref-filter.h +1 -0
index 44e6dc05ac2..053980a6a42 100644
@@ -78,6 +78,7 @@ struct ref_format {
 	 */
 	const char *format;
 	const char *rest;
+	int cat_file_mode;
 	int quote_style;
 	int use_rest;
 	int use_color;
Signature · 1 line
gitgitgadget
#11 lore raw

PATCH 7/15 ref-filter: introduce free_ref_array_item_value() function

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

When we use ref_array_item which is not dynamically allocated and want to free the space of its member "value" after the end of use, free_array_item() does not meet our needs, because it tries to free ref_array_item itself and its member "symref".

Introduce free_ref_array_item_value() for freeing ref_array_item value. It will be called internally by free_array_item(), and it will help cat-file --batch free ref_array_item's value memory later.

---
 ref-filter.c | 11 ++++++++---
 ref-filter.h |  2 ++
 2 files changed, 10 insertions(+), 3 deletions(-)
ref-filter.c +8 -3
index e4988aa8a24..731e596eaa6 100644
@@ -2291,16 +2291,21 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
 	return 0;
 }
 
-/*  Free memory allocated for a ref_array_item */
-static void free_array_item(struct ref_array_item *item)
+void free_ref_array_item_value(struct ref_array_item *item)
 {
-	free((char *)item->symref);
 	if (item->value) {
 		int i;
 		for (i = 0; i < used_atom_cnt; i++)
 			free((char *)item->value[i].s);
 		free(item->value);
 	}
+}
+
+/*  Free memory allocated for a ref_array_item */
+static void free_array_item(struct ref_array_item *item)
+{
+	free((char *)item->symref);
+	free_ref_array_item_value(item);
 	free(item);
 }
 
ref-filter.h +2 -0
index c15dee8d6b9..44e6dc05ac2 100644
@@ -111,6 +111,8 @@ struct ref_format {
 int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type);
 /*  Clear all memory allocated to ref_array */
 void ref_array_clear(struct ref_array *array);
+/* Free ref_array_item's value */
+void free_ref_array_item_value(struct ref_array_item *item);
 /*  Used to verify if the given format is correct and to parse out the used atoms */
 int verify_ref_format(struct ref_format *format);
 /*  Sort the given ref_array as per the ref_sorting provided */
Signature · 1 line
gitgitgadget
#18 lore raw

PATCH 9/15 ref-filter: modify the error message and value in get_object

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Let get_object() return 1 and print "<oid> missing" instead of returning -1 and printing "missing object <oid> for <refname>" if oid_object_info_extended() unable to find the data corresponding to oid. When cat-file --batch use ref-filter logic later it can help format_ref_array_item() just report that the object is missing without letting Git exit.

---
 ref-filter.c                   | 4 ++--
 t/t6301-for-each-ref-errors.sh | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)
ref-filter.c +2 -2
index 45122959eef..9ca3dd5557d 100644
@@ -1749,8 +1749,8 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
 	}
 	if (oid_object_info_extended(the_repository, &oi->oid, &oi->info,
 				     OBJECT_INFO_LOOKUP_REPLACE))
-		return strbuf_addf_ret(err, -1, _("missing object %s for %s"),
-				       oid_to_hex(&oi->oid), ref->refname);
+		return strbuf_addf_ret(err, 1, _("%s missing"),
+				       oid_to_hex(&oi->oid));
 	if (oi->info.disk_sizep && oi->disk_size < 0)
 		BUG("Object size is less than zero.");
 
t/t6301-for-each-ref-errors.sh +1 -1
index 40edf9dab53..3553f84a00c 100755
@@ -41,7 +41,7 @@ test_expect_success 'Missing objects are reported correctly' '
 	r=refs/heads/missing &&
 	echo $MISSING >.git/$r &&
 	test_when_finished "rm -f .git/$r" &&
-	echo "fatal: missing object $MISSING for $r" >missing-err &&
+	echo "fatal: $MISSING missing" >missing-err &&
 	test_must_fail git for-each-ref 2>err &&
 	test_cmp missing-err err &&
 	(
Signature · 1 line
gitgitgadget
#22 lore raw

PATCH 11/15 cat-file: change batch_objects parameter name

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Because later cat-file reuses ref-filter logic that will add parameter "const struct option *options" to batch_objects(), the two synonymous parameters of "opt" and "options" may confuse readers, so change batch_options parameter of batch_objects() from "opt" to "batch".

---
 builtin/cat-file.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
builtin/cat-file.c +10 -10
index 59a86412fd0..41d407638d5 100644
@@ -495,7 +495,7 @@ static int batch_unordered_packed(const struct object_id *oid,
 	return batch_unordered_object(oid, data);
 }
 
-static int batch_objects(struct batch_options *opt)
+static int batch_objects(struct batch_options *batch)
 {
 	struct strbuf input = STRBUF_INIT;
 	struct strbuf output = STRBUF_INIT;
@@ -503,8 +503,8 @@ static int batch_objects(struct batch_options *opt)
 	int save_warning;
 	int retval = 0;
 
-	if (!opt->format)
-		opt->format = "%(objectname) %(objecttype) %(objectsize)";
+	if (!batch->format)
+		batch->format = "%(objectname) %(objecttype) %(objectsize)";
 
 	/*
 	 * Expand once with our special mark_query flag, which will prime the
@@ -513,20 +513,20 @@ static int batch_objects(struct batch_options *opt)
 	 */
 	memset(&data, 0, sizeof(data));
 	data.mark_query = 1;
-	strbuf_expand(&output, opt->format, expand_format, &data);
+	strbuf_expand(&output, batch->format, expand_format, &data);
 	data.mark_query = 0;
 	strbuf_release(&output);
-	if (opt->cmdmode)
+	if (batch->cmdmode)
 		data.split_on_whitespace = 1;
 
 	/*
 	 * If we are printing out the object, then always fill in the type,
 	 * since we will want to decide whether or not to stream.
 	 */
-	if (opt->print_contents)
+	if (batch->print_contents)
 		data.info.typep = &data.type;
 
-	if (opt->all_objects) {
+	if (batch->all_objects) {
 		struct object_cb_data cb;
 		struct object_info empty = OBJECT_INFO_INIT;
 
@@ -536,11 +536,11 @@ static int batch_objects(struct batch_options *opt)
 		if (has_promisor_remote())
 			warning("This repository uses promisor remotes. Some objects may not be loaded.");
 
-		cb.opt = opt;
+		cb.opt = batch;
 		cb.expand = &data;
 		cb.scratch = &output;
 
-		if (opt->unordered) {
+		if (batch->unordered) {
 			struct oidset seen = OIDSET_INIT;
 
 			cb.seen = &seen;
@@ -590,7 +590,7 @@ static int batch_objects(struct batch_options *opt)
 			data.rest = p;
 		}
 
-		batch_one_object(input.buf, &output, opt, &data);
+		batch_one_object(input.buf, &output, batch, &data);
 	}
 
 	strbuf_release(&input);
Signature · 1 line
gitgitgadget
#23 lore raw

PATCH 12/15 cat-file: reuse ref-filter logic

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

In order to let cat-file use ref-filter logic, let's do the following:

1. Change the type of member format in struct batch_options to ref_format, we will pass it to ref-filter later.
2. Let batch_objects() add atoms to format, and use verify_ref_format() to check atoms.
3. Use format_ref_array_item() in batch_object_write() to get the formatted data corresponding to the object. If the return value of format_ref_array_item() is equals to zero, use batch_write() to print object data; else if the return value is less than zero, use die() to print the error message and exit; else if return value is greater than zero, only print the error message, but don't exit.
4. Use free_ref_array_item_value() to free ref_array_item's value.

Most of the atoms in for-each-ref --format are now supported, such as %(tree), %(parent), %(author), %(tagger), %(if), %(then), %(else), %(end). But these atoms will be rejected:
%(refname), %(symref), %(upstream), %(push), %(worktreepath), %(flag), %(HEAD), because these atoms are unique to those objects that pointed to by a ref, "for-each-ref"'s family can naturally use these atoms, but not all objects are pointed to be a ref, so "cat-file" will not be able to use them.

The performance for git cat-file --batch-all-objects --batch-check on the Git repository itself with performance testing tool hyperfine changes from 669.4 ms ± 31.1 ms to 1.134 s ± 0.063 s.

The performance for `git cat-file --batch-all-objects --batch

Quoted 1 line

/dev/null` on the Git repository itself with performance testing

tool time change from "27.37s user 0.29s system 98% cpu 28.089 total" to "33.69s user 1.54s system 87% cpu 40.258 total".

---
 Documentation/git-cat-file.txt |   6 +
 builtin/cat-file.c             | 242 ++++++-------------------------
 t/t1006-cat-file.sh            | 251 +++++++++++++++++++++++++++++++++
 3 files changed, 304 insertions(+), 195 deletions(-)
Documentation/git-cat-file.txt +6 -0
index 4eb0421b3fd..ef8ab952b2f 100644
@@ -226,6 +226,12 @@ newline. The available atoms are:
 	after that first run of whitespace (i.e., the "rest" of the
 	line) are output in place of the `%(rest)` atom.
 
+Note that most of the atoms in `for-each-ref --format` are now supported,
+such as `%(tree)`, `%(parent)`, `%(author)`, `%(tagger)`, `%(if)`,
+`%(then)`, `%(else)`, `%(end)`. But these atoms will be rejected:
+`%(refname)`, `%(symref)`, `%(upstream)`, `%(push)`, `%(worktreepath)`,
+`%(flag)`, `%(HEAD)`. See linkgit:git-for-each-ref[1].
+
 If no format is specified, the default format is `%(objectname)
 %(objecttype) %(objectsize)`.
 
builtin/cat-file.c +47 -195

340 lines not on this page · raw message

t/t1006-cat-file.sh +251 -0

256 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#19 lore raw

PATCH 10/15 cat-file: add has_object_file() check

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Use has_object_file() in batch_one_object() to check whether the input object exists. This can help us reject the missing oid when we let cat-file --batch use ref-filter logic later.

---
 builtin/cat-file.c | 7 +++++++
 1 file changed, 7 insertions(+)
builtin/cat-file.c +7 -0
index 243fe6844bc..59a86412fd0 100644
@@ -428,6 +428,13 @@ static void batch_one_object(const char *obj_name,
 		return;
 	}
 
+	if (!has_object_file(&data->oid)) {
+		printf("%s missing\n",
+		       obj_name ? obj_name : oid_to_hex(&data->oid));
+		fflush(stdout);
+		return;
+	}
+
 	batch_object_write(obj_name, scratch, opt, data);
 }
 
Signature · 1 line
gitgitgadget
#31 lore raw

PATCH 13/15 cat-file: reuse err buf in batch_object_write()

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Reuse the err buffer in batch_object_write(), as the buffer scratch does. This will reduce the overhead of multiple allocations of memory of the err buffer.

---
 builtin/cat-file.c | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)
builtin/cat-file.c +14 -8
index 5b163551fc6..dc604a9879d 100644
@@ -212,35 +212,36 @@ static void batch_write(struct batch_options *opt, const void *data, int len)
 
 static void batch_object_write(const char *obj_name,
 			       struct strbuf *scratch,
+			       struct strbuf *err,
 			       struct batch_options *opt,
 			       struct expand_data *data)
 {
 	int ret;
-	struct strbuf err = STRBUF_INIT;
 	struct ref_array_item item = { data->oid, data->rest };
 
 	strbuf_reset(scratch);
+	strbuf_reset(err);
 
-	ret = format_ref_array_item(&item, &opt->format, scratch, &err);
+	ret = format_ref_array_item(&item, &opt->format, scratch, err);
 	if (ret < 0)
-		die("%s\n", err.buf);
+		die("%s\n", err->buf);
 	if (ret) {
 		/* ret > 0 means when the object corresponding to oid
 		 * cannot be found in format_ref_array_item(), we only print
 		 * the error message.
 		 */
-		printf("%s\n", err.buf);
+		printf("%s\n", err->buf);
 		fflush(stdout);
 	} else {
 		strbuf_addch(scratch, '\n');
 		batch_write(opt, scratch->buf, scratch->len);
 	}
 	free_ref_array_item_value(&item);
-	strbuf_release(&err);
 }
 
 static void batch_one_object(const char *obj_name,
 			     struct strbuf *scratch,
+			     struct strbuf *err,
 			     struct batch_options *opt,
 			     struct expand_data *data)
 {
@@ -294,7 +295,7 @@ static void batch_one_object(const char *obj_name,
 		return;
 	}
 
-	batch_object_write(obj_name, scratch, opt, data);
+	batch_object_write(obj_name, scratch, err, opt, data);
 }
 
 struct object_cb_data {
@@ -302,13 +303,14 @@ struct object_cb_data {
 	struct expand_data *expand;
 	struct oidset *seen;
 	struct strbuf *scratch;
+	struct strbuf *err;
 };
 
 static int batch_object_cb(const struct object_id *oid, void *vdata)
 {
 	struct object_cb_data *data = vdata;
 	oidcpy(&data->expand->oid, oid);
-	batch_object_write(NULL, data->scratch, data->opt, data->expand);
+	batch_object_write(NULL, data->scratch, data->err, data->opt, data->expand);
 	return 0;
 }
 
@@ -364,6 +366,7 @@ static int batch_objects(struct batch_options *batch, const struct option *optio
 {
 	struct strbuf input = STRBUF_INIT;
 	struct strbuf output = STRBUF_INIT;
+	struct strbuf err = STRBUF_INIT;
 	struct strbuf format = STRBUF_INIT;
 	struct expand_data data;
 	int save_warning;
@@ -392,6 +395,7 @@ static int batch_objects(struct batch_options *batch, const struct option *optio
 		cb.opt = batch;
 		cb.expand = &data;
 		cb.scratch = &output;
+		cb.err = &err;
 
 		if (batch->unordered) {
 			struct oidset seen = OIDSET_INIT;
@@ -416,6 +420,7 @@ static int batch_objects(struct batch_options *batch, const struct option *optio
 
 		strbuf_release(&format);
 		strbuf_release(&output);
+		strbuf_release(&err);
 		return 0;
 	}
 
@@ -444,12 +449,13 @@ static int batch_objects(struct batch_options *batch, const struct option *optio
 			data.rest = p;
 		}
 
-		batch_one_object(input.buf, &output, batch, &data);
+		batch_one_object(input.buf, &output, &err, batch, &data);
 	}
 
 	strbuf_release(&format);
 	strbuf_release(&input);
 	strbuf_release(&output);
+	strbuf_release(&err);
 	warn_on_object_refname_ambiguity = save_warning;
 	return retval;
 }
Signature · 1 line
gitgitgadget
#32 lore raw

PATCH 14/15 cat-file: re-implement --textconv, --filters options

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

After cat-file reuses the ref-filter logic, we re-implement the functions of --textconv and --filters options.

Add members use_textconv and use_filters in struct ref_format, and use global variables use_filters and use_textconv in ref-filter.c, so that we can filter the content of the object in get_object(). Use actual_oi to record the real expand_data:
it may point to the original oi or the act_oi processed by textconv_object() or convert_to_working_tree(). grab_values() will grab the contents of actual_oi and grab_common_values() to grab the contents of origin oi, this ensures that %(objectsize) still uses the size of the unfiltered data.

In get_object(), we made an optimization: Firstly, get the size and type of the object instead of directly getting the object data. If using --textconv, after successfully obtaining the filtered object data, an extra oid_object_info_extended() will be skipped, which can reduce the cost of object data copy; If using --filter, the data of the object first will be getted first, and then convert_to_working_tree() will be used to get the filtered object data.

---
 builtin/cat-file.c |  6 +++++
 ref-filter.c       | 59 ++++++++++++++++++++++++++++++++++++++++++++--
 ref-filter.h       |  2 ++
 3 files changed, 65 insertions(+), 2 deletions(-)
builtin/cat-file.c +6 -0
index dc604a9879d..710085b7d72 100644
@@ -380,6 +380,12 @@ static int batch_objects(struct batch_options *batch, const struct option *optio
 	if (batch->print_contents)
 		strbuf_addstr(&format, "\n%(raw)");
 	batch->format.format = format.buf;
+
+	if (batch->cmdmode == 'c')
+		batch->format.use_textconv = 1;
+	else if (batch->cmdmode == 'w')
+		batch->format.use_filters = 1;
+
 	if (verify_ref_format(&batch->format))
 		usage_with_options(cat_file_usage, options);
 
ref-filter.c +57 -2

101 lines not on this page · raw message

ref-filter.h +2 -0
index 053980a6a42..497e3e93632 100644
@@ -80,6 +80,8 @@ struct ref_format {
 	const char *rest;
 	int cat_file_mode;
 	int quote_style;
+	int use_textconv;
+	int use_filters;
 	int use_rest;
 	int use_color;
 
Signature · 1 line
gitgitgadget
#38 lore raw

PATCH 15/15 ref-filter: remove grab_oid() function

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Because "atom_type == ATOM_OBJECTNAME" implies the condition of starts_with(name, "objectname"), "atom_type == ATOM_TREE" implies the condition of starts_with(name, "tree"), so the check for starts_with(name, field) in grab_oid() is redundant.

So Remove the grab_oid() from ref-filter, to reduce repeated check.

---
 ref-filter.c | 26 +++++++++-----------------
 1 file changed, 9 insertions(+), 17 deletions(-)
ref-filter.c +9 -17
index 427552b8108..55bff4ed9e7 100644
@@ -1071,16 +1071,6 @@ static const char *do_grab_oid(const char *field, const struct object_id *oid,
 	}
 }
 
-static int grab_oid(const char *name, const char *field, const struct object_id *oid,
-		    struct atom_value *v, struct used_atom *atom)
-{
-	if (starts_with(name, field)) {
-		v->s = xstrdup(do_grab_oid(field, oid, atom));
-		return 1;
-	}
-	return 0;
-}
-
 /* See grab_values */
 static void grab_common_values(struct atom_value *val, int deref, struct expand_data *oi)
 {
@@ -1106,8 +1096,9 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_
 			}
 		} else if (atom_type == ATOM_DELTABASE)
 			v->s = xstrdup(oid_to_hex(&oi->delta_base_oid));
-		else if (atom_type == ATOM_OBJECTNAME && deref)
-			grab_oid(name, "objectname", &oi->oid, v, &used_atom[i]);
+		else if (atom_type == ATOM_OBJECTNAME && deref) {
+			v->s = xstrdup(do_grab_oid("objectname", &oi->oid, &used_atom[i]));
+		}
 	}
 }
 
@@ -1148,9 +1139,10 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
 			continue;
 		if (deref)
 			name++;
-		if (atom_type == ATOM_TREE &&
-		    grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
+		if (atom_type == ATOM_TREE) {
+			v->s = xstrdup(do_grab_oid("tree", get_commit_tree_oid(commit), &used_atom[i]));
 			continue;
+		}
 		if (atom_type == ATOM_NUMPARENT) {
 			v->value = commit_list_count(commit->parents);
 			v->s = xstrfmt("%lu", (unsigned long)v->value);
@@ -1971,9 +1963,9 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
 				v->s = xstrdup(buf + 1);
 			}
 			continue;
-		} else if (!deref && atom_type == ATOM_OBJECTNAME &&
-			   grab_oid(name, "objectname", &ref->objectname, v, atom)) {
-				continue;
+		} else if (!deref && atom_type == ATOM_OBJECTNAME) {
+			   v->s = xstrdup(do_grab_oid("objectname", &ref->objectname, atom));
+			   continue;
 		} else if (atom_type == ATOM_HEAD) {
 			if (atom->u.head && !strcmp(ref->refname, atom->u.head))
 				v->s = xstrdup("*");
Signature · 1 line
gitgitgadget
#33 lore raw

Re: [PATCH 14/15] [GSOC] cat-file: re-implement --textconv, --filters options

In reply to ZheNing Hu via GitGitGadget

ref-filter.c static struct expand_data { · line 89

Quoted 10 lines · ZheNing Hu via GitGitGadget
ref-filter.c +3 -0
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "builtin.h"
 #include "cache.h"
 #include "parse-options.h"
@@ -84,6 +85,9 @@ static struct expand_data {
 	struct object_info info;
 } oi, oi_deref;
 
+int use_filters;
+int use_textconv;

With this commit in, "make sparse" dies like so:

SP ref-filter.c
ref-filter.c:88:5: error: symbol 'use_filters' was not declared. Should it be static? ref-filter.c:89:5: error: symbol 'use_textconv' was not declared. Should it be static?

#34 lore raw

Re: [PATCH 14/15] [GSOC] cat-file: re-implement --textconv, --filters options

In reply to ZheNing Hu via GitGitGadget
Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

After cat-file reuses the ref-filter logic, we re-implement the functions of --textconv and --filters options.

Add members use_textconv and use_filters in struct ref_format, and use global variables use_filters and use_textconv in ref-filter.c, so that we can filter the content of the object in get_object().

These global variables are problematic. The places that check them are quite isolated, and it makes me wonder why we cannot pass a format instance through the callchain down to get_object().

#35 lore raw

Re: [PATCH 14/15] [GSOC] cat-file: re-implement --textconv, --filters options

Quoted 8 lines · Junio C Hamano <[email protected]> 于2021年7月2日周五 上午4:11写道:
Quoted 5 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

After cat-file reuses the ref-filter logic, we re-implement the functions of --textconv and --filters options.

Quoted 8 lines
Quoted 4 lines

Add members use_textconv and use_filters in struct ref_format, and use global variables use_filters and use_textconv in ref-filter.c, so that we can filter the content of the object in get_object().

These global variables are problematic. The places that check them are quite isolated, and it makes me wonder why we cannot pass a format instance through the callchain down to get_object().

If we pass such two options In such a deep call chain, it seems not worth to do it.

format_ref_array_item()->get_ref_atom_value()->populate_value()-->get_object()

But if we puts them in struct ref_array_item, It may be a solution, but it may not be
very good.

Or just use "static" prefix?

Quoted 2 lines

Thanks.
--
ZheNing Hu

#3 lore raw

Re: [PATCH 02/15] [GSOC] ref-filter: add %(raw) atom

In reply to ZheNing Hu via GitGitGadget

ref-filter.c static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp · line 1401

Quoted 5 lines · ZheNing Hu via GitGitGadget
ref-filter.c +1 -0
@@ -1362,17 +1398,29 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp
 	const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
 	size_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
 	void *buf = data->content;
+	unsigned long buf_size = data->size;

Let's declare this.

ref-filter.c static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp · line 1414

Quoted 12 lines
ref-filter.c +2 -0
 	for (i = 0; i < used_atom_cnt; i++) {
 		struct used_atom *atom = &used_atom[i];
 		const char *name = atom->name;
 		struct atom_value *v = &val[i];
+		enum atom_type atom_type = atom->atom_type;
 
 		if (!!deref != (*name == '*'))
 			continue;
 		if (deref)
 			name++;
 
+		if (atom_type == ATOM_RAW) {

Here, where it's going to be used.

ref-filter.c static int compare_detached_head(struct ref_array_item *a, struct ref_array_item · line 2434

Quoted 12 lines
ref-filter.c +12 -0
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
+{
+	const char *s1 = vs1, *s2 = vs2;
+	const char *end = s1 + n;
+
+	for (; s1 < end; s1++, s2++) {
+		int diff = tolower(*s1) - tolower(*s2);
+		if (diff)
+			return diff;
+	}
+	return 0;
+}

I thought we had such a thing already, but clearly not...

t/t6300-for-each-ref.sh test_atom head parent:short=10 '' · line 134

Quoted 10 lines
t/t6300-for-each-ref.sh +2 -0
index 9e0214076b4..18554f62d94 100755
@@ -130,6 +130,8 @@ test_atom head parent:short=10 ''
 test_atom head numparent 0
 test_atom head object ''
 test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"

Extra stay line here, is the \n intentional?

#6 lore raw

Re: [PATCH 03/15] [GSOC] ref-filter: --format=%(raw) re-support --perl

In reply to ZheNing Hu via GitGitGadget
Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Because the perl language can handle binary data correctly, add the function perl_quote_buf_with_len(), which can specify the length of the data and prevent the data from being truncated at '\0' to help --format="%(raw)" re-support --perl.

Thanks!

Quoted 12 lines
t/t6300-for-each-ref.sh +10 -2
-test_expect_success '%(raw) with --perl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --perl
+test_expect_success '%(raw) with --perl' '
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl > actual &&
+	cmp blob1 actual &&
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl > actual &&
+	cmp blob3 actual &&
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl > actual &&
+	cmp blob8 actual &&

t/t6300-for-each-ref.sh test_expect_success '%(raw) with --tcl must fail' ' · line 937

Quoted 10 lines
t/t6300-for-each-ref.sh +7 -0
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl > actual &&
+	cmp one actual &&
+	git cat-file tree refs/mytrees/first > expected &&
+	git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl > actual &&
+	cmp expected actual
 '
 
 test_expect_success '%(raw) with --shell must fail' '

Nit: Formatting can be "| perl >actual", not "| perl > actual", like the usual style...

#13 lore raw

Re: [PATCH 08/15] [GSOC] ref-filter: add cat_file_mode in struct ref_format

In reply to ZheNing Hu via GitGitGadget
Quoted 24 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add cat_file_mode member in struct ref_format, when cat-file --batch use ref-filter logic later, it can help us reject atoms in verify_ref_format() which cat-file cannot use, e.g. %(refname), %(push), %(upstream)...

---
 ref-filter.c | 11 +++++++++--
 ref-filter.h |  1 +
 2 files changed, 10 insertions(+), 2 deletions(-)
ref-filter.c +0 -1
index 731e596eaa6..45122959eef 100644
@@ -1021,8 +1021,15 @@ int verify_ref_format(struct ref_format *format)
 		if (at < 0)
 			die("%s", err.buf);
 
-		if (used_atom[at].atom_type == ATOM_REST)

ref-filter.c int verify_ref_format(struct ref_format *format) · line 1032

Quoted 10 lines
ref-filter.c +9 -1
-			die("this command reject atom %%(%.*s)", (int)(ep - sp - 2), sp + 2);
+		if ((!format->cat_file_mode && used_atom[at].atom_type == ATOM_REST) ||
+		    (format->cat_file_mode && (used_atom[at].atom_type == ATOM_FLAG ||
+					       used_atom[at].atom_type == ATOM_HEAD ||
+					       used_atom[at].atom_type == ATOM_PUSH ||
+					       used_atom[at].atom_type == ATOM_REFNAME ||
+					       used_atom[at].atom_type == ATOM_SYMREF ||
+					       used_atom[at].atom_type == ATOM_UPSTREAM ||
+					       used_atom[at].atom_type == ATOM_WORKTREEPATH)))
+			die(_("this command reject atom %%(%.*s)"), (int)(ep - sp - 2), sp + 2);

Maybe it was brought up already, but those ATOM_* are mostly continuous where they're declared, Maybe worth making it so having a comment like:

    /* These need to be in order, used in verify_ref_format */

And then just doing:

used_atom[at].atom_type >= FIRST_OF_THOSE &&
used_atom[at].atom_type >= LAST_OF_THOSE

Maybe it's too magical and peeking under the hood of an enum...

#20 lore raw

Re: [PATCH 10/15] [GSOC] cat-file: add has_object_file() check

In reply to ZheNing Hu via GitGitGadget
Quoted 21 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Use has_object_file() in batch_one_object() to check whether the input object exists. This can help us reject the missing oid when we let cat-file --batch use ref-filter logic later.

---
 builtin/cat-file.c | 7 +++++++
 1 file changed, 7 insertions(+)
builtin/cat-file.c +0 -0
index 243fe6844bc..59a86412fd0 100644
@@ -428,6 +428,13 @@ static void batch_one_object(const char *obj_name,
 		return;
 	}

builtin/cat-file.c static void batch_one_object(const char *obj_name, · line 439

Quoted 10 lines
builtin/cat-file.c +7 -0
 
+	if (!has_object_file(&data->oid)) {
+		printf("%s missing\n",
+		       obj_name ? obj_name : oid_to_hex(&data->oid));
+		fflush(stdout);
+		return;
+	}
+
 	batch_object_write(obj_name, scratch, opt, data);
 }

In 12/15 the old "missing" is removed, so is this strictly redundant to what's in batch_object_write() for now, does it change anything?

#24 lore raw
In reply to ZheNing Hu via GitGitGadget
Quoted 33 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

In order to let cat-file use ref-filter logic, let's do the following:

1. Change the type of member format in struct batch_options to ref_format, we will pass it to ref-filter later.
2. Let batch_objects() add atoms to format, and use verify_ref_format() to check atoms.
3. Use format_ref_array_item() in batch_object_write() to get the formatted data corresponding to the object. If the return value of format_ref_array_item() is equals to zero, use batch_write() to print object data; else if the return value is less than zero, use die() to print the error message and exit; else if return value is greater than zero, only print the error message, but don't exit.
4. Use free_ref_array_item_value() to free ref_array_item's value.

Most of the atoms in for-each-ref --format are now supported, such as %(tree), %(parent), %(author), %(tagger), %(if), %(then), %(else), %(end). But these atoms will be rejected:
%(refname), %(symref), %(upstream), %(push), %(worktreepath), %(flag), %(HEAD), because these atoms are unique to those objects that pointed to by a ref, "for-each-ref"'s family can naturally use these atoms, but not all objects are pointed to be a ref, so "cat-file" will not be able to use them.

The performance for git cat-file --batch-all-objects --batch-check on the Git repository itself with performance testing tool hyperfine changes from 669.4 ms ± 31.1 ms to 1.134 s ± 0.063 s.

Quoted 4 lines

The performance for `git cat-file --batch-all-objects --batch

Quoted 1 line

/dev/null` on the Git repository itself with performance testing

tool time change from "27.37s user 0.29s system 98% cpu 28.089 total" to "33.69s user 1.54s system 87% cpu 40.258 total".

This new feature is really nice, but that's a really bad performance regression. A lot of software in the wild relies on "cat-file --batch" to be *the* performant interface to git for mass-extrction of object data.

That's in increase of ~70% and ~20%, respectively. Have you dug into (e.g. with a profiler) where we're now spending all this time?

#25 lore raw
Quoted 13 lines · Ævar Arnfjörð Bjarmason
Quoted 9 lines · ZheNing Hu via GitGitGadget

The performance for git cat-file --batch-all-objects --batch-check on the Git repository itself with performance testing tool hyperfine changes from 669.4 ms ± 31.1 ms to 1.134 s ± 0.063 s.

The performance for `git cat-file --batch-all-objects --batch

Quoted 1 line

/dev/null` on the Git repository itself with performance testing

tool time change from "27.37s user 0.29s system 98% cpu 28.089 total" to "33.69s user 1.54s system 87% cpu 40.258 total".

Quoted 7 lines

This new feature is really nice, but that's a really bad performance regression. A lot of software in the wild relies on "cat-file --batch" to be *the* performant interface to git for mass-extrction of object data.

That's in increase of ~70% and ~20%, respectively. Have you dug into (e.g. with a profiler) where we're now spending all this time?

Yeah, I think it's better to discuss this performance issue as part of the discussion of this series, instead of as part of the discussion of the blog posts.

#36 lore raw

Re: [PATCH 14/15] [GSOC] cat-file: re-implement --textconv, --filters options

In reply to ZheNing Hu
Quoted 6 lines · ZheNing Hu
Quoted 3 lines

These global variables are problematic. The places that check them are quite isolated, and it makes me wonder why we cannot pass a format instance through the callchain down to get_object().

If we pass such two options In such a deep call chain, it seems not worth to do it.

OK. After all, ref-filter.c has too many existing global variables (starting from the used_atom[] array) that makes it impossible to be a proper API that can be used to handle more than one invocations at once, so two more globals aren't making it any more hopeless than it currently is. If we wanted to make the API reentrant, we'd need a bigger change anyway.

Quoted 1 line

Or just use "static" prefix?

Yes, that is needed regardless to pass "make sparse".

TShanks.

#14 lore raw

Re: [PATCH 08/15] [GSOC] ref-filter: add cat_file_mode in struct ref_format

In reply to Ævar Arnfjörð Bjarmason
Quoted 14 lines · Ævar Arnfjörð Bjarmason

ref-filter.c int verify_ref_format(struct ref_format *format) · line 1032

Quoted 9 lines · ZheNing Hu via GitGitGadget
ref-filter.c +9 -0
+             if ((!format->cat_file_mode && used_atom[at].atom_type == ATOM_REST) ||
+                 (format->cat_file_mode && (used_atom[at].atom_type == ATOM_FLAG ||
+                                            used_atom[at].atom_type == ATOM_HEAD ||
+                                            used_atom[at].atom_type == ATOM_PUSH ||
+                                            used_atom[at].atom_type == ATOM_REFNAME ||
+                                            used_atom[at].atom_type == ATOM_SYMREF ||
+                                            used_atom[at].atom_type == ATOM_UPSTREAM ||
+                                            used_atom[at].atom_type == ATOM_WORKTREEPATH)))
+                     die(_("this command reject atom %%(%.*s)"), (int)(ep - sp - 2), sp + 2);

Maybe it was brought up already, but those ATOM_* are mostly continuous where they're declared, Maybe worth making it so having a comment like:

Quoted 8 lines
    /* These need to be in order, used in verify_ref_format */

And then just doing:

used_atom[at].atom_type >= FIRST_OF_THOSE &&
used_atom[at].atom_type >= LAST_OF_THOSE

Maybe it's too magical and peeking under the hood of an enum...

Aside from the potential maintenance burden of the atom_type >= ... && atom_type <= ... approach, another problem is that it increases cognitive load. The reader has to go reference the enum to fully understand the cases to which this code applies. On the other hand, the way the patch mentions the enumeration items explicitly, it is obvious at-a-glance to which cases the code applies. An additional downside of the suggestion is that the range specified by >= and <= may cause some readers to think that there is some sort of implicit relationship between the items in the range, which doesn't seem to be the case. So, I find the way it's done in the patch presently easier to comprehend.

#15 lore raw

Re: [PATCH 08/15] [GSOC] ref-filter: add cat_file_mode in struct ref_format

Quoted 12 lines · Eric Sunshine

Aside from the potential maintenance burden of the atom_type >= ... && atom_type <= ... approach, another problem is that it increases cognitive load. The reader has to go reference the enum to fully understand the cases to which this code applies. On the other hand, the way the patch mentions the enumeration items explicitly, it is obvious at-a-glance to which cases the code applies. An additional downside of the suggestion is that the range specified by >= and <= may cause some readers to think that there is some sort of implicit relationship between the items in the range, which doesn't seem to be the case. So, I find the way it's done in the patch presently easier to comprehend.

I agree that it's less cognitive load, but maybe it could be improved using a separate function like:

static int reject_atom(int cat_file_mode, enum atom_type atom_type)

{
    if (!cat_file_mode)
        return atom_type == ATOM_REST;

    /* cat_file_mode */
    switch (atom_type) {
    case ATOM_FLAG:
    case ATOM_HEAD:
    case ATOM_PUSH:
    case ATOM_REFNAME:
    case ATOM_SYMREF:
    case ATOM_UPSTREAM:
    case ATOM_WORKTREEPATH:
        return 1;
    default:
        return 0;
    }
}
#4 lore raw

Re: [PATCH 02/15] [GSOC] ref-filter: add %(raw) atom

In reply to Ævar Arnfjörð Bjarmason
Quoted 18 lines · Ævar Arnfjörð Bjarmason <[email protected]> 于2021年7月2日周五 下午9:27写道:

ref-filter.c static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp · line 1401

Quoted 5 lines · ZheNing Hu via GitGitGadget
ref-filter.c +1 -0
@@ -1362,17 +1398,29 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp
      const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
      size_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
      void *buf = data->content;
+     unsigned long buf_size = data->size;

Let's declare this.

Quoted 6 lines
ref-filter.c +1 -0
      for (i = 0; i < used_atom_cnt; i++) {
              struct used_atom *atom = &used_atom[i];
              const char *name = atom->name;
              struct atom_value *v = &val[i];
+             enum atom_type atom_type = atom->atom_type;
 
Quoted 9 lines

ref-filter.c static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp · line 1414

Quoted 6 lines
ref-filter.c +1 -0
              if (!!deref != (*name == '*'))
                      continue;
              if (deref)
                      name++;
 
+             if (atom_type == ATOM_RAW) {

Here, where it's going to be used.

Yeah, buf_size is only used here.

Quoted 4 lines
Quoted 4 lines
t/t6300-for-each-ref.sh +0 -0
index 9e0214076b4..18554f62d94 100755
Quoted 8 lines

t/t6300-for-each-ref.sh test_atom head parent:short=10 '' · line 134

Quoted 6 lines
t/t6300-for-each-ref.sh +2 -0
@@ -130,6 +130,8 @@ test_atom head parent:short=10 ''
 test_atom head numparent 0
 test_atom head object ''
 test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"

Extra stay line here, is the \n intentional?

I think so. \n is necessary.
In cmd_for_each_ref(), there have a extern "putchar('\n');" In cat_one_file(), write_or_die() output origin data of the object.

Thanks.
--
ZheNing Hu

#7 lore raw

Re: [PATCH 03/15] [GSOC] ref-filter: --format=%(raw) re-support --perl

In reply to Ævar Arnfjörð Bjarmason
Quoted 37 lines · Ævar Arnfjörð Bjarmason <[email protected]> 于2021年7月2日周五 下午9:30写道:
Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Because the perl language can handle binary data correctly, add the function perl_quote_buf_with_len(), which can specify the length of the data and prevent the data from being truncated at '\0' to help --format="%(raw)" re-support --perl.

Thanks!

Quoted 21 lines
t/t6300-for-each-ref.sh +17 -2
-test_expect_success '%(raw) with --perl must fail' '
-     test_must_fail git for-each-ref --format="%(raw)" --perl
+test_expect_success '%(raw) with --perl' '
+     git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl > actual &&
+     cmp blob1 actual &&
+     git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl > actual &&
+     cmp blob3 actual &&
+     git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl > actual &&
+     cmp blob8 actual &&
+     git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl > actual &&
+     cmp one actual &&
+     git cat-file tree refs/mytrees/first > expected &&
+     git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl > actual &&
+     cmp expected actual
 '
Quoted 5 lines
Quoted 1 line

test_expect_success '%(raw) with --shell must fail' '

Nit: Formatting can be "| perl >actual", not "| perl > actual", like the usual style...

Yeah, thanks.
--
ZheNing Hu

#21 lore raw

Re: [PATCH 10/15] [GSOC] cat-file: add has_object_file() check

In reply to Ævar Arnfjörð Bjarmason
Quoted 26 lines · Ævar Arnfjörð Bjarmason <[email protected]> 于2021年7月2日周五 下午9:34写道:
Quoted 22 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Use has_object_file() in batch_one_object() to check whether the input object exists. This can help us reject the missing oid when we let cat-file --batch use ref-filter logic later.

---
 builtin/cat-file.c | 7 +++++++
 1 file changed, 7 insertions(+)
builtin/cat-file.c +0 -0
index 243fe6844bc..59a86412fd0 100644
@@ -428,6 +428,13 @@ static void batch_one_object(const char *obj_name,
              return;
      }
 
Quoted 12 lines

builtin/cat-file.c static void batch_one_object(const char *obj_name, · line 439

Quoted 9 lines
builtin/cat-file.c +7 -0
+     if (!has_object_file(&data->oid)) {
+             printf("%s missing\n",
+                    obj_name ? obj_name : oid_to_hex(&data->oid));
+             fflush(stdout);
+             return;
+     }
+
      batch_object_write(obj_name, scratch, opt, data);
 }

In 12/15 the old "missing" is removed, so is this strictly redundant to what's in batch_object_write() for now, does it change anything?

Both of these commits are used to pass some tests for checking missing objects.

If we don't have this commit, "empty --batch-check notices missing object" in "t/t1006-cat-file.sh"
will fail;
If we don't have the commit "[GSOC] ref-filter: modify the error message and value in get_object",
"bogus OFS_DELTA in packfile" in "t/t5313-pack-bounds-checks.sh" will fail.

Thanks.
--
ZheNing Hu

#16 lore raw

Re: [PATCH 08/15] [GSOC] ref-filter: add cat_file_mode in struct ref_format

In reply to Christian Couder
Quoted 29 lines · Christian Couder <[email protected]> 于2021年7月3日周六 上午6:11写道:
Quoted 12 lines · Eric Sunshine

Aside from the potential maintenance burden of the atom_type >= ... && atom_type <= ... approach, another problem is that it increases cognitive load. The reader has to go reference the enum to fully understand the cases to which this code applies. On the other hand, the way the patch mentions the enumeration items explicitly, it is obvious at-a-glance to which cases the code applies. An additional downside of the suggestion is that the range specified by >= and <= may cause some readers to think that there is some sort of implicit relationship between the items in the range, which doesn't seem to be the case. So, I find the way it's done in the patch presently easier to comprehend.

I agree that it's less cognitive load, but maybe it could be improved using a separate function like:

static int reject_atom(int cat_file_mode, enum atom_type atom_type)

{
    if (!cat_file_mode)
        return atom_type == ATOM_REST;

    /* cat_file_mode */
    switch (atom_type) {
    case ATOM_FLAG:
    case ATOM_HEAD:
    case ATOM_PUSH:
    case ATOM_REFNAME:
Quoted 8 lines
    case ATOM_SYMREF:
    case ATOM_UPSTREAM:
    case ATOM_WORKTREEPATH:
        return 1;
    default:
        return 0;
    }
}

I agree. It is clearer to use a function to wrap origin reject atoms step.

Thanks.
--
ZheNIng Hu

#37 lore raw

Re: [PATCH 14/15] [GSOC] cat-file: re-implement --textconv, --filters options

In reply to Junio C Hamano
Quoted 10 lines · Junio C Hamano <[email protected]> 于2021年7月2日周五 下午11:27写道:
Quoted 6 lines · ZheNing Hu
Quoted 3 lines

These global variables are problematic. The places that check them are quite isolated, and it makes me wonder why we cannot pass a format instance through the callchain down to get_object().

If we pass such two options In such a deep call chain, it seems not worth to do it.

Quoted 7 lines

OK. After all, ref-filter.c has too many existing global variables (starting from the used_atom[] array) that makes it impossible to be a proper API that can be used to handle more than one invocations at once, so two more globals aren't making it any more hopeless than it currently is. If we wanted to make the API reentrant, we'd need a bigger change anyway.

Yes, Using global variables may make the code non-reentrant. Now I think because the "rest" is already in struct ref_array_item, we can emulate "rest", adding these two flags "use_textconv" and "use_filter" to struct ref_array_item. Then we can use them in get_object().

Quoted 5 lines
Quoted 1 line

Or just use "static" prefix?

Yes, that is needed regardless to pass "make sparse".

TShanks.

Thanks.
--
ZheNIng Hu

#26 lore raw
In reply to Ævar Arnfjörð Bjarmason
Quoted 38 lines · Ævar Arnfjörð Bjarmason <[email protected]> 于2021年7月2日周五 下午9:39写道:
Quoted 34 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

In order to let cat-file use ref-filter logic, let's do the following:

1. Change the type of member format in struct batch_options to ref_format, we will pass it to ref-filter later.
2. Let batch_objects() add atoms to format, and use verify_ref_format() to check atoms.
3. Use format_ref_array_item() in batch_object_write() to get the formatted data corresponding to the object. If the return value of format_ref_array_item() is equals to zero, use batch_write() to print object data; else if the return value is less than zero, use die() to print the error message and exit; else if return value is greater than zero, only print the error message, but don't exit.
4. Use free_ref_array_item_value() to free ref_array_item's value.

Most of the atoms in for-each-ref --format are now supported, such as %(tree), %(parent), %(author), %(tagger), %(if), %(then), %(else), %(end). But these atoms will be rejected:
%(refname), %(symref), %(upstream), %(push), %(worktreepath), %(flag), %(HEAD), because these atoms are unique to those objects that pointed to by a ref, "for-each-ref"'s family can naturally use these atoms, but not all objects are pointed to be a ref, so "cat-file" will not be able to use them.

The performance for git cat-file --batch-all-objects --batch-check on the Git repository itself with performance testing tool hyperfine changes from 669.4 ms ± 31.1 ms to 1.134 s ± 0.063 s.

The performance for `git cat-file --batch-all-objects --batch

Quoted 9 lines
Quoted 3 lines
Quoted 1 line

/dev/null` on the Git repository itself with performance testing

tool time change from "27.37s user 0.29s system 98% cpu 28.089 total" to "33.69s user 1.54s system 87% cpu 40.258 total".

This new feature is really nice, but that's a really bad performance regression. A lot of software in the wild relies on "cat-file --batch" to be *the* performant interface to git for mass-extrction of object data.

Thanks, this performance is indeed worrying.

Quoted 2 lines

That's in increase of ~70% and ~20%, respectively. Have you dug into (e.g. with a profiler) where we're now spending all this time?

See this two attachment about performance flame graph, oid_object_info_extended() in get_object() is the key to performance limitations.

--
ZheNing Hu

#27 lore raw
Quoted 5 lines · ZheNing Hu <[email protected]> 于2021年7月3日周六 下午7:45写道:

See this two attachment about performance flame graph, oid_object_info_extended() in get_object() is the key to performance limitations.

Well, from the current point of view, The time of oid_object_info_extended() before and after reconstruction is similar. But after refactoring, there are a lot
of additional detections in ref-filter.

E.g. append_literal() 1.68% append_atom 1.63% ...

--
ZheNing Hu

#28 lore raw
In reply to ZheNing Hu
Quoted 49 lines · ZheNing Hu
Quoted 38 lines · Ævar Arnfjörð Bjarmason <[email protected]> 于2021年7月2日周五 下午9:39写道:
Quoted 34 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

In order to let cat-file use ref-filter logic, let's do the following:

1. Change the type of member format in struct batch_options to ref_format, we will pass it to ref-filter later.
2. Let batch_objects() add atoms to format, and use verify_ref_format() to check atoms.
3. Use format_ref_array_item() in batch_object_write() to get the formatted data corresponding to the object. If the return value of format_ref_array_item() is equals to zero, use batch_write() to print object data; else if the return value is less than zero, use die() to print the error message and exit; else if return value is greater than zero, only print the error message, but don't exit.
4. Use free_ref_array_item_value() to free ref_array_item's value.

Most of the atoms in for-each-ref --format are now supported, such as %(tree), %(parent), %(author), %(tagger), %(if), %(then), %(else), %(end). But these atoms will be rejected:
%(refname), %(symref), %(upstream), %(push), %(worktreepath), %(flag), %(HEAD), because these atoms are unique to those objects that pointed to by a ref, "for-each-ref"'s family can naturally use these atoms, but not all objects are pointed to be a ref, so "cat-file" will not be able to use them.

The performance for git cat-file --batch-all-objects --batch-check on the Git repository itself with performance testing tool hyperfine changes from 669.4 ms ± 31.1 ms to 1.134 s ± 0.063 s.

The performance for `git cat-file --batch-all-objects --batch

Quoted 9 lines
Quoted 3 lines
Quoted 1 line

/dev/null` on the Git repository itself with performance testing

tool time change from "27.37s user 0.29s system 98% cpu 28.089 total" to "33.69s user 1.54s system 87% cpu 40.258 total".

This new feature is really nice, but that's a really bad performance regression. A lot of software in the wild relies on "cat-file --batch" to be *the* performant interface to git for mass-extrction of object data.

Quoted 8 lines

Thanks, this performance is indeed worrying.

Quoted 2 lines

That's in increase of ~70% and ~20%, respectively. Have you dug into (e.g. with a profiler) where we're now spending all this time?

See this two attachment about performance flame graph, oid_object_info_extended() in get_object() is the key to performance limitations.

Most of the problem, although this may not entirely fix the performance regression, is that you're either looking up everything twice now, or taking a much more expensive path.

I think using gprof is probably much more handy here. See [1. I did a git rev-list --all >rla and ran that piped into 'git cat-file --batch' with/without your pathces. Results:

    $ gprof ./git-master ./gmon-master.out | head -n 10
    Flat profile:
    Each sample counts as 0.01 seconds.
      %   cumulative   self              self     total           
     time   seconds   seconds    calls  ms/call  ms/call  name    
     14.29      0.02     0.02   475186     0.00     0.00  nth_packed_object_offset
     14.29      0.04     0.02   237835     0.00     0.00  hash_to_hex_algop_r
      7.14      0.05     0.01  5220425     0.00     0.00  hashcmp_algop
      7.14      0.06     0.01  4757120     0.00     0.00  hex2chr
      7.14      0.07     0.01  1732023     0.00     0.00  find_entry_ptr

And:

    $ gprof ./git-new ./gmon-new.out |head -n 10
    Flat profile:
    Each sample counts as 0.01 seconds.
      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
      7.32      0.06     0.06   764570     0.00     0.00  lookup_object
      7.32      0.12     0.06   237835     0.00     0.00  parse_commit_date
      4.88      0.16     0.04   712779     0.00     0.00  nth_packed_object_offset
      3.66      0.19     0.03   964574     0.00     0.00  bsearch_hash
      3.66      0.22     0.03   237835     0.00     0.00  grab_sub_body_contents

If you e.g. make lookup_object() simply die when it's called you'll see that before we don't call it at all, after your patch it's our #1 function.

Before when we have the simplest case of writing out an object this is our callstack:

    (gdb) bt
    #0  batch_write (opt=0x7fffffffde50, data=0x555555ab9470, len=52) at builtin/cat-file.c:298
    #1  0x000055555558b160 in batch_object_write (obj_name=0x55555597cef0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd8c0,
        opt=0x7fffffffde50, data=0x7fffffffd7f0) at builtin/cat-file.c:375
    #2  0x000055555558b36e in batch_one_object (obj_name=0x55555597cef0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd8c0, opt=0x7fffffffde50,
        data=0x7fffffffd7f0) at builtin/cat-file.c:431
    #3  0x000055555558b8ed in batch_objects (opt=0x7fffffffde50) at builtin/cat-file.c:588
    #4  0x000055555558c0d3 in cmd_cat_file (argc=0, argv=0x7fffffffe1e0, prefix=0x0) at builtin/cat-file.c:716
    #5  0x0000555555573adb in run_builtin (p=0x555555941870 <commands+240>, argc=2, argv=0x7fffffffe1e0) at git.c:461
    #6  0x0000555555573f00 in handle_builtin (argc=2, argv=0x7fffffffe1e0) at git.c:714
    #7  0x0000555555574182 in run_argv (argcp=0x7fffffffe08c, argv=0x7fffffffe080) at git.c:781
    #8  0x000055555557460f in cmd_main (argc=2, argv=0x7fffffffe1e0) at git.c:912
    #9  0x000055555565b508 in main (argc=3, argv=0x7fffffffe1d8) at common-main.c:52

After (well, here we're not even to writing it, just looking it up), the BUG() is my addition:

    (gdb) bt
    #0  BUG_fl (file=0x5555558ade71 "object.c", line=91, fmt=0x5555558ade6e "yo") at usage.c:290
    #1  0x00005555557441ca in lookup_object (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>) at object.c:91
    #2  0x000055555569dfc8 in lookup_commit (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>) at commit.c:62
    #3  0x00005555557445f5 in parse_object_buffer (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>, type=OBJ_COMMIT, size=342, buffer=0x555555ab48e0,
        eaten_p=0x7fffffffd36c) at object.c:215
    #4  0x0000555555785094 in get_object (ref=0x7fffffffd6b0, deref=0, obj=0x7fffffffd520, oi=0x555555975160 <oi>, err=0x7fffffffd860) at ref-filter.c:1803
    #5  0x0000555555785c99 in populate_value (ref=0x7fffffffd6b0, err=0x7fffffffd860) at ref-filter.c:2030
    #6  0x0000555555785d7b in get_ref_atom_value (ref=0x7fffffffd6b0, atom=0, v=0x7fffffffd628, err=0x7fffffffd860) at ref-filter.c:2064
    #7  0x000055555578742f in format_ref_array_item (info=0x7fffffffd6b0, format=0x7fffffffde30, final_buf=0x7fffffffd880, error_buf=0x7fffffffd860)
        at ref-filter.c:2659
    #8  0x000055555558ab1c in batch_object_write (obj_name=0x55555597e3f0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd880,
        err=0x7fffffffd860, opt=0x7fffffffde10, data=0x7fffffffd800) at builtin/cat-file.c:225
    #9  0x000055555558ade5 in batch_one_object (obj_name=0x55555597e3f0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd880, err=0x7fffffffd860,
        opt=0x7fffffffde10, data=0x7fffffffd800) at builtin/cat-file.c:298
    #10 0x000055555558b394 in batch_objects (batch=0x7fffffffde10, options=0x7fffffffd900) at builtin/cat-file.c:458
    #11 0x000055555558bbd5 in cmd_cat_file (argc=0, argv=0x7fffffffe1d0, prefix=0x0) at builtin/cat-file.c:585
    #12 0x0000555555573adb in run_builtin (p=0x555555942850 <commands+240>, argc=2, argv=0x7fffffffe1d0) at git.c:461
    #13 0x0000555555573f00 in handle_builtin (argc=2, argv=0x7fffffffe1d0) at git.c:714
    #14 0x0000555555574182 in run_argv (argcp=0x7fffffffe07c, argv=0x7fffffffe070) at git.c:781
    #15 0x000055555557460f in cmd_main (argc=2, argv=0x7fffffffe1d0) at git.c:912
    #16 0x000055555565afc1 in main (argc=3, argv=0x7fffffffe1c8) at common-main.c:52

I.e. before in batch_object_write() we could use a cheap path of doing oid_object_info_extended() and directly emitting the content. With your version we're all the way down to parse_object_buffer(). Meaning that we're going to be creating a "struct commit" or whatever if we're looking at a commit, just to print out the raw contents.

I think the best next step here is to add a t/perf/t1006-cat-file.sh test to stress these various cases, i.e. a plain --batch without a format, with format, with --batch-all-objects etc. Try to then run that on each of your commits against the preceding one and see if/when you have regressions.

Aside from any double-lookups etc, the problem is also that you're trying to handle a really general case (e.g. with textconv) in a codepath that needs to be really fast. If anything we should be inserting some more more optimization shortcuts for common cases into it. E.g. I was able to trivially speed up 'cat-file --batch-check' on "master" by hardcoding a path for our default format (patch at the end of this mail):

    # passed all 2 test(s)
    1..2
    Test                             origin/master     HEAD
    -------------------------------------------------------------------------
    1006.2: cat-file --batch-check   0.60(0.37+0.23)   0.35(0.33+0.02) -41.7%

Anything that needs to handle general format patching is going to be slower. I think /some/ performance regression if we're using something that's not just the current light strbuf_expand() probably can't be avoided, but we could/should try to make up the difference at least for the common case of --batch or --batch-check without --textconv and perhaps hardcode (and document that it's faster) a path for the default formats).

1. https://sourceware.org/binutils/docs/gprof/Output.html

builtin/cat-file.c +19 -8
index 5ebf13359e8..775b7dd1b01 100644
@@ -360,6 +360,11 @@ static void batch_object_write(const char *obj_name,
 			       struct batch_options *opt,
 			       struct expand_data *data)
 {
+	int default_format = !strcmp(opt->format, "%(objectname) %(objecttype) %(objectsize)");
+	struct strbuf type_name = STRBUF_INIT;
+	if (default_format)
+		data->info.type_name = &type_name;
+		
 	if (!data->skip_object_info &&
 	    oid_object_info_extended(the_repository, &data->oid, &data->info,
 				     OBJECT_INFO_LOOKUP_REPLACE) < 0) {
@@ -369,14 +374,20 @@ static void batch_object_write(const char *obj_name,
 		return;
 	}
 
-	strbuf_reset(scratch);
-	strbuf_expand(scratch, opt->format, expand_format, data);
-	strbuf_addch(scratch, '\n');
-	batch_write(opt, scratch->buf, scratch->len);
-
-	if (opt->print_contents) {
-		print_object_or_die(opt, data);
-		batch_write(opt, "\n", 1);
+	if (default_format && !opt->print_contents) {
+		fprintf(stdout, "%s %s %"PRIuMAX"\n", oid_to_hex(&data->oid),
+			data->info.type_name->buf, 
+			(uintmax_t)*data->info.sizep);
+	} else {
+		strbuf_reset(scratch);
+		strbuf_expand(scratch, opt->format, expand_format, data);
+		strbuf_addch(scratch, '\n');
+		batch_write(opt, scratch->buf, scratch->len);
+
+		if (opt->print_contents) {
+			print_object_or_die(opt, data);
+			batch_write(opt, "\n", 1);
+		}
 	}
 }
 
t/perf/p1006-cat-file.sh new file +16 -0
new file mode 100755
index 00000000000..a295d334715
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test_description='Basic sort performance tests'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_expect_success 'setup' '
+	git rev-list --all >rla
+'
+
+test_perf 'cat-file --batch-check' '
+	git cat-file --batch-check <rla
+'
+
+test_done
#29 lore raw
Quoted 4 lines · Ævar Arnfjörð Bjarmason <[email protected]> 于2021年7月3日周六 下午10:18写道:

Most of the problem, although this may not entirely fix the performance regression, is that you're either looking up everything twice now, or taking a much more expensive path.

Yeah, In get_object(), oid_object_info_extended() is called twice when we want print object's contents. The original intention is to reduce the call of oid_object_info_extended() once when using --textconv. Should we make --textconv and --filters have the same logic? In this way, without using --textconv and --filters, we can call oid_object_info_extended() only once.

Quoted 21 lines

I think using gprof is probably much more handy here. See [1. I did a git rev-list --all >rla and ran that piped into 'git cat-file --batch' with/without your pathces. Results:

    $ gprof ./git-master ./gmon-master.out | head -n 10
    Flat profile:
    Each sample counts as 0.01 seconds.
      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
     14.29      0.02     0.02   475186     0.00     0.00  nth_packed_object_offset
     14.29      0.04     0.02   237835     0.00     0.00  hash_to_hex_algop_r
      7.14      0.05     0.01  5220425     0.00     0.00  hashcmp_algop
      7.14      0.06     0.01  4757120     0.00     0.00  hex2chr
      7.14      0.07     0.01  1732023     0.00     0.00  find_entry_ptr

And:

    $ gprof ./git-new ./gmon-new.out |head -n 10
    Flat profile:
Quoted 9 lines
    Each sample counts as 0.01 seconds.
      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
      7.32      0.06     0.06   764570     0.00     0.00  lookup_object
      7.32      0.12     0.06   237835     0.00     0.00  parse_commit_date
      4.88      0.16     0.04   712779     0.00     0.00  nth_packed_object_offset
      3.66      0.19     0.03   964574     0.00     0.00  bsearch_hash
      3.66      0.22     0.03   237835     0.00     0.00  grab_sub_body_contents

It seems that lookup_object() took a lot of time with the patch of my version .

Quoted 38 lines

If you e.g. make lookup_object() simply die when it's called you'll see that before we don't call it at all, after your patch it's our #1 function.

Before when we have the simplest case of writing out an object this is our callstack:

    (gdb) bt
    #0  batch_write (opt=0x7fffffffde50, data=0x555555ab9470, len=52) at builtin/cat-file.c:298
    #1  0x000055555558b160 in batch_object_write (obj_name=0x55555597cef0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd8c0,
        opt=0x7fffffffde50, data=0x7fffffffd7f0) at builtin/cat-file.c:375
    #2  0x000055555558b36e in batch_one_object (obj_name=0x55555597cef0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd8c0, opt=0x7fffffffde50,
        data=0x7fffffffd7f0) at builtin/cat-file.c:431
    #3  0x000055555558b8ed in batch_objects (opt=0x7fffffffde50) at builtin/cat-file.c:588
    #4  0x000055555558c0d3 in cmd_cat_file (argc=0, argv=0x7fffffffe1e0, prefix=0x0) at builtin/cat-file.c:716
    #5  0x0000555555573adb in run_builtin (p=0x555555941870 <commands+240>, argc=2, argv=0x7fffffffe1e0) at git.c:461
    #6  0x0000555555573f00 in handle_builtin (argc=2, argv=0x7fffffffe1e0) at git.c:714
    #7  0x0000555555574182 in run_argv (argcp=0x7fffffffe08c, argv=0x7fffffffe080) at git.c:781
    #8  0x000055555557460f in cmd_main (argc=2, argv=0x7fffffffe1e0) at git.c:912
    #9  0x000055555565b508 in main (argc=3, argv=0x7fffffffe1d8) at common-main.c:52

After (well, here we're not even to writing it, just looking it up), the BUG() is my addition:

    (gdb) bt
    #0  BUG_fl (file=0x5555558ade71 "object.c", line=91, fmt=0x5555558ade6e "yo") at usage.c:290
    #1  0x00005555557441ca in lookup_object (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>) at object.c:91
    #2  0x000055555569dfc8 in lookup_commit (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>) at commit.c:62
    #3  0x00005555557445f5 in parse_object_buffer (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>, type=OBJ_COMMIT, size=342, buffer=0x555555ab48e0,
        eaten_p=0x7fffffffd36c) at object.c:215
    #4  0x0000555555785094 in get_object (ref=0x7fffffffd6b0, deref=0, obj=0x7fffffffd520, oi=0x555555975160 <oi>, err=0x7fffffffd860) at ref-filter.c:1803
    #5  0x0000555555785c99 in populate_value (ref=0x7fffffffd6b0, err=0x7fffffffd860) at ref-filter.c:2030
    #6  0x0000555555785d7b in get_ref_atom_value (ref=0x7fffffffd6b0, atom=0, v=0x7fffffffd628, err=0x7fffffffd860) at ref-filter.c:2064
    #7  0x000055555578742f in format_ref_array_item (info=0x7fffffffd6b0, format=0x7fffffffde30, final_buf=0x7fffffffd880, error_buf=0x7fffffffd860)
        at ref-filter.c:2659
    #8  0x000055555558ab1c in batch_object_write (obj_name=0x55555597e3f0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd880,
        err=0x7fffffffd860, opt=0x7fffffffde10, data=0x7fffffffd800) at builtin/cat-file.c:225
    #9  0x000055555558ade5 in batch_one_object (obj_name=0x55555597e3f0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd880, err=0x7fffffffd860,
Quoted 9 lines
        opt=0x7fffffffde10, data=0x7fffffffd800) at builtin/cat-file.c:298
    #10 0x000055555558b394 in batch_objects (batch=0x7fffffffde10, options=0x7fffffffd900) at builtin/cat-file.c:458
    #11 0x000055555558bbd5 in cmd_cat_file (argc=0, argv=0x7fffffffe1d0, prefix=0x0) at builtin/cat-file.c:585
    #12 0x0000555555573adb in run_builtin (p=0x555555942850 <commands+240>, argc=2, argv=0x7fffffffe1d0) at git.c:461
    #13 0x0000555555573f00 in handle_builtin (argc=2, argv=0x7fffffffe1d0) at git.c:714
    #14 0x0000555555574182 in run_argv (argcp=0x7fffffffe07c, argv=0x7fffffffe070) at git.c:781
    #15 0x000055555557460f in cmd_main (argc=2, argv=0x7fffffffe1d0) at git.c:912
    #16 0x000055555565afc1 in main (argc=3, argv=0x7fffffffe1c8) at common-main.c:52

It seems that the call stack is very deep after using my version.

Quoted 6 lines

I.e. before in batch_object_write() we could use a cheap path of doing oid_object_info_extended() and directly emitting the content. With your version we're all the way down to parse_object_buffer(). Meaning that we're going to be creating a "struct commit" or whatever if we're looking at a commit, just to print out the raw contents.

I agree that the logic in ref-filter is too complicated in order to be able to print
the structured object data.

Quoted 6 lines

I think the best next step here is to add a t/perf/t1006-cat-file.sh test to stress these various cases, i.e. a plain --batch without a format, with format, with --batch-all-objects etc. Try to then run that on each of your commits against the preceding one and see if/when you have regressions.

Make sence.

Quoted 2 lines

Aside from any double-lookups etc, the problem is also that you're trying to handle a really general case (e.g. with textconv) in a

Well, "--textconv" is a common situation? Here I may not be sure which scenarios where the upper application calls "git cat-file --batch" are the most common.

Quoted 12 lines

codepath that needs to be really fast. If anything we should be inserting some more more optimization shortcuts for common cases into it. E.g. I was able to trivially speed up 'cat-file --batch-check' on "master" by hardcoding a path for our default format (patch at the end of this mail):

    # passed all 2 test(s)
    1..2
    Test                             origin/master     HEAD
    -------------------------------------------------------------------------
    1006.2: cat-file --batch-check   0.60(0.37+0.23)   0.35(0.33+0.02) -41.7%
Quoted 8 lines

Anything that needs to handle general format patching is going to be slower. I think /some/ performance regression if we're using something that's not just the current light strbuf_expand() probably can't be avoided, but we could/should try to make up the difference at least for the common case of --batch or --batch-check without --textconv and perhaps hardcode (and document that it's faster) a path for the default formats).

Yeah, Like the hardcode in your patch may be a solution to the performance degradation. This will indeed help those upper-level applications that use the
common case.

Quoted 59 lines

1. https://sourceware.org/binutils/docs/gprof/Output.html

builtin/cat-file.c +19 -8
index 5ebf13359e8..775b7dd1b01 100644
@@ -360,6 +360,11 @@ static void batch_object_write(const char *obj_name,
                               struct batch_options *opt,
                               struct expand_data *data)
 {
+       int default_format = !strcmp(opt->format, "%(objectname) %(objecttype) %(objectsize)");
+       struct strbuf type_name = STRBUF_INIT;
+       if (default_format)
+               data->info.type_name = &type_name;
+
        if (!data->skip_object_info &&
            oid_object_info_extended(the_repository, &data->oid, &data->info,
                                     OBJECT_INFO_LOOKUP_REPLACE) < 0) {
@@ -369,14 +374,20 @@ static void batch_object_write(const char *obj_name,
                return;
        }
 
-       strbuf_reset(scratch);
-       strbuf_expand(scratch, opt->format, expand_format, data);
-       strbuf_addch(scratch, '\n');
-       batch_write(opt, scratch->buf, scratch->len);
-
-       if (opt->print_contents) {
-               print_object_or_die(opt, data);
-               batch_write(opt, "\n", 1);
+       if (default_format && !opt->print_contents) {
+               fprintf(stdout, "%s %s %"PRIuMAX"\n", oid_to_hex(&data->oid),
+                       data->info.type_name->buf,
+                       (uintmax_t)*data->info.sizep);
+       } else {
+               strbuf_reset(scratch);
+               strbuf_expand(scratch, opt->format, expand_format, data);
+               strbuf_addch(scratch, '\n');
+               batch_write(opt, scratch->buf, scratch->len);
+
+               if (opt->print_contents) {
+                       print_object_or_die(opt, data);
+                       batch_write(opt, "\n", 1);
+               }
        }
 }
 
t/perf/p1006-cat-file.sh new file +6 -0
new file mode 100755
index 00000000000..a295d334715
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test_description='Basic sort performance tests'
+. ./perf-lib.sh
+
+test_perf_default_repo

t/perf/p1006-cat-file.sh line 16

Quoted 10 lines
t/perf/p1006-cat-file.sh new file +10 -0
+
+test_expect_success 'setup' '
+       git rev-list --all >rla
+'
+
+test_perf 'cat-file --batch-check' '
+       git cat-file --batch-check <rla
+'
+
+test_done

Thanks.
--
ZheNing Hu

#17 lore raw

Re: [PATCH 08/15] [GSOC] ref-filter: add cat_file_mode in struct ref_format

In reply to ZheNing Hu
Quoted 39 lines · ZheNing Hu
Quoted 29 lines · Christian Couder <[email protected]> 于2021年7月3日周六 上午6:11写道:
Quoted 12 lines · Eric Sunshine

Aside from the potential maintenance burden of the atom_type >= ... && atom_type <= ... approach, another problem is that it increases cognitive load. The reader has to go reference the enum to fully understand the cases to which this code applies. On the other hand, the way the patch mentions the enumeration items explicitly, it is obvious at-a-glance to which cases the code applies. An additional downside of the suggestion is that the range specified by >= and <= may cause some readers to think that there is some sort of implicit relationship between the items in the range, which doesn't seem to be the case. So, I find the way it's done in the patch presently easier to comprehend.

I agree that it's less cognitive load, but maybe it could be improved using a separate function like:

static int reject_atom(int cat_file_mode, enum atom_type atom_type)

{
    if (!cat_file_mode)
        return atom_type == ATOM_REST;

    /* cat_file_mode */
    switch (atom_type) {
    case ATOM_FLAG:
    case ATOM_HEAD:
    case ATOM_PUSH:
    case ATOM_REFNAME:
Quoted 8 lines
    case ATOM_SYMREF:
    case ATOM_UPSTREAM:
    case ATOM_WORKTREEPATH:
        return 1;
    default:
        return 0;
    }
}
Quoted 3 lines

I agree. It is clearer to use a function to wrap origin reject atoms step.

Thanks.

Yeah I guess you're both right, I just found the dense giant if to be hard to read, but having a helper is even better.

#30 lore raw
In reply to ZheNing Hu
Quoted 103 lines · ZheNing Hu
Quoted 4 lines · Ævar Arnfjörð Bjarmason <[email protected]> 于2021年7月3日周六 下午10:18写道:

Most of the problem, although this may not entirely fix the performance regression, is that you're either looking up everything twice now, or taking a much more expensive path.

Yeah, In get_object(), oid_object_info_extended() is called twice when we want print object's contents. The original intention is to reduce the call of oid_object_info_extended() once when using --textconv. Should we make --textconv and --filters have the same logic? In this way, without using --textconv and --filters, we can call oid_object_info_extended() only once.

Quoted 21 lines

I think using gprof is probably much more handy here. See [1. I did a git rev-list --all >rla and ran that piped into 'git cat-file --batch' with/without your pathces. Results:

    $ gprof ./git-master ./gmon-master.out | head -n 10
    Flat profile:
    Each sample counts as 0.01 seconds.
      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
     14.29      0.02     0.02   475186     0.00     0.00  nth_packed_object_offset
     14.29      0.04     0.02   237835     0.00     0.00  hash_to_hex_algop_r
      7.14      0.05     0.01  5220425     0.00     0.00  hashcmp_algop
      7.14      0.06     0.01  4757120     0.00     0.00  hex2chr
      7.14      0.07     0.01  1732023     0.00     0.00  find_entry_ptr

And:

    $ gprof ./git-new ./gmon-new.out |head -n 10
    Flat profile:
Quoted 9 lines
    Each sample counts as 0.01 seconds.
      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
      7.32      0.06     0.06   764570     0.00     0.00  lookup_object
      7.32      0.12     0.06   237835     0.00     0.00  parse_commit_date
      4.88      0.16     0.04   712779     0.00     0.00  nth_packed_object_offset
      3.66      0.19     0.03   964574     0.00     0.00  bsearch_hash
      3.66      0.22     0.03   237835     0.00     0.00  grab_sub_body_contents

It seems that lookup_object() took a lot of time with the patch of my version .

Quoted 38 lines

If you e.g. make lookup_object() simply die when it's called you'll see that before we don't call it at all, after your patch it's our #1 function.

Before when we have the simplest case of writing out an object this is our callstack:

    (gdb) bt
    #0  batch_write (opt=0x7fffffffde50, data=0x555555ab9470, len=52) at builtin/cat-file.c:298
    #1  0x000055555558b160 in batch_object_write (obj_name=0x55555597cef0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd8c0,
        opt=0x7fffffffde50, data=0x7fffffffd7f0) at builtin/cat-file.c:375
    #2  0x000055555558b36e in batch_one_object (obj_name=0x55555597cef0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd8c0, opt=0x7fffffffde50,
        data=0x7fffffffd7f0) at builtin/cat-file.c:431
    #3  0x000055555558b8ed in batch_objects (opt=0x7fffffffde50) at builtin/cat-file.c:588
    #4  0x000055555558c0d3 in cmd_cat_file (argc=0, argv=0x7fffffffe1e0, prefix=0x0) at builtin/cat-file.c:716
    #5  0x0000555555573adb in run_builtin (p=0x555555941870 <commands+240>, argc=2, argv=0x7fffffffe1e0) at git.c:461
    #6  0x0000555555573f00 in handle_builtin (argc=2, argv=0x7fffffffe1e0) at git.c:714
    #7  0x0000555555574182 in run_argv (argcp=0x7fffffffe08c, argv=0x7fffffffe080) at git.c:781
    #8  0x000055555557460f in cmd_main (argc=2, argv=0x7fffffffe1e0) at git.c:912
    #9  0x000055555565b508 in main (argc=3, argv=0x7fffffffe1d8) at common-main.c:52

After (well, here we're not even to writing it, just looking it up), the BUG() is my addition:

    (gdb) bt
    #0  BUG_fl (file=0x5555558ade71 "object.c", line=91, fmt=0x5555558ade6e "yo") at usage.c:290
    #1  0x00005555557441ca in lookup_object (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>) at object.c:91
    #2  0x000055555569dfc8 in lookup_commit (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>) at commit.c:62
    #3  0x00005555557445f5 in parse_object_buffer (r=0x5555559755c0 <the_repo>, oid=0x555555975160 <oi>, type=OBJ_COMMIT, size=342, buffer=0x555555ab48e0,
        eaten_p=0x7fffffffd36c) at object.c:215
    #4  0x0000555555785094 in get_object (ref=0x7fffffffd6b0, deref=0, obj=0x7fffffffd520, oi=0x555555975160 <oi>, err=0x7fffffffd860) at ref-filter.c:1803
    #5  0x0000555555785c99 in populate_value (ref=0x7fffffffd6b0, err=0x7fffffffd860) at ref-filter.c:2030
    #6  0x0000555555785d7b in get_ref_atom_value (ref=0x7fffffffd6b0, atom=0, v=0x7fffffffd628, err=0x7fffffffd860) at ref-filter.c:2064
    #7  0x000055555578742f in format_ref_array_item (info=0x7fffffffd6b0, format=0x7fffffffde30, final_buf=0x7fffffffd880, error_buf=0x7fffffffd860)
        at ref-filter.c:2659
    #8  0x000055555558ab1c in batch_object_write (obj_name=0x55555597e3f0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd880,
        err=0x7fffffffd860, opt=0x7fffffffde10, data=0x7fffffffd800) at builtin/cat-file.c:225
    #9  0x000055555558ade5 in batch_one_object (obj_name=0x55555597e3f0 "504fe6b39f7747be6427f28d9ca97decf5e6cecf", scratch=0x7fffffffd880, err=0x7fffffffd860,
Quoted 9 lines
        opt=0x7fffffffde10, data=0x7fffffffd800) at builtin/cat-file.c:298
    #10 0x000055555558b394 in batch_objects (batch=0x7fffffffde10, options=0x7fffffffd900) at builtin/cat-file.c:458
    #11 0x000055555558bbd5 in cmd_cat_file (argc=0, argv=0x7fffffffe1d0, prefix=0x0) at builtin/cat-file.c:585
    #12 0x0000555555573adb in run_builtin (p=0x555555942850 <commands+240>, argc=2, argv=0x7fffffffe1d0) at git.c:461
    #13 0x0000555555573f00 in handle_builtin (argc=2, argv=0x7fffffffe1d0) at git.c:714
    #14 0x0000555555574182 in run_argv (argcp=0x7fffffffe07c, argv=0x7fffffffe070) at git.c:781
    #15 0x000055555557460f in cmd_main (argc=2, argv=0x7fffffffe1d0) at git.c:912
    #16 0x000055555565afc1 in main (argc=3, argv=0x7fffffffe1c8) at common-main.c:52

It seems that the call stack is very deep after using my version.

Quoted 6 lines

I.e. before in batch_object_write() we could use a cheap path of doing oid_object_info_extended() and directly emitting the content. With your version we're all the way down to parse_object_buffer(). Meaning that we're going to be creating a "struct commit" or whatever if we're looking at a commit, just to print out the raw contents.

Quoted 3 lines

I agree that the logic in ref-filter is too complicated in order to be able to print
the structured object data.

Well, maybe it isn't. I think the main problem with your performance is just looking things up again from the object store needlessly, and possibly redundantly copying things around. E.g. if you have a buffer with a commit object and you need to xstrdup() that, and xstrdup() the answer etc, it adds up.

Maybe if all that's gone the formatting will still slow things down, but I bet it's going to be to a much smaller extent.

Quoted 7 lines
Quoted 6 lines

I think the best next step here is to add a t/perf/t1006-cat-file.sh test to stress these various cases, i.e. a plain --batch without a format, with format, with --batch-all-objects etc. Try to then run that on each of your commits against the preceding one and see if/when you have regressions.

Quoted 8 lines

Make sence.

Quoted 2 lines

Aside from any double-lookups etc, the problem is also that you're trying to handle a really general case (e.g. with textconv) in a

Well, "--textconv" is a common situation? Here I may not be sure which scenarios where the upper application calls "git cat-file --batch" are the most common.

I think --textconv is much rarer. I think it's used for API purposes e.g. for people who are doing \n translation, or maybe for the likes of git-lfs (or was that --filters)? I'm not very familiar with it.

For what it's worth 'git cat-file --batch' is very performance sensitive in backend use-cases like e.g. gitlab.com's gitaly (which executes all git commands for you on that site). Anything that looks up .. any object pretty much .. is prining that object line to a persistent "cat-file --batch" process.

There was a case where some very small (unrelated to cat-file per-se) slowdown in that codepath ended up slowing down overall API requests for some calls by >100%, because it's executed in a tight loop. E.g. if you're showing N commits per page that might be N cat-file --batch calls.