Skip to content

Commit c623237

Browse files
committed
Merge branch 'jt/rev-list-z' into seen
"git rev-list" learns machine-parsable output format that delimits each field with NUL. * jt/rev-list-z: rev-list: support NUL-delimited --missing option rev-list: support NUL-delimited --boundary option rev-list: support delimiting objects with NUL bytes revision: support NUL-delimited --stdin mode rev-list: refactor early option parsing rev-list: inline `show_object_with_name()` in `show_object()`
2 parents 29ade02 + 9e16e69 commit c623237

File tree

7 files changed

+200
-41
lines changed

7 files changed

+200
-41
lines changed

Documentation/rev-list-options.adoc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,32 @@ ifdef::git-rev-list[]
361361
--progress=<header>::
362362
Show progress reports on stderr as objects are considered. The
363363
`<header>` text will be printed with each progress update.
364+
365+
-z::
366+
Instead of being newline-delimited, each outputted object and its
367+
accompanying metadata is delimited using NUL bytes in the following
368+
form:
369+
+
370+
-----------------------------------------------------------------------
371+
<OID> NUL [<token>=<value> NUL]...
372+
-----------------------------------------------------------------------
373+
+
374+
Additional object metadata, such as object paths or boundary/missing objects,
375+
is printed using the `<token>=<value>` form. Token values are printed as-is
376+
without any encoding/truncation. An OID entry never contains a '=' character
377+
and thus is used to signal the start of a new object record. Examples:
378+
+
379+
-----------------------------------------------------------------------
380+
<OID> NUL
381+
<OID> NUL path=<path> NUL
382+
<OID> NUL boundary=yes NUL
383+
<OID> NUL missing=yes NUL [<token>=<value> NUL]...
384+
-----------------------------------------------------------------------
385+
+
386+
This mode is only compatible with the `--objects`, `--boundary`, and
387+
`--missing` output options. Also, revision and pathspec argument parsing on
388+
stdin with the `--stdin` option is NUL byte delimited instead of using newlines
389+
while in this mode.
364390
endif::git-rev-list[]
365391

366392
History Simplification

builtin/rev-list.c

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "object-file.h"
1717
#include "object-store-ll.h"
1818
#include "pack-bitmap.h"
19+
#include "parse-options.h"
1920
#include "log-tree.h"
2021
#include "graph.h"
2122
#include "bisect.h"
@@ -64,6 +65,7 @@ static const char rev_list_usage[] =
6465
" --abbrev-commit\n"
6566
" --left-right\n"
6667
" --count\n"
68+
" -z\n"
6769
" special purpose:\n"
6870
" --bisect\n"
6971
" --bisect-vars\n"
@@ -96,6 +98,9 @@ static int arg_show_object_names = 1;
9698

9799
#define DEFAULT_OIDSET_SIZE (16*1024)
98100

101+
static char line_term = '\n';
102+
static char info_term = ' ';
103+
99104
static int show_disk_usage;
100105
static off_t total_disk_usage;
101106
static int human_readable;
@@ -131,24 +136,39 @@ static void print_missing_object(struct missing_objects_map_entry *entry,
131136
{
132137
struct strbuf sb = STRBUF_INIT;
133138

139+
if (line_term)
140+
putchar('?');
141+
142+
printf("%s", oid_to_hex(&entry->entry.oid));
143+
144+
if (!line_term)
145+
printf("%cmissing=yes", info_term);
146+
134147
if (!print_missing_info) {
135-
printf("?%s\n", oid_to_hex(&entry->entry.oid));
148+
putchar(line_term);
136149
return;
137150
}
138151

139152
if (entry->path && *entry->path) {
140153
struct strbuf path = STRBUF_INIT;
141154

142-
strbuf_addstr(&sb, " path=");
143-
quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP);
144-
strbuf_addbuf(&sb, &path);
155+
strbuf_addf(&sb, "%cpath=", info_term);
156+
157+
if (line_term) {
158+
quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP);
159+
strbuf_addbuf(&sb, &path);
160+
} else {
161+
strbuf_addstr(&sb, entry->path);
162+
}
145163

146164
strbuf_release(&path);
147165
}
148166
if (entry->type)
149-
strbuf_addf(&sb, " type=%s", type_name(entry->type));
167+
strbuf_addf(&sb, "%ctype=%s", info_term, type_name(entry->type));
168+
169+
fwrite(sb.buf, sizeof(char), sb.len, stdout);
170+
putchar(line_term);
150171

151-
printf("?%s%s\n", oid_to_hex(&entry->entry.oid), sb.buf);
152172
strbuf_release(&sb);
153173
}
154174

@@ -235,13 +255,18 @@ static void show_commit(struct commit *commit, void *data)
235255
fputs(info->header_prefix, stdout);
236256

