diff options
| author | Junio C Hamano <gitster@pobox.com> | 2025-04-16 13:54:18 -0700 |
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2025-04-16 13:54:18 -0700 |
| commit | 1a1661bd41697a106481e9e2467d0f5a0697349a (patch) | |
| tree | 8b0e86ca28f1eaf85f55517473748e0f7acbce99 | |
| parent | Merge branch 'ab/pathspec-sign-compare-workaround' (diff) | |
| parent | rev-list: support NUL-delimited --missing option (diff) | |
| download | git-1a1661bd41697a106481e9e2467d0f5a0697349a.tar.gz git-1a1661bd41697a106481e9e2467d0f5a0697349a.zip | |
Merge branch 'jt/rev-list-z'
"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
rev-list: refactor early option parsing
rev-list: inline `show_object_with_name()` in `show_object()`
| -rw-r--r-- | Documentation/rev-list-options.adoc | 24 | ||||
| -rw-r--r-- | builtin/rev-list.c | 93 | ||||
| -rw-r--r-- | revision.c | 8 | ||||
| -rw-r--r-- | revision.h | 2 | ||||
| -rwxr-xr-x | t/t6000-rev-list-misc.sh | 51 | ||||
| -rwxr-xr-x | t/t6022-rev-list-missing.sh | 31 |
6 files changed, 175 insertions, 34 deletions
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 1c403c1e40..d38875efda 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -361,6 +361,30 @@ ifdef::git-rev-list[] --progress=<header>:: Show progress reports on stderr as objects are considered. The `<header>` text will be printed with each progress update. + +-z:: + Instead of being newline-delimited, each outputted object and its + accompanying metadata is delimited using NUL bytes. Output is printed + in the following form: ++ +----------------------------------------------------------------------- +<OID> NUL [<token>=<value> NUL]... +----------------------------------------------------------------------- ++ +Additional object metadata, such as object paths or boundary objects, is +printed using the `<token>=<value>` form. Token values are printed as-is +without any encoding/truncation. An OID entry never contains a '=' character +and thus is used to signal the start of a new object record. Examples: ++ +----------------------------------------------------------------------- +<OID> NUL +<OID> NUL path=<path> NUL +<OID> NUL boundary=yes NUL +<OID> NUL missing=yes NUL [<token>=<value> NUL]... +----------------------------------------------------------------------- ++ +This mode is only compatible with the `--objects`, `--boundary`, and +`--missing` output options. endif::git-rev-list[] History Simplification diff --git a/builtin/rev-list.c b/builtin/rev-list.c index bb26bee0d4..e6ee3f82ee 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -16,6 +16,7 @@ #include "object-file.h" #include "object-store-ll.h" #include "pack-bitmap.h" +#include "parse-options.h" #include "log-tree.h" #include "graph.h" #include "bisect.h" @@ -64,6 +65,7 @@ static const char rev_list_usage[] = " --abbrev-commit\n" " --left-right\n" " --count\n" +" -z\n" " special purpose:\n" " --bisect\n" " --bisect-vars\n" @@ -96,6 +98,9 @@ static int arg_show_object_names = 1; #define DEFAULT_OIDSET_SIZE (16*1024) +static char line_term = '\n'; +static char info_term = ' '; + static int show_disk_usage; static off_t total_disk_usage; static int human_readable; @@ -131,24 +136,37 @@ static void print_missing_object(struct missing_objects_map_entry *entry, { struct strbuf sb = STRBUF_INIT; + if (line_term) + printf("?%s", oid_to_hex(&entry->entry.oid)); + else + printf("%s%cmissing=yes", oid_to_hex(&entry->entry.oid), + info_term); + if (!print_missing_info) { - printf("?%s\n", oid_to_hex(&entry->entry.oid)); + putchar(line_term); return; } if (entry->path && *entry->path) { - struct strbuf path = STRBUF_INIT; + strbuf_addf(&sb, "%cpath=", info_term); - strbuf_addstr(&sb, " path="); - quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP); - strbuf_addbuf(&sb, &path); + if (line_term) { + struct strbuf path = STRBUF_INIT; - strbuf_release(&path); + quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP); + strbuf_addbuf(&sb, &path); + + strbuf_release(&path); + } else { + strbuf_addstr(&sb, entry->path); + } } if (entry->type) - strbuf_addf(&sb, " type=%s", type_name(entry->type)); + strbuf_addf(&sb, "%ctype=%s", info_term, type_name(entry->type)); + + fwrite(sb.buf, sizeof(char), sb.len, stdout); + putchar(line_term); - printf("?%s%s\n", oid_to_hex(&entry->entry.oid), sb.buf); strbuf_release(&sb); } @@ -235,13 +253,18 @@ static void show_commit(struct commit *commit, void *data) fputs(info->header_prefix, stdout); if (revs->include_header) { - if (!revs->graph) + if (!revs->graph && line_term) fputs(get_revision_mark(revs, commit), stdout); if (revs->abbrev_commit && revs->abbrev) fputs(repo_find_unique_abbrev(the_repository, &commit->object.oid, revs->abbrev), stdout); else fputs(oid_to_hex(&commit->object.oid), stdout); + + if (!line_term) { + if (commit->object.flags & BOUNDARY) + printf("%cboundary=yes", info_term); + } } if (revs->print_parents) { struct commit_list *parents = commit->parents; @@ -263,7 +286,7 @@ static void show_commit(struct commit *commit, void *data) if (revs->commit_format == CMIT_FMT_ONELINE) putchar(' '); else if (revs->include_header) - putchar('\n'); + putchar(line_term); if (revs->verbose_header) { struct strbuf buf = STRBUF_INIT; @@ -357,10 +380,19 @@ static void show_object(struct object *obj, const char *name, void *cb_data) return; } - if (arg_show_object_names) - show_object_with_name(stdout, obj, name); - else - printf("%s\n", oid_to_hex(&obj->oid)); + printf("%s", oid_to_hex(&obj->oid)); + + if (arg_show_object_names) { + if (line_term) { + putchar(info_term); + for (const char *p = name; *p && *p != '\n'; p++) + putchar(*p); + } else if (*name) { + printf("%cpath=%s", info_term, name); + } + } + + putchar(line_term); } static void show_edge(struct commit *commit) @@ -634,19 +666,18 @@ int cmd_rev_list(int argc, if (!strcmp(arg, "--exclude-promisor-objects")) { fetch_if_missing = 0; revs.exclude_promisor_objects = 1; - break; - } - } - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (skip_prefix(arg, "--missing=", &arg)) { - if (revs.exclude_promisor_objects) - die(_("options '%s' and '%s' cannot be used together"), "--exclude-promisor-objects", "--missing"); - if (parse_missing_action_value(arg)) - break; + } else if (skip_prefix(arg, "--missing=", &arg)) { + parse_missing_action_value(arg); + } else if (!strcmp(arg, "-z")) { + line_term = '\0'; + info_term = '\0'; } } + die_for_incompatible_opt2(revs.exclude_promisor_objects, + "--exclude_promisor_objects", + arg_missing_action, "--missing"); + if (arg_missing_action) revs.do_not_die_on_missing_objects = 1; @@ -755,6 +786,20 @@ int cmd_rev_list(int argc, usage(rev_list_usage); } + + /* + * Reject options currently incompatible with -z. For some options, this + * is not an inherent limitation and support may be implemented in the + * future. + */ + if (!line_term) { + if (revs.graph || revs.verbose_header || show_disk_usage || + info.show_timestamp || info.header_prefix || bisect_list || + use_bitmap_index || revs.edge_hint || revs.left_right || + revs.cherry_mark) + die(_("-z option used with unsupported option")); + } + if (revs.commit_format != CMIT_FMT_USERFORMAT) revs.include_header = 1; if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { diff --git a/revision.c b/revision.c index b536c4a29a..3fb4c4adb7 100644 --- a/revision.c +++ b/revision.c @@ -59,14 +59,6 @@ implement_shared_commit_slab(revision_sources, char *); static inline int want_ancestry(const struct rev_info *revs); -void show_object_with_name(FILE *out, struct object *obj, const char *name) -{ - fprintf(out, "%s ", oid_to_hex(&obj->oid)); - for (const char *p = name; *p && *p != '\n'; p++) - fputc(*p, out); - fputc('\n', out); -} - static void mark_blob_uninteresting(struct blob *blob) { if (!blob) diff --git a/revision.h b/revision.h index 71e984c452..21c6a69899 100644 --- a/revision.h +++ b/revision.h @@ -489,8 +489,6 @@ void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit); void mark_tree_uninteresting(struct repository *r, struct tree *tree); void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees); -void show_object_with_name(FILE *, struct object *, const char *); - /** * Helpers to check if a reference should be excluded. */ diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index 6289a2e8b0..33881274a4 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -182,4 +182,55 @@ test_expect_success 'rev-list --unpacked' ' test_cmp expect actual ' +test_expect_success 'rev-list -z' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + + oid1=$(git -C repo rev-parse HEAD~) && + oid2=$(git -C repo rev-parse HEAD) && + + printf "%s\0%s\0" "$oid2" "$oid1" >expect && + git -C repo rev-list -z HEAD >actual && + + test_cmp expect actual +' + +test_expect_success 'rev-list -z --objects' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + + oid1=$(git -C repo rev-parse HEAD:1.t) && + oid2=$(git -C repo rev-parse HEAD:2.t) && + path1=1.t && + path2=2.t && + + printf "%s\0path=%s\0%s\0path=%s\0" "$oid1" "$path1" "$oid2" "$path2" \ + >expect && + git -C repo rev-list -z --objects HEAD:1.t HEAD:2.t >actual && + + test_cmp expect actual +' + +test_expect_success 'rev-list -z --boundary' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + + oid1=$(git -C repo rev-parse HEAD~) && + oid2=$(git -C repo rev-parse HEAD) && + + printf "%s\0%s\0boundary=yes\0" "$oid2" "$oid1" >expect && + git -C repo rev-list -z --boundary HEAD~.. >actual && + + test_cmp expect actual +' + test_done diff --git a/t/t6022-rev-list-missing.sh b/t/t6022-rev-list-missing.sh index 3e2790d4c8..08e92dd002 100755 --- a/t/t6022-rev-list-missing.sh +++ b/t/t6022-rev-list-missing.sh @@ -198,4 +198,35 @@ do ' done +test_expect_success "-z nul-delimited --missing" ' + test_when_finished rm -rf repo && + + git init repo && + ( + cd repo && + git commit --allow-empty -m first && + + path="foo bar" && + echo foobar >"$path" && + git add -A && + git commit -m second && + + oid=$(git rev-parse "HEAD:$path") && + type="$(git cat-file -t $oid)" && + + obj_path=".git/objects/$(test_oid_to_path $oid)" && + + git rev-list -z --objects --no-object-names \ + HEAD ^"$oid" >expect && + printf "%s\0missing=yes\0path=%s\0type=%s\0" "$oid" "$path" \ + "$type" >>expect && + + mv "$obj_path" "$obj_path.hidden" && + git rev-list -z --objects --no-object-names \ + --missing=print-info HEAD >actual && + + test_cmp expect actual + ) +' + test_done |
