Skip to content

Commit

Permalink
Merge branch 'ps/cat-file-null-output' into next
Browse files Browse the repository at this point in the history
"git cat-file --batch" and friends learned "-Z" that uses NUL
delimiter for both input and output.

* ps/cat-file-null-output:
  cat-file: add option '-Z' that delimits input and output with NUL
  cat-file: simplify reading from standard input
  strbuf: provide CRLF-aware helper to read until a specified delimiter
  t1006: modernize test style to use `test_cmp`
  t1006: don't strip timestamps from expected results
  • Loading branch information
gitster committed Jun 15, 2023
2 parents e7baddb + f79e188 commit e841ad2
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 138 deletions.
15 changes: 13 additions & 2 deletions Documentation/git-cat-file.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ SYNOPSIS
'git cat-file' (-t | -s) [--allow-unknown-type] <object>
'git cat-file' (--batch | --batch-check | --batch-command) [--batch-all-objects]
[--buffer] [--follow-symlinks] [--unordered]
[--textconv | --filters] [-z]
[--textconv | --filters] [-Z]
'git cat-file' (--textconv | --filters)
[<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]

Expand Down Expand Up @@ -243,10 +243,16 @@ respectively print:
/etc/passwd
--

-Z::
Only meaningful with `--batch`, `--batch-check`, or
`--batch-command`; input and output is NUL-delimited instead of
newline-delimited.

-z::
Only meaningful with `--batch`, `--batch-check`, or
`--batch-command`; input is NUL-delimited instead of
newline-delimited.
newline-delimited. This option is deprecated in favor of
`-Z` as the output can otherwise be ambiguous.


OUTPUT
Expand Down Expand Up @@ -384,6 +390,11 @@ notdir SP <size> LF
is printed when, during symlink resolution, a file is used as a
directory name.

Alternatively, when `-Z` is passed, the line feeds in any of the above examples
are replaced with NUL terminators. This ensures that output will be parsable if
the output itself would contain a linefeed and is thus recommended for
scripting purposes.

CAVEATS
-------

Expand Down
85 changes: 43 additions & 42 deletions builtin/cat-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ struct batch_options {
int all_objects;
int unordered;
int transform_mode; /* may be 'w' or 'c' for --filters or --textconv */
int nul_terminated;
char input_delim;
char output_delim;
const char *format;
};

Expand Down Expand Up @@ -436,11 +437,12 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
}
}

static void print_default_format(struct strbuf *scratch, struct expand_data *data)
static void print_default_format(struct strbuf *scratch, struct expand_data *data,
struct batch_options *opt)
{
strbuf_addf(scratch, "%s %s %"PRIuMAX"\n", oid_to_hex(&data->oid),
strbuf_addf(scratch, "%s %s %"PRIuMAX"%c", oid_to_hex(&data->oid),
type_name(data->type),
(uintmax_t)data->size);
(uintmax_t)data->size, opt->output_delim);
}