237257
if (revs->include_header) {
238-
if (!revs->graph)
258+
if (!revs->graph && line_term)
239259
fputs(get_revision_mark(revs, commit), stdout);
240260
if (revs->abbrev_commit && revs->abbrev)
241261
fputs(repo_find_unique_abbrev(the_repository, &commit->object.oid, revs->abbrev),
242262
stdout);
243263
else
244264
fputs(oid_to_hex(&commit->object.oid), stdout);
265+
266+
if (!line_term) {
267+
if (commit->object.flags & BOUNDARY)
268+
printf("%cboundary=yes", info_term);
269+
}
245270
}
246271
if (revs->print_parents) {
247272
struct commit_list *parents = commit->parents;
@@ -263,7 +288,7 @@ static void show_commit(struct commit *commit, void *data)
263288
if (revs->commit_format == CMIT_FMT_ONELINE)
264289
putchar(' ');
265290
else if (revs->include_header)
266-
putchar('\n');
291+
putchar(line_term);
267292

268293
if (revs->verbose_header) {
269294
struct strbuf buf = STRBUF_INIT;
@@ -357,10 +382,19 @@ static void show_object(struct object *obj, const char *name, void *cb_data)
357382
return;
358383
}
359384

360-
if (arg_show_object_names)
361-
show_object_with_name(stdout, obj, name);
362-
else
363-
printf("%s\n", oid_to_hex(&obj->oid));
385+
printf("%s", oid_to_hex(&obj->oid));
386+
387+
if (arg_show_object_names) {
388+
if (line_term) {
389+
putchar(info_term);
390+
for (const char *p = name; *p && *p != '\n'; p++)
391+
putchar(*p);
392+
} else if (*name) {
393+
printf("%cpath=%s", info_term, name);
394+
}
395+
}
396+
397+
putchar(line_term);
364398
}
365399

366400
static void show_edge(struct commit *commit)
@@ -634,19 +668,19 @@ int cmd_rev_list(int argc,
634668
if (!strcmp(arg, "--exclude-promisor-objects")) {
635669
fetch_if_missing = 0;
636670
revs.exclude_promisor_objects = 1;
637-
break;
638-
}
639-
}
640-
for (i = 1; i < argc; i++) {
641-
const char *arg = argv[i];
642-
if (skip_prefix(arg, "--missing=", &arg)) {
643-
if (revs.exclude_promisor_objects)
644-
die(_("options '%s' and '%s' cannot be used together"), "--exclude-promisor-objects", "--missing");
645-
if (parse_missing_action_value(arg))
646-
break;
671+
} else if (skip_prefix(arg, "--missing=", &arg)) {
672+
parse_missing_action_value(arg);
673+
} else if (!strcmp(arg, "-z")) {
674+
s_r_opt.nul_delim_stdin = 1;
675+
line_term = '\0';
676+
info_term = '\0';
647677
}
648678
}
649679

680+
die_for_incompatible_opt2(revs.exclude_promisor_objects,
681+
"--exclude_promisor_objects",
682+
arg_missing_action, "--missing");
683+
650684
if (arg_missing_action)
651685
revs.do_not_die_on_missing_objects = 1;
652686

@@ -755,6 +789,20 @@ int cmd_rev_list(int argc,
755789
usage(rev_list_usage);
756790

757791
}
792+
793+
/*
794+
* Reject options currently incompatible with -z. For some options, this
795+
* is not an inherent limitation and support may be implemented in the
796+
* future.
797+
*/
798+
if (!line_term) {
799+
if (revs.graph || revs.verbose_header || show_disk_usage ||
800+
info.show_timestamp || info.header_prefix || bisect_list ||
801+
use_bitmap_index || revs.edge_hint || revs.left_right ||
802+
revs.cherry_mark)
803+
die(_("-z option used with unsupported option"));
804+
}
805+
758806
if (revs.commit_format != CMIT_FMT_USERFORMAT)
759807
revs.include_header = 1;
760808
if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {

revision.c

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,6 @@ implement_shared_commit_slab(revision_sources, char *);
5959

6060
static inline int want_ancestry(const struct rev_info *revs);
6161

62-
void show_object_with_name(FILE *out, struct object *obj, const char *name)
63-
{
64-
fprintf(out, "%s ", oid_to_hex(&obj->oid));
65-
for (const char *p = name; *p && *p != '\n'; p++)
66-
fputc(*p, out);
67-
fputc('\n', out);
68-
}
69-
7062
static void mark_blob_uninteresting(struct blob *blob)
7163
{
7264
if (!blob)
@@ -2283,10 +2275,10 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsig
22832275
return ret;
22842276
}
22852277

2286-
static void read_pathspec_from_stdin(struct strbuf *sb,
2287-
struct strvec *prune)
2278+
static void read_pathspec_from_stdin(struct strbuf *sb, struct strvec *prune,
2279+
int line_term)
22882280
{
2289-
while (strbuf_getline(sb, stdin) != EOF)
2281+
while (strbuf_getdelim_strip_crlf(sb, stdin, line_term) != EOF)
22902282
strvec_push(prune, sb->buf);
22912283
}
22922284

@@ -2913,8 +2905,8 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
29132905
return 1;
29142906
}
29152907

