@@ -235,6 +235,20 @@ and `date` to extract the named component. For email fields (`authoremail`,
without angle brackets, and `:localpart` to get the part before the `@` symbol
out of the trimmed email.
+The raw data in a object is `raw`, For commit and tag objects, `raw` contain
+`header` and `contents` two parts, `header` is structured part of raw data, it
+composed of "tree XXX", "parent YYY", etc lines in commits , or composed of
+"object OOO", "type TTT", etc lines in tags; `contents` is unstructured "free
+text" part of raw object data. For blob and tree objects, their raw data don't
+have `header` and `contents` parts.
+
+raw:size::
+ The raw data size of the object.
+
+Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,
+`--perl` because if our binary raw data is passed to a variable in the host language,
+the host languages may cause escape errors.
+
The message in a commit or a tag object is `contents`, from which
`contents:<part>` can be used to extract various parts out of:
@@ -138,6 +138,9 @@ static struct used_atom {
struct process_trailer_options trailer_opts;
unsigned int nlines;
} contents;
+ struct {
+ enum { RAW_BARE, RAW_LENGTH } option;
+ } raw_data;
struct {
cmp_status cmp_status;
const char *str;
@@ -370,6 +373,18 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato
return 0;
}
+static int raw_atom_parser(const struct ref_format *format, struct used_atom *atom,
+ const char *arg, struct strbuf *err)
+{
+ if (!arg)
+ atom->u.raw_data.option = RAW_BARE;
+ else if (!strcmp(arg, "size"))
+ atom->u.raw_data.option = RAW_LENGTH;
+ else
+ return strbuf_addf_ret(err, -1, _("unrecognized %%(raw) argument: %s"), arg);
+ return 0;
+}
+
static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
@@ -530,6 +545,7 @@ static struct {
{ "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
{ "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
{ "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser },
+ { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser },
{ "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
{ "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
{ "symref", SOURCE_NONE, FIELD_STR, refname_atom_parser },
@@ -564,12 +580,15 @@ struct ref_formatting_state {
struct atom_value {
const char *s;
+ size_t s_size;
int (*handler)(struct atom_value *atomv, struct ref_formatting_state *state,
struct strbuf *err);
uintmax_t value; /* used for sorting when not FIELD_STR */
struct used_atom *atom;
};
+#define ATOM_VALUE_S_SIZE_INIT (-1)
+
/*
* Used to parse format string and sort specifiers
*/
@@ -588,13 +607,6 @@ static int parse_ref_filter_atom(const struct ref_format *format,
return strbuf_addf_ret(err, -1, _("malformed field name: %.*s"),
(int)(ep-atom), atom);
- /* Do we have the atom already used elsewhere? */
- for (i = 0; i < used_atom_cnt; i++) {
- int len = strlen(used_atom[i].name);
- if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))
- return i;
- }
-
/*
* If the atom name has a colon, strip it and everything after
* it off - it specifies the format for this entry, and
@@ -604,6 +616,17 @@ static int parse_ref_filter_atom(const struct ref_format *format,
arg = memchr(sp, ':', ep - sp);
atom_len = (arg ? arg : ep) - sp;
+ if (format->quote_style && !strncmp(sp, "raw", 3) && !arg)
+ return strbuf_addf_ret(err, -1, _("--format=%.*s cannot be used with"
+ "--python, --shell, --tcl, --perl"), (int)(ep-atom), atom);
+
+ /* Do we have the atom already used elsewhere? */
+ for (i = 0; i < used_atom_cnt; i++) {
+ int len = strlen(used_atom[i].name);
+ if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))
+ return i;
+ }
+
/* Is the atom a valid one? */
for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
int len = strlen(valid_atom[i].name);
@@ -652,11 +675,14 @@ static int parse_ref_filter_atom(const struct ref_format *format,
return at;
}
-static void quote_formatting(struct strbuf *s, const char *str, int quote_style)
+static void quote_formatting(struct strbuf *s, const char *str, size_t len, int quote_style)
{
switch (quote_style) {
case QUOTE_NONE:
- strbuf_addstr(s, str);
+ if (len != ATOM_VALUE_S_SIZE_INIT)
+ strbuf_add(s, str, len);
+ else
+ strbuf_addstr(s, str);
break;
case QUOTE_SHELL:
sq_quote_buf(s, str);
@@ -683,9 +709,12 @@ static int append_atom(struct atom_value *v, struct ref_formatting_state *state,
* encountered.
*/
if (!state->stack->prev)
- quote_formatting(&state->stack->output, v->s, state->quote_style);
+ quote_formatting(&state->stack->output, v->s, v->s_size, state->quote_style);
else
- strbuf_addstr(&state->stack->output, v->s);
+ if (v->s_size != ATOM_VALUE_S_SIZE_INIT)
+ strbuf_add(&state->stack->output, v->s, v->s_size);
+ else
+ strbuf_addstr(&state->stack->output, v->s);
return 0;
}
@@ -785,14 +814,16 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state
return 0;
}
-static int is_empty(const char *s)
+static int is_empty(struct strbuf *buf)
{
- while (*s != '\0') {
- if (!isspace(*s))
- return 0;
+ const char *s = buf->buf;
+ size_t cur_len = 0;
+
+ while ((cur_len != buf->len) && (isspace(*s) || *s == '\0')) {
s++;
+ cur_len++;
}
- return 1;
+ return cur_len == buf->len;
}
static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,
@@ -800,6 +831,7 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
{
struct ref_formatting_stack *cur = state->stack;
struct if_then_else *if_then_else = NULL;
+ size_t str_len = 0;
if (cur->at_end == if_then_else_handler)
if_then_else = (struct if_then_else *)cur->at_end_data;
@@ -810,18 +842,22 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
if (if_then_else->else_atom_seen)
return strbuf_addf_ret(err, -1, _("format: %%(then) atom used after %%(else)"));
if_then_else->then_atom_seen = 1;
+ if (if_then_else->str)
+ str_len = strlen(if_then_else->str);
/*
* If the 'equals' or 'notequals' attribute is used then
* perform the required comparison. If not, only non-empty
* strings satisfy the 'if' condition.
*/
if (if_then_else->cmp_status == COMPARE_EQUAL) {
- if (!strcmp(if_then_else->str, cur->output.buf))
+ if (str_len == cur->output.len &&
+ !memcmp(if_then_else->str, cur->output.buf, cur->output.len))
if_then_else->condition_satisfied = 1;
} else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
- if (strcmp(if_then_else->str, cur->output.buf))
+ if (str_len != cur->output.len ||
+ memcmp(if_then_else->str, cur->output.buf, cur->output.len))
if_then_else->condition_satisfied = 1;
- } else if (cur->output.len && !is_empty(cur->output.buf))
+ } else if (cur->output.len && !is_empty(&cur->output))
if_then_else->condition_satisfied = 1;
strbuf_reset(&cur->output);
return 0;
@@ -867,7 +903,7 @@ static int end_atom_handler(struct atom_value *atomv, struct ref_formatting_stat
* only on the topmost supporting atom.
*/
if (!current->prev->prev) {
- quote_formatting(&s, current->output.buf, state->quote_style);
+ quote_formatting(&s, current->output.buf, current->output.len, state->quote_style);
strbuf_swap(¤t->output, &s);
}
strbuf_release(&s);
@@ -1293,7 +1329,7 @@ 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,
- struct object *obj)
+ unsigned long buf_size, struct object *obj)
{
int i;
const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
@@ -1309,6 +1345,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf,
if (deref)
name++;
+ if (starts_with(name, "raw")) {
+ if (atom->u.raw_data.option == RAW_BARE) {
+ v->s = xmemdupz(buf, buf_size);
+ v->s_size = buf_size;
+ } else if (atom->u.raw_data.option == RAW_LENGTH) {
+ v->s = xstrfmt("%"PRIuMAX, (uintmax_t)buf_size);
+ }
+ continue;
+ }
+
if ((obj->type != OBJ_TAG &&
obj->type != OBJ_COMMIT) ||
(strcmp(name, "body") &&
@@ -1378,25 +1424,30 @@ static void fill_missing_values(struct atom_value *val)
* pointed at by the ref itself; otherwise it is the object the
* ref (which is a tag) refers to.
*/
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)
+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)
{
+ void *buf = data->content;
+ unsigned long buf_size = data->size;
+
switch (obj->type) {
case OBJ_TAG:
grab_tag_values(val, deref, obj);
- grab_sub_body_contents(val, deref, buf, obj);
+ grab_sub_body_contents(val, deref, buf, buf_size, obj);
grab_person("tagger", val, deref, buf);
break;
case OBJ_COMMIT:
grab_commit_values(val, deref, obj);
- grab_sub_body_contents(val, deref, buf, obj);
+ grab_sub_body_contents(val, deref, buf, buf_size, obj);
grab_person("author", val, deref, buf);
grab_person("committer", val, deref, buf);
break;
case OBJ_TREE:
/* grab_tree_values(val, deref, obj, buf, sz); */
+ grab_sub_body_contents(val, deref, buf, buf_size, obj);
break;
case OBJ_BLOB:
/* grab_blob_values(val, deref, obj, buf, sz); */
+ grab_sub_body_contents(val, deref, buf, buf_size, obj);
break;
default:
die("Eh? Object of type %d?", obj->type);
@@ -1618,7 +1669,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);
@@ -1698,7 +1749,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
int deref = 0;
const char *refname;
struct branch *branch = NULL;
-
+ v->s_size = ATOM_VALUE_S_SIZE_INIT;
v->handler = append_atom;
v->atom = atom;
@@ -2301,6 +2352,20 @@ static int compare_detached_head(struct ref_array_item *a, struct ref_array_item
return 0;
}
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
+{
+ const char *s1 = (const void *)vs1;
+ const char *s2 = (const void *)vs2;
+ const char *end = s1 + n;
+
+ for (; s1 < end; s1++, s2++) {
+ int diff = tolower(*s1) - tolower(*s2);
+ if (diff)
+ return diff;
+ }
+ return 0;
+}
+
static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
{
struct atom_value *va, *vb;
@@ -2321,10 +2386,30 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
} else if (s->sort_flags & REF_SORTING_VERSION) {
cmp = versioncmp(va->s, vb->s);
} else if (cmp_type == FIELD_STR) {
- int (*cmp_fn)(const char *, const char *);
- cmp_fn = s->sort_flags & REF_SORTING_ICASE
- ? strcasecmp : strcmp;
- cmp = cmp_fn(va->s, vb->s);
+ if (va->s_size == ATOM_VALUE_S_SIZE_INIT &&
+ vb->s_size == ATOM_VALUE_S_SIZE_INIT) {
+ int (*cmp_fn)(const char *, const char *);
+ cmp_fn = s->sort_flags & REF_SORTING_ICASE
+ ? strcasecmp : strcmp;
+ cmp = cmp_fn(va->s, vb->s);
+ } else {
+ int (*cmp_fn)(const void *, const void *, size_t);
+ cmp_fn = s->sort_flags & REF_SORTING_ICASE
+ ? memcasecmp : memcmp;
+ size_t a_size = va->s_size == ATOM_VALUE_S_SIZE_INIT ?
+ strlen(va->s) : va->s_size;
+ size_t b_size = vb->s_size == ATOM_VALUE_S_SIZE_INIT ?
+ strlen(vb->s) : vb->s_size;
+
+ cmp = cmp_fn(va->s, vb->s, b_size > a_size ?
+ a_size : b_size);
+ if (!cmp) {
+ if (a_size > b_size)
+ cmp = 1;
+ else if (a_size < b_size)
+ cmp = -1;
+ }
+ }
} else {
if (va->value < vb->value)
cmp = -1;
@@ -2424,6 +2509,7 @@ int format_ref_array_item(struct ref_array_item *info,
}
if (format->need_color_reset_at_eol) {
struct atom_value resetv;
+ resetv.s_size = ATOM_VALUE_S_SIZE_INIT;
resetv.s = GIT_COLOR_RESET;
if (append_atom(&resetv, &state, error_buf)) {
pop_stack_element(&state.stack);
@@ -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)
+"
test_atom head '*objectname' ''
test_atom head '*objecttype' ''
test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
@@ -221,6 +223,15 @@ test_atom tag contents 'Tagging at 1151968727
'
test_atom tag HEAD ' '
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+ git cat-file commit refs/tags/testtag^{} >expected &&
+ git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ echo "" >>expected.clean &&
+ test_cmp expected.clean actual.clean
+'
+
test_expect_success 'Check invalid atoms names are errors' '
test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
'
@@ -686,6 +697,15 @@ test_atom refs/tags/signed-empty contents:body ''
test_atom refs/tags/signed-empty contents:signature "$sig"
test_atom refs/tags/signed-empty contents "$sig"
+test_expect_success 'basic atom: refs/tags/signed-empty raw' '
+ git cat-file tag refs/tags/signed-empty >expected &&
+ git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ echo "" >>expected.clean &&
+ test_cmp expected.clean actual.clean
+'
+
test_atom refs/tags/signed-short subject 'subject line'
test_atom refs/tags/signed-short subject:sanitize 'subject-line'
test_atom refs/tags/signed-short contents:subject 'subject line'
@@ -695,6 +715,15 @@ test_atom refs/tags/signed-short contents:signature "$sig"
test_atom refs/tags/signed-short contents "subject line
$sig"
+test_expect_success 'basic atom: refs/tags/signed-short raw' '
+ git cat-file tag refs/tags/signed-short >expected &&
+ git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ echo "" >>expected.clean &&
+ test_cmp expected.clean actual.clean
+'
+
test_atom refs/tags/signed-long subject 'subject line'
test_atom refs/tags/signed-long subject:sanitize 'subject-line'
test_atom refs/tags/signed-long contents:subject 'subject line'
@@ -708,6 +737,15 @@ test_atom refs/tags/signed-long contents "subject line
body contents
$sig"
+test_expect_success 'basic atom: refs/tags/signed-long raw' '
+ git cat-file tag refs/tags/signed-long >expected &&
+ git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ echo "" >>expected.clean &&
+ test_cmp expected.clean actual.clean
+'
+
test_expect_success 'set up refs pointing to tree and blob' '
git update-ref refs/mytrees/first refs/heads/main^{tree} &&
git update-ref refs/myblobs/first refs/heads/main:one
@@ -720,6 +758,16 @@ test_atom refs/mytrees/first contents:body ""
test_atom refs/mytrees/first contents:signature ""
test_atom refs/mytrees/first contents ""
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+ git cat-file tree refs/mytrees/first >expected &&
+ echo "" >>expected &&
+ git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
+ test_cmp expected actual &&
+ git cat-file -s refs/mytrees/first >expected &&
+ git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
test_atom refs/myblobs/first subject ""
test_atom refs/myblobs/first contents:subject ""
test_atom refs/myblobs/first body ""
@@ -727,6 +775,158 @@ test_atom refs/myblobs/first contents:body ""
test_atom refs/myblobs/first contents:signature ""
test_atom refs/myblobs/first contents ""
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+ git cat-file blob refs/myblobs/first >expected &&
+ echo "" >>expected &&
+ git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
+ test_cmp expected actual &&
+ git cat-file -s refs/myblobs/first >expected &&
+ git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+ printf "%b" "a\0b\0c" >blob1 &&
+ printf "%b" "a\0c\0b" >blob2 &&
+ printf "%b" "\0a\0b\0c" >blob3 &&
+ printf "%b" "abc" >blob4 &&
+ printf "%b" "\0 \0 \0 " >blob5 &&
+ printf "%b" "\0 \0a\0 " >blob6 &&
+ >blob7 &&
+ git hash-object blob1 -w | xargs git update-ref refs/myblobs/blob1 &&
+ git hash-object blob2 -w | xargs git update-ref refs/myblobs/blob2 &&
+ git hash-object blob3 -w | xargs git update-ref refs/myblobs/blob3 &&
+ git hash-object blob4 -w | xargs git update-ref refs/myblobs/blob4 &&
+ git hash-object blob5 -w | xargs git update-ref refs/myblobs/blob5 &&
+ git hash-object blob6 -w | xargs git update-ref refs/myblobs/blob6 &&
+ git hash-object blob7 -w | xargs git update-ref refs/myblobs/blob7
+'
+
+test_expect_success 'Verify sorts with raw' '
+ cat >expected <<-EOF &&
+ refs/myblobs/blob7
+ refs/myblobs/blob5
+ refs/myblobs/blob6
+ refs/myblobs/blob3
+ refs/mytrees/first
+ refs/myblobs/first
+ refs/myblobs/blob1
+ refs/myblobs/blob2
+ refs/myblobs/blob4
+ refs/heads/main
+ EOF
+ git for-each-ref --format="%(refname)" --sort=raw \
+ refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+ cat >expected <<-EOF &&
+ refs/myblobs/blob7
+ refs/myblobs/first
+ refs/heads/main
+ refs/myblobs/blob4
+ refs/myblobs/blob1
+ refs/myblobs/blob2
+ refs/myblobs/blob3
+ refs/myblobs/blob5
+ refs/myblobs/blob6
+ refs/mytrees/first
+ EOF
+ git for-each-ref --format="%(refname)" --sort=raw:size \
+ refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+ cat >expected <<-EOF &&
+ not equals
+ not equals
+ not equals
+ not equals
+ not equals
+ not equals
+ refs/myblobs/blob4
+ not equals
+ not equals
+ not equals
+ not equals
+ EOF
+ git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+ refs/myblobs/ refs/heads/ >actual &&
+ test_cmp expected actual
+'
+test_expect_success 'validate raw atom with %(if:notequals)' '
+ cat >expected <<-EOF &&
+ refs/heads/ambiguous
+ refs/heads/main
+ refs/heads/newtag
+ refs/myblobs/blob1
+ refs/myblobs/blob2
+ refs/myblobs/blob3
+ equals
+ refs/myblobs/blob5
+ refs/myblobs/blob6
+ refs/myblobs/blob7
+ refs/myblobs/first
+ EOF
+ git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+ refs/myblobs/ refs/heads/ >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+ cat >expected <<-EOF &&
+ refs/myblobs/blob1 not empty
+ refs/myblobs/blob2 not empty
+ refs/myblobs/blob3 not empty
+ refs/myblobs/blob4 not empty
+ refs/myblobs/blob5 empty
+ refs/myblobs/blob6 not empty
+ refs/myblobs/blob7 empty
+ refs/myblobs/first not empty
+ EOF
+ git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+ refs/myblobs/ >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must failed' '
+ test_must_fail git for-each-ref --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must failed' '
+ test_must_fail git for-each-ref --format="%(raw)" --tcl
+'
+
+test_expect_success '%(raw) with --perl must failed' '
+ test_must_fail git for-each-ref --format="%(raw)" --perl
+'
+
+test_expect_success '%(raw) with --shell must failed' '
+ test_must_fail git for-each-ref --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must failed' '
+ test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+ git for-each-ref --format="%(raw:size)" | while read line
+ do
+ echo "'\''$line'\''" >>expect
+ done &&
+ git for-each-ref --format="%(raw:size)" --shell >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'for-each-ref --format compare with cat-file --batch' '
+ git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+ git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'set up multiple-sort tags' '
for when in 100000 200000
do