/*
Expand Down Expand Up @@ -469,8 +471,8 @@ static void batch_object_write(const char *obj_name,
&data->oid, &data->info,
OBJECT_INFO_LOOKUP_REPLACE);
if (ret < 0) {
printf("%s missing\n",
obj_name ? obj_name : oid_to_hex(&data->oid));
printf("%s missing%c",
obj_name ? obj_name : oid_to_hex(&data->oid), opt->output_delim);
fflush(stdout);
return;
}
Expand All @@ -491,17 +493,17 @@ static void batch_object_write(const char *obj_name,
strbuf_reset(scratch);

if (!opt->format) {
print_default_format(scratch, data);
print_default_format(scratch, data, opt);
} else {
strbuf_expand(scratch, opt->format, expand_format, data);
strbuf_addch(scratch, '\n');
strbuf_addch(scratch, opt->output_delim);
}

batch_write(opt, scratch->buf, scratch->len);

if (opt->batch_mode == BATCH_MODE_CONTENTS) {
print_object_or_die(opt, data);
batch_write(opt, "\n", 1);
batch_write(opt, &opt->output_delim, 1);
}
}

Expand All @@ -519,22 +521,25 @@ static void batch_one_object(const char *obj_name,
if (result != FOUND) {
switch (result) {
case MISSING_OBJECT:
printf("%s missing\n", obj_name);
printf("%s missing%c", obj_name, opt->output_delim);
break;
case SHORT_NAME_AMBIGUOUS:
printf("%s ambiguous\n", obj_name);
printf("%s ambiguous%c", obj_name, opt->output_delim);
break;
case DANGLING_SYMLINK:
printf("dangling %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
printf("dangling %"PRIuMAX"%c%s%c",
(uintmax_t)strlen(obj_name),
opt->output_delim, obj_name, opt->output_delim);
break;
case SYMLINK_LOOP:
printf("loop %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
printf("loop %"PRIuMAX"%c%s%c",
(uintmax_t)strlen(obj_name),
opt->output_delim, obj_name, opt->output_delim);
break;
case NOT_DIR:
printf("notdir %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
printf("notdir %"PRIuMAX"%c%s%c",
(uintmax_t)strlen(obj_name),
opt->output_delim, obj_name, opt->output_delim);
break;
default:
BUG("unknown get_sha1_with_context result %d\n",
Expand All @@ -546,9 +551,9 @@ static void batch_one_object(const char *obj_name,
}

if (ctx.mode == 0) {
printf("symlink %"PRIuMAX"\n%s\n",
printf("symlink %"PRIuMAX"%c%s%c",
(uintmax_t)ctx.symlink_path.len,
ctx.symlink_path.buf);
opt->output_delim, ctx.symlink_path.buf, opt->output_delim);
fflush(stdout);
return;
}
Expand Down Expand Up @@ -693,20 +698,12 @@ static void batch_objects_command(struct batch_options *opt,
struct queued_cmd *queued_cmd = NULL;
size_t alloc = 0, nr = 0;

while (1) {
int i, ret;
while (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {
int i;
const struct parse_cmd *cmd = NULL;
const char *p = NULL, *cmd_end;
struct queued_cmd call = {0};

if (opt->nul_terminated)
ret = strbuf_getline_nul(&input, stdin);
else
ret = strbuf_getline(&input, stdin);

if (ret)
break;

if (!input.len)
die(_("empty command in input"));
if (isspace(*input.buf))
Expand Down Expand Up @@ -850,16 +847,7 @@ static int batch_objects(struct batch_options *opt)
goto cleanup;
}

while (1) {
int ret;
if (opt->nul_terminated)
ret = strbuf_getline_nul(&input, stdin);
else
ret = strbuf_getline(&input, stdin);

if (ret == EOF)
break;

while (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {
if (data.split_on_whitespace) {
/*
* Split at first whitespace, tying off the beginning
Expand Down Expand Up @@ -928,14 +916,16 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
const char *exp_type = NULL, *obj_name = NULL;
struct batch_options batch = {0};
int unknown_type = 0;
int input_nul_terminated = 0;
int nul_terminated = 0;

const char * const usage[] = {
N_("git cat-file <type> <object>"),
N_("git cat-file (-e | -p) <object>"),
N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"),
N_("git cat-file (--batch | --batch-check | --batch-command) [--batch-all-objects]\n"
" [--buffer] [--follow-symlinks] [--unordered]\n"
" [--textconv | --filters] [-z]"),
" [--textconv | --filters] [-Z]"),
N_("git cat-file (--textconv | --filters)\n"
" [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"),
NULL
Expand Down Expand Up @@ -964,7 +954,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
N_("like --batch, but don't emit <contents>"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
batch_option_callback),
OPT_BOOL('z', NULL, &batch.nul_terminated, N_("stdin is NUL-terminated")),
OPT_BOOL_F('z', NULL, &input_nul_terminated, N_("stdin is NUL-terminated"),
PARSE_OPT_HIDDEN),
OPT_BOOL('Z', NULL, &nul_terminated, N_("stdin and stdout is NUL-terminated")),
OPT_CALLBACK_F(0, "batch-command", &batch, N_("format"),
N_("read commands from stdin"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
Expand Down Expand Up @@ -1023,9 +1015,18 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
else if (batch.all_objects)
usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
"--batch-all-objects");
else if (batch.nul_terminated)
else if (input_nul_terminated)
usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
"-z");
else if (nul_terminated)
usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
"-Z");

batch.input_delim = batch.output_delim = '\n';
if (input_nul_terminated)
batch.input_delim = '\0';
if (nul_terminated)
batch.input_delim = batch.output_delim = '\0';

/* Batch defaults */
if (batch.buffer_output < 0)
Expand Down
11 changes: 8 additions & 3 deletions strbuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -722,18 +722,23 @@ static int strbuf_getdelim(struct strbuf *sb, FILE *fp, int term)
return 0;
}

int strbuf_getline(struct strbuf *sb, FILE *fp)
int strbuf_getdelim_strip_crlf(struct strbuf *sb, FILE *fp, int term)
{
if (strbuf_getwholeline(sb, fp, '\n'))
if (strbuf_getwholeline(sb, fp, term))
return EOF;
if (sb->buf[sb->len - 1] == '\n') {
if (term == '\n' && sb->buf[sb->len - 1] == '\n') {
strbuf_setlen(sb, sb->len - 1);
if (sb->len && sb->buf[sb->len - 1] == '\r')
strbuf_setlen(sb, sb->len - 1);
}
return 0;
}

int strbuf_getline(struct strbuf *sb, FILE *fp)
{
return strbuf_getdelim_strip_crlf(sb, fp, '\n');
}

int strbuf_getline_lf(struct strbuf *sb, FILE *fp)
{
return strbuf_getdelim(sb, fp, '\n');
Expand Down
12 changes: 12 additions & 0 deletions strbuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,18 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
*/
ssize_t strbuf_write(struct strbuf *sb, FILE *stream);

/**
* Read from a FILE * until the specified terminator is encountered,
* overwriting the existing contents of the strbuf.
*
* Reading stops after the terminator or at EOF. The terminator is
* removed from the buffer before returning. If the terminator is LF
* and if it is preceded by a CR, then the whole CRLF is stripped.
* Returns 0 unless there was nothing left before EOF, in which case
* it returns `EOF`.
*/
int strbuf_getdelim_strip_crlf(struct strbuf *sb, FILE *fp, int term);

/**
* Read a line from a FILE *, overwriting the existing contents of
* the strbuf. The strbuf_getline*() family of functions share
Expand Down
Loading

0 comments on commit e841ad2

Please sign in to comment.