2916-
static void read_revisions_from_stdin(struct rev_info *revs,
2917-
struct strvec *prune)
2908+
static void read_revisions_from_stdin(struct rev_info *revs, struct strvec *prune,
2909+
int line_term)
29182910
{
29192911
struct strbuf sb;
29202912
int seen_dashdash = 0;
@@ -2926,7 +2918,7 @@ static void read_revisions_from_stdin(struct rev_info *revs,
29262918
warn_on_object_refname_ambiguity = 0;
29272919

29282920
strbuf_init(&sb, 1000);
2929-
while (strbuf_getline(&sb, stdin) != EOF) {
2921+
while (strbuf_getdelim_strip_crlf(&sb, stdin, line_term) != EOF) {
29302922
if (!sb.len)
29312923
break;
29322924

@@ -2954,7 +2946,7 @@ static void read_revisions_from_stdin(struct rev_info *revs,
29542946
die("bad revision '%s'", sb.buf);
29552947
}
29562948
if (seen_dashdash)
2957-
read_pathspec_from_stdin(&sb, prune);
2949+
read_pathspec_from_stdin(&sb, prune, line_term);
29582950

29592951
strbuf_release(&sb);
29602952
warn_on_object_refname_ambiguity = save_warning;
@@ -3027,13 +3019,16 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
30273019
}
30283020

30293021
if (!strcmp(arg, "--stdin")) {
3022+
int term = opt && opt->nul_delim_stdin ? '\0' : '\n';
3023+
30303024
if (revs->disable_stdin) {
30313025
argv[left++] = arg;
30323026
continue;
30333027
}
30343028
if (revs->read_from_stdin++)
30353029
die("--stdin given twice?");
3036-
read_revisions_from_stdin(revs, &prune_data);
3030+
read_revisions_from_stdin(revs, &prune_data,
3031+
term);
30373032
continue;
30383033
}
30393034

revision.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,8 @@ struct setup_revision_opt {
439439
void (*tweak)(struct rev_info *);
440440
unsigned int assume_dashdash:1,
441441
allow_exclude_promisor_objects:1,
442-
free_removed_argv_elements:1;
442+
free_removed_argv_elements:1,
443+
nul_delim_stdin:1;
443444
unsigned revarg_opt;
444445
};
445446
int setup_revisions(int argc, const char **argv, struct rev_info *revs,
@@ -489,8 +490,6 @@ void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit);
489490
void mark_tree_uninteresting(struct repository *r, struct tree *tree);
490491
void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees);
491492

492-
void show_object_with_name(FILE *, struct object *, const char *);
493-
494493
/**
495494
* Helpers to check if a reference should be excluded.
496495
*/

t/t6000-rev-list-misc.sh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,55 @@ test_expect_success 'rev-list --unpacked' '
182182
test_cmp expect actual
183183
'
184184

185+
test_expect_success 'rev-list -z' '
186+
test_when_finished rm -rf repo &&
187+
188+
git init repo &&
189+
test_commit -C repo 1 &&
190+
test_commit -C repo 2 &&
191+
192+
oid1=$(git -C repo rev-parse HEAD) &&
193+
oid2=$(git -C repo rev-parse HEAD~) &&
194+
195+
printf "%s\0%s\0" "$oid1" "$oid2" >expect &&
196+
git -C repo rev-list -z HEAD >actual &&
197+
198+
test_cmp expect actual
199+
'
200+
201+
test_expect_success 'rev-list -z --objects' '
202+
test_when_finished rm -rf repo &&
203+
204+
git init repo &&
205+
test_commit -C repo 1 &&
206+
test_commit -C repo 2 &&
207+
208+
oid1=$(git -C repo rev-parse HEAD:1.t) &&
209+
oid2=$(git -C repo rev-parse HEAD:2.t) &&
210+
path1=1.t &&
211+
path2=2.t &&
212+
213+
printf "%s\0path=%s\0%s\0path=%s\0" "$oid1" "$path1" "$oid2" "$path2" \
214+
>expect &&
215+
git -C repo rev-list -z --objects HEAD:1.t HEAD:2.t >actual &&
216+
217+
test_cmp expect actual
218+
'
219+
220+
test_expect_success 'rev-list -z --boundary' '
221+
test_when_finished rm -rf repo &&
222+
223+
git init repo &&
224+
test_commit -C repo 1 &&
225+
test_commit -C repo 2 &&
226+
227+
oid1=$(git -C repo rev-parse HEAD) &&
228+
oid2=$(git -C repo rev-parse HEAD~) &&
229+
230+
printf "%s\0%s\0boundary=yes\0" "$oid1" "$oid2" >expect &&
231+
git -C repo rev-list -z --boundary HEAD~.. >actual &&
232+
233+
test_cmp expect actual
234+
'
235+
185236
test_done

t/t6017-rev-list-stdin.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,13 @@ test_expect_success '--not via stdin does not influence revisions from command l
148148
test_cmp expect actual
149149
'
150150

151+
test_expect_success 'NUL-delimited stdin' '
152+
printf "%s\0%s\0%s\0" "HEAD" "--" "file-1" > input &&
153+
154+
git rev-list -z --objects HEAD -- file-1 >expect &&
155+
git rev-list -z --objects --stdin <input >actual &&
156+
157+
test_cmp expect actual
158+
'
159+
151160
test_done

0 commit comments

Comments
 (0)