From 79959a54eb4c1a0812b1f4643530069a63e549f4 Mon Sep 17 00:00:00 2001 From: Alphadelta14 Date: Sat, 15 Oct 2022 09:56:06 -0400 Subject: [PATCH 1/9] tree: do not use the_repository for tree traversal methods. Expect that tree walking may switch repository contexts for cases such as submodules. Added compatibility macros for existing cases. Annotate an existing issue where repo is wrong when traversing. Signed-off-by: Heather Lapointe --- tree.c | 15 +++++++++------ tree.h | 14 ++++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/tree.c b/tree.c index 410e3b477e557f..13f9173d45eb9a 100644 --- a/tree.c +++ b/tree.c @@ -22,7 +22,7 @@ int read_tree_at(struct repository *r, int len, oldlen = base->len; enum interesting retval = entry_not_interesting; - if (parse_tree(tree)) + if (repo_parse_tree(r, tree)) return -1; init_tree_desc(&desc, tree->buffer, tree->size); @@ -58,7 +58,11 @@ int read_tree_at(struct repository *r, oid_to_hex(&entry.oid), base->buf, entry.path); - if (parse_commit(commit)) + // FIXME: This is the wrong repo instance (it refers to the superproject) + // it will always fail as is (will fix in later patch) + // This current codepath isn't executed by any existing callbacks + // so it wouldn't show up as an issue at this time. + if (repo_parse_commit(r, commit)) die("Invalid commit %s in submodule path %s%s", oid_to_hex(&entry.oid), base->buf, entry.path); @@ -121,7 +125,7 @@ int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) return 0; } -int parse_tree_gently(struct tree *item, int quiet_on_missing) +int repo_parse_tree_gently(struct repository *r, struct tree *item, int quiet_on_missing) { enum object_type type; void *buffer; @@ -129,7 +133,7 @@ int parse_tree_gently(struct tree *item, int quiet_on_missing) if (item->object.parsed) return 0; - buffer = read_object_file(&item->object.oid, &type, &size); + buffer = repo_read_object_file(r, &item->object.oid, &type, &size); if (!buffer) return quiet_on_missing ? -1 : error("Could not read %s", @@ -149,9 +153,8 @@ void free_tree_buffer(struct tree *tree) tree->object.parsed = 0; } -struct tree *parse_tree_indirect(const struct object_id *oid) +struct tree *repo_parse_tree_indirect(struct repository *r, const struct object_id *oid) { - struct repository *r = the_repository; struct object *obj = parse_object(r, oid); return (struct tree *)repo_peel_to_type(r, NULL, 0, obj, OBJ_TREE); } diff --git a/tree.h b/tree.h index 6efff003e2120e..cc6402e473864e 100644 --- a/tree.h +++ b/tree.h @@ -18,15 +18,21 @@ struct tree *lookup_tree(struct repository *r, const struct object_id *oid); int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size); -int parse_tree_gently(struct tree *tree, int quiet_on_missing); -static inline int parse_tree(struct tree *tree) +int repo_parse_tree_gently(struct repository *r, struct tree *tree, int quiet_on_missing); +static inline int repo_parse_tree(struct repository *r, struct tree *tree) { - return parse_tree_gently(tree, 0); + return repo_parse_tree_gently(r, tree, 0); } + +#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS +#define parse_tree(tree) repo_parse_tree(the_repository, tree) +#define parse_tree_gently(tree, quiet_on_missing) repo_parse_tree_gently(the_repository, tree, quiet_on_missing) +#define parse_tree_indirect(oid) repo_parse_tree_indirect(the_repository, oid) +#endif void free_tree_buffer(struct tree *tree); /* Parses and returns the tree in the given ent, chasing tags and commits. */ -struct tree *parse_tree_indirect(const struct object_id *oid); +struct tree *repo_parse_tree_indirect(struct repository *r, const struct object_id *oid); int cmp_cache_name_compare(const void *a_, const void *b_); From 2291e0f9b5c61f9668b206b85368829db9384bb3 Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sat, 15 Oct 2022 10:18:42 -0400 Subject: [PATCH 2/9] tree: update cases to use repo_ tree methods For cases which had already had a repository instance, update those to use the repo_parse_tree* methods. Leave the remaining invocations that were already using the_repository alone. Signed-off-by: Heather Lapointe --- merge.c | 4 ++-- reset.c | 2 +- revision.c | 4 ++-- sequencer.c | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/merge.c b/merge.c index 2382ff66d351cc..1efc4440c03adc 100644 --- a/merge.c +++ b/merge.c @@ -63,12 +63,12 @@ int checkout_fast_forward(struct repository *r, memset(&trees, 0, sizeof(trees)); memset(&t, 0, sizeof(t)); - trees[nr_trees] = parse_tree_indirect(head); + trees[nr_trees] = repo_parse_tree_indirect(r, head); if (!trees[nr_trees++]) { rollback_lock_file(&lock_file); return -1; } - trees[nr_trees] = parse_tree_indirect(remote); + trees[nr_trees] = repo_parse_tree_indirect(r, remote); if (!trees[nr_trees++]) { rollback_lock_file(&lock_file); return -1; diff --git a/reset.c b/reset.c index e3383a93343e3d..a0ac5e8a68439d 100644 --- a/reset.c +++ b/reset.c @@ -153,7 +153,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts) goto leave_reset_head; } - tree = parse_tree_indirect(oid); + tree = repo_parse_tree_indirect(r, oid); prime_cache_tree(r, r->index, tree); if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) { diff --git a/revision.c b/revision.c index 36e31942ceec3c..dab5ddaf0391b7 100644 --- a/revision.c +++ b/revision.c @@ -74,7 +74,7 @@ static void mark_tree_contents_uninteresting(struct repository *r, struct tree_desc desc; struct name_entry entry; - if (parse_tree_gently(tree, 1) < 0) + if (repo_parse_tree_gently(r, tree, 1) < 0) return; init_tree_desc(&desc, tree->buffer, tree->size); @@ -181,7 +181,7 @@ static void add_children_by_path(struct repository *r, if (!tree) return; - if (parse_tree_gently(tree, 1) < 0) + if (repo_parse_tree_gently(r, tree, 1) < 0) return; init_tree_desc(&desc, tree->buffer, tree->size); diff --git a/sequencer.c b/sequencer.c index a4d85f1fbdd4cb..a4c09dfa18298a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -685,9 +685,9 @@ static int do_recursive_merge(struct repository *r, o.buffer_output = 2; o.show_rename_progress = 1; - head_tree = parse_tree_indirect(head); - next_tree = next ? get_commit_tree(next) : empty_tree(r); - base_tree = base ? get_commit_tree(base) : empty_tree(r); + head_tree = repo_parse_tree_indirect(r, head); + next_tree = next ? repo_get_commit_tree(r, next) : empty_tree(r); + base_tree = base ? repo_get_commit_tree(r, base) : empty_tree(r); for (i = 0; i < opts->xopts_nr; i++) parse_merge_opt(&o, opts->xopts[i]); From 9a07c6932f4c7ef844df1fc4f5b6b9feb1810135 Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sun, 16 Oct 2022 15:12:52 -0400 Subject: [PATCH 3/9] tree: increase test coverage for tree.c This highlights some buggy behavior from read_tree for submodules that was not being executed. This introduces a test-tool tree-read-tree-at command (the complex name is because it is not related to the read-tree command). Signed-off-by: Heather Lapointe --- Makefile | 1 + t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/helper/test-tree-read-tree-at.c | 40 +++++++++++++++++++ t/t1023-tree-read-tree-at.sh | 65 +++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 t/helper/test-tree-read-tree-at.c create mode 100755 t/t1023-tree-read-tree-at.sh diff --git a/Makefile b/Makefile index 6bfb62cbe941fc..52d17ca7276583 100644 --- a/Makefile +++ b/Makefile @@ -788,6 +788,7 @@ TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o TEST_BUILTINS_OBJS += test-submodule.o TEST_BUILTINS_OBJS += test-subprocess.o TEST_BUILTINS_OBJS += test-trace2.o +TEST_BUILTINS_OBJS += test-tree-read-tree-at.o TEST_BUILTINS_OBJS += test-urlmatch-normalization.o TEST_BUILTINS_OBJS += test-userdiff.o TEST_BUILTINS_OBJS += test-wildmatch.o diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index d1d013bcd920b1..a8a9bedec5fc85 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -82,6 +82,7 @@ static struct test_cmd cmds[] = { { "submodule-nested-repo-config", cmd__submodule_nested_repo_config }, { "subprocess", cmd__subprocess }, { "trace2", cmd__trace2 }, + { "tree-read-tree-at", cmd__tree_read_tree_at }, { "userdiff", cmd__userdiff }, { "urlmatch-normalization", cmd__urlmatch_normalization }, { "xml-encode", cmd__xml_encode }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 6b46b6444b657c..409fddfaeb86c3 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -76,6 +76,7 @@ int cmd__submodule_config(int argc, const char **argv); int cmd__submodule_nested_repo_config(int argc, const char **argv); int cmd__subprocess(int argc, const char **argv); int cmd__trace2(int argc, const char **argv); +int cmd__tree_read_tree_at(int argc, const char **argv); int cmd__userdiff(int argc, const char **argv); int cmd__urlmatch_normalization(int argc, const char **argv); int cmd__xml_encode(int argc, const char **argv); diff --git a/t/helper/test-tree-read-tree-at.c b/t/helper/test-tree-read-tree-at.c new file mode 100644 index 00000000000000..bba759bb26400c --- /dev/null +++ b/t/helper/test-tree-read-tree-at.c @@ -0,0 +1,40 @@ +/* This tests tree.c's read_tree / read_tree_at. +We call it tree-read-tree-at to disambiguate with the read-tree tool. +*/ +#include "cache.h" +#include "pathspec.h" +#include "test-tool.h" +#include "tree.h" + +static int test_handle_entry(const struct object_id *oid, + struct strbuf *base, const char *filename, + unsigned mode, void *context UNUSED) { + printf("%i %s %s%s\n", mode, oid_to_hex(oid), base->buf, filename); + if (S_ISDIR(mode) || S_ISGITLINK(mode)) { + return READ_TREE_RECURSIVE; + } + return 0; +} + +int cmd__tree_read_tree_at(int argc UNUSED, const char **argv) +{ + struct pathspec pathspec; + struct tree *tree; + struct repository *repo; + struct object_id oid; + + setup_git_directory(); + repo = the_repository; + assert(repo); + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL, + "", argv); + + assert(repo_get_oid(repo, "HEAD", &oid) == 0); + tree = repo_parse_tree_indirect(repo, &oid); + assert(tree); + pathspec.recurse_submodules = 1; + read_tree(repo, tree, &pathspec, test_handle_entry, NULL); + return 0; +} diff --git a/t/t1023-tree-read-tree-at.sh b/t/t1023-tree-read-tree-at.sh new file mode 100755 index 00000000000000..9e5ce3abb4bac6 --- /dev/null +++ b/t/t1023-tree-read-tree-at.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +# tests for tree.c (not read-tree.c) +test_description='Test read_tree / read_tree_at' +. ./test-lib.sh + +test_expect_success 'read_tree basic' ' + rm -rf walk_tree_basic && + git init walk_tree_basic && + ( + cd walk_tree_basic && + set -x && + + mkdir -p dir1/dirA && + mkdir -p dir1/dirB && + mkdir -p dir2 && + echo "file1" > file1.txt && + echo "file2" > file2.txt && + # uncommitted + echo "file3" > file3.txt && + + echo "file1A1" > dir1/dirA/file1.txt && + git add file1.txt file2.txt dir1/dirA/file1.txt && + git commit -m "initial commit" && + + test-tool tree-read-tree-at . > walk1.txt && + grep " file1.txt" walk1.txt && + ! grep " file3.txt" walk1.txt && + ! grep " dir1/dirB" walk1.txt && + grep " dir1/dirA/file1.txt" walk1.txt + ) +' + +test_expect_success 'read_tree submodules' ' + rm -rf walk_tree_submodules && + git init submodule1 && + ( + cd submodule1 && + mkdir -p dir1/dirA && + echo "dir2/sub1/file1.txt" > file1.txt && + echo "dir2/sub1/file1A1.txt" > dir1/dirA/file1.txt && + git add file1.txt dir1/dirA/file1.txt && + git commit -m "initial commit" + ) && + git init walk_tree_submodules && + ( + cd walk_tree_submodules && + + mkdir -p dir2 && + echo "file1" > file1.txt && + echo "dir2/file2" > dir2/file2.txt && + git add file1.txt dir2/file2.txt && + git commit -m "initial commit" && + + git submodule add ../submodule1 dir2/sub1 && + git commit -m "add submodule1" && + + test-tool tree-read-tree-at . > walk2.txt && + grep " file1.txt" walk2.txt && + grep " dir2/sub1/file1.txt" walk2.txt && + grep " dir2/sub1/dir1/dirA/file1.txt" walk2.txt + ) +' + +test_done From d3d1738e670d5dbf1378fc5c3209b2e98234a771 Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sun, 16 Oct 2022 15:27:22 -0400 Subject: [PATCH 4/9] tree: handle submodule case for read_tree_at properly This supports traversal into an actual submodule for read_tree_at. The logic is blocked on pathspec->recurse_submodules now, but previously hadn't been executed due to all fn() cases returning early for submodules. Signed-off-by: Heather Lapointe --- tree.c | 88 ++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/tree.c b/tree.c index 13f9173d45eb9a..2a087c010f9a80 100644 --- a/tree.c +++ b/tree.c @@ -8,6 +8,7 @@ #include "alloc.h" #include "tree-walk.h" #include "repository.h" +#include "pathspec.h" const char *tree_type = "tree"; @@ -47,40 +48,73 @@ int read_tree_at(struct repository *r, return -1; } - if (S_ISDIR(entry.mode)) + if (S_ISDIR(entry.mode)) { oidcpy(&oid, &entry.oid); - else if (S_ISGITLINK(entry.mode)) { - struct commit *commit; - commit = lookup_commit(r, &entry.oid); + len = tree_entry_len(&entry); + strbuf_add(base, entry.path, len); + strbuf_addch(base, '/'); + retval = read_tree_at(r, lookup_tree(r, &oid), + base, pathspec, + fn, context); + strbuf_setlen(base, oldlen); + if (retval) + return -1; + } else if (pathspec->recurse_submodules && S_ISGITLINK(entry.mode)) { + struct commit *commit; + struct repository subrepo; + struct repository* subrepo_p = &subrepo; + struct tree* submodule_tree; + char *submodule_rel_path; + int name_base_len = 0; + + len = tree_entry_len(&entry); + strbuf_add(base, entry.path, len); + submodule_rel_path = base->buf; + // repo_submodule_init expects a path relative to submodule_prefix + if (r->submodule_prefix) { + name_base_len = strlen(r->submodule_prefix); + // we should always expect to start with submodule_prefix + assert(!strncmp(submodule_rel_path, r->submodule_prefix, name_base_len)); + // strip the prefix + submodule_rel_path += name_base_len; + // if submodule_prefix doesn't end with a /, we want to get rid of that too + if (is_dir_sep(submodule_rel_path[0])) { + submodule_rel_path++; + } + } + + if (repo_submodule_init(subrepo_p, r, submodule_rel_path, null_oid())) + die("couldn't init submodule %s", base->buf); + + if (repo_read_index(subrepo_p) < 0) + die("index file corrupt"); + + commit = lookup_commit(subrepo_p, &entry.oid); if (!commit) - die("Commit %s in submodule path %s%s not found", + die("Commit %s in submodule path %s not found", oid_to_hex(&entry.oid), - base->buf, entry.path); - - // FIXME: This is the wrong repo instance (it refers to the superproject) - // it will always fail as is (will fix in later patch) - // This current codepath isn't executed by any existing callbacks - // so it wouldn't show up as an issue at this time. - if (repo_parse_commit(r, commit)) - die("Invalid commit %s in submodule path %s%s", + base->buf); + + if (repo_parse_commit(subrepo_p, commit)) + die("Invalid commit %s in submodule path %s", oid_to_hex(&entry.oid), - base->buf, entry.path); + base->buf); - oidcpy(&oid, get_commit_tree_oid(commit)); - } - else - continue; + submodule_tree = repo_get_commit_tree(subrepo_p, commit); + oidcpy(&oid, submodule_tree ? &submodule_tree->object.oid : NULL); - len = tree_entry_len(&entry); - strbuf_add(base, entry.path, len); - strbuf_addch(base, '/'); - retval = read_tree_at(r, lookup_tree(r, &oid), - base, pathspec, - fn, context); - strbuf_setlen(base, oldlen); - if (retval) - return -1; + strbuf_addch(base, '/'); + + retval = read_tree_at(subrepo_p, lookup_tree(subrepo_p, &oid), + base, pathspec, + fn, context); + if (retval) + die("failed to read tree for %s", base->buf); + strbuf_setlen(base, oldlen); + repo_clear(subrepo_p); + } + // else, this is a file (or a submodule, but no pathspec->recurse_submodules) } return 0; } From 376345fdf66b274a7ce3dfff4d0a2b185858147c Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sun, 16 Oct 2022 15:45:57 -0400 Subject: [PATCH 5/9] tree: add repository parameter to read_tree_fn_t Add a repo paramter to read_tree_fn_t because most callbacks do need some repo instance. This avoids having to use the_repository functions otherwise and improves repo context switching for submodules. Signed-off-by: Heather Lapointe --- archive.c | 11 ++++++---- builtin/checkout.c | 4 +++- builtin/log.c | 4 +++- builtin/ls-files.c | 8 ++++++-- builtin/ls-tree.c | 34 ++++++++++++++++++++----------- merge-recursive.c | 4 +++- sparse-index.c | 4 +++- t/helper/test-tree-read-tree-at.c | 3 ++- tree.c | 2 +- tree.h | 2 +- wt-status.c | 4 +++- 11 files changed, 54 insertions(+), 26 deletions(-) diff --git a/archive.c b/archive.c index 61a79e4a2270df..15f3ac92dfc48e 100644 --- a/archive.c +++ b/archive.c @@ -225,7 +225,9 @@ static int write_directory(struct archiver_context *c) return ret ? -1 : 0; } -static int queue_or_write_archive_entry(const struct object_id *oid, +static int queue_or_write_archive_entry( + struct repository *r, + const struct object_id *oid, struct strbuf *base, const char *filename, unsigned mode, void *context) { @@ -246,7 +248,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid, /* Borrow base, but restore its original value when done. */ strbuf_addstr(base, filename); strbuf_addch(base, '/'); - check = get_archive_attrs(c->args->repo->index, base->buf); + check = get_archive_attrs(r->index, base->buf); strbuf_setlen(base, baselen); if (check_attr_export_ignore(check)) @@ -382,7 +384,8 @@ struct path_exists_context { struct archiver_args *args; }; -static int reject_entry(const struct object_id *oid UNUSED, +static int reject_entry( + struct repository *r, const struct object_id *oid UNUSED, struct strbuf *base, const char *filename, unsigned mode, void *context) @@ -394,7 +397,7 @@ static int reject_entry(const struct object_id *oid UNUSED, struct strbuf sb = STRBUF_INIT; strbuf_addbuf(&sb, base); strbuf_addstr(&sb, filename); - if (!match_pathspec(ctx->args->repo->index, + if (!match_pathspec(r->index, &ctx->pathspec, sb.buf, sb.len, 0, NULL, 1)) ret = READ_TREE_RECURSIVE; diff --git a/builtin/checkout.c b/builtin/checkout.c index 2a132392fbe747..ee98858afe6dcf 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -124,7 +124,9 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm } -static int update_some(const struct object_id *oid, struct strbuf *base, +static int update_some( + struct repository *r UNUSED, + const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context UNUSED) { int len; diff --git a/builtin/log.c b/builtin/log.c index ee19dc5d450c57..608a448fe4d955 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -698,7 +698,9 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev) return 0; } -static int show_tree_object(const struct object_id *oid UNUSED, +static int show_tree_object( + struct repository *r UNUSED, + const struct object_id *oid UNUSED, struct strbuf *base UNUSED, const char *pathname, unsigned mode, void *context) diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 4cf8a2364835c9..fbb07fa08c293c 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -533,7 +533,9 @@ static int read_one_entry_opt(struct index_state *istate, return add_index_entry(istate, ce, opt); } -static int read_one_entry(const struct object_id *oid, struct strbuf *base, +static int read_one_entry( + struct repository *r UNUSED, + const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context) { @@ -547,7 +549,9 @@ static int read_one_entry(const struct object_id *oid, struct strbuf *base, * This is used when the caller knows there is no existing entries at * the stage that will conflict with the entry being added. */ -static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base, +static int read_one_entry_quick( + struct repository *r UNUSED, + const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context) { diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index c3ea09281afebe..dd571abad1ca55 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -141,8 +141,10 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname return 0; } -static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context UNUSED) +static int show_tree_fmt( + struct repository *r UNUSED, + const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, void *context UNUSED) { size_t baselen; int recurse = 0; @@ -211,9 +213,11 @@ static void show_tree_common_default_long(struct strbuf *base, strbuf_setlen(base, baselen); } -static int show_tree_default(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, - void *context UNUSED) +static int show_tree_default( + struct repository *r, + const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context UNUSED) { int early; int recurse; @@ -224,12 +228,14 @@ static int show_tree_default(const struct object_id *oid, struct strbuf *base, return early; printf("%06o %s %s\t", data.mode, type_name(data.type), - find_unique_abbrev(data.oid, abbrev)); + repo_find_unique_abbrev(r, data.oid, abbrev)); show_tree_common_default_long(base, pathname, data.base->len); return recurse; } -static int show_tree_long(const struct object_id *oid, struct strbuf *base, +static int show_tree_long( + struct repository *r, + const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context UNUSED) { @@ -244,7 +250,7 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base, if (data.type == OBJ_BLOB) { unsigned long size; - if (oid_object_info(the_repository, data.oid, &size) == OBJ_BAD) + if (oid_object_info(r, data.oid, &size) == OBJ_BAD) xsnprintf(size_text, sizeof(size_text), "BAD"); else xsnprintf(size_text, sizeof(size_text), @@ -254,12 +260,14 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base, } printf("%06o %s %s %7s\t", data.mode, type_name(data.type), - find_unique_abbrev(data.oid, abbrev), size_text); + repo_find_unique_abbrev(r, data.oid, abbrev), size_text); show_tree_common_default_long(base, pathname, data.base->len); return recurse; } -static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, +static int show_tree_name_only( + struct repository *r UNUSED, + const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context UNUSED) { @@ -280,7 +288,9 @@ static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, return recurse; } -static int show_tree_object(const struct object_id *oid, struct strbuf *base, +static int show_tree_object( + struct repository *r, + const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context UNUSED) { @@ -292,7 +302,7 @@ static int show_tree_object(const struct object_id *oid, struct strbuf *base, if (early >= 0) return early; - printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination); + printf("%s%c", repo_find_unique_abbrev(r, oid, abbrev), line_termination); return recurse; } diff --git a/merge-recursive.c b/merge-recursive.c index 4ddd3adea003e3..dccde2766557b2 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -456,7 +456,9 @@ static void unpack_trees_finish(struct merge_options *opt) clear_unpack_trees_porcelain(&opt->priv->unpack_opts); } -static int save_files_dirs(const struct object_id *oid UNUSED, +static int save_files_dirs( + struct repository *r UNUSED, + const struct object_id *oid UNUSED, struct strbuf *base, const char *path, unsigned int mode, void *context) { diff --git a/sparse-index.c b/sparse-index.c index e4a54ce19433dd..4187c7ce9c4271 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -232,7 +232,9 @@ static void set_index_entry(struct index_state *istate, int nr, struct cache_ent add_name_hash(istate, ce); } -static int add_path_to_index(const struct object_id *oid, +static int add_path_to_index( + struct repository *r UNUSED, + const struct object_id *oid, struct strbuf *base, const char *path, unsigned int mode, void *context) { diff --git a/t/helper/test-tree-read-tree-at.c b/t/helper/test-tree-read-tree-at.c index bba759bb26400c..d2bcc8c849ad2c 100644 --- a/t/helper/test-tree-read-tree-at.c +++ b/t/helper/test-tree-read-tree-at.c @@ -6,7 +6,8 @@ We call it tree-read-tree-at to disambiguate with the read-tree tool. #include "test-tool.h" #include "tree.h" -static int test_handle_entry(const struct object_id *oid, +static int test_handle_entry( + struct repository *r UNUSED, const struct object_id *oid, struct strbuf *base, const char *filename, unsigned mode, void *context UNUSED) { printf("%i %s %s%s\n", mode, oid_to_hex(oid), base->buf, filename); diff --git a/tree.c b/tree.c index 2a087c010f9a80..17c3af819e2d80 100644 --- a/tree.c +++ b/tree.c @@ -38,7 +38,7 @@ int read_tree_at(struct repository *r, continue; } - switch (fn(&entry.oid, base, + switch (fn(r, &entry.oid, base, entry.path, entry.mode, context)) { case 0: continue; diff --git a/tree.h b/tree.h index cc6402e473864e..94b1e11d9eb144 100644 --- a/tree.h +++ b/tree.h @@ -37,7 +37,7 @@ struct tree *repo_parse_tree_indirect(struct repository *r, const struct object_ int cmp_cache_name_compare(const void *a_, const void *b_); #define READ_TREE_RECURSIVE 1 -typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const char *, unsigned int, void *); +typedef int (*read_tree_fn_t)(struct repository *r, const struct object_id *, struct strbuf *, const char *, unsigned int, void *); int read_tree_at(struct repository *r, struct tree *tree, struct strbuf *base, diff --git a/wt-status.c b/wt-status.c index 5813174896cc9a..cff2a780f3221d 100644 --- a/wt-status.c +++ b/wt-status.c @@ -665,7 +665,9 @@ static void wt_status_collect_changes_index(struct wt_status *s) release_revisions(&rev); } -static int add_file_to_list(const struct object_id *oid, +static int add_file_to_list( + struct repository *r UNUSED, + const struct object_id *oid, struct strbuf *base, const char *path, unsigned int mode, void *context) { From 1b9b049d64fc4ea919c94e57b74a1760b3837892 Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sun, 16 Oct 2022 16:05:01 -0400 Subject: [PATCH 6/9] archive: pass repo objects to write_archive handlers Use contextual repos instead of the_repository or args->repo to ensure that submodules will be handled correctly since they use multiple repo instances. Signed-off-by: Heather Lapointe --- archive-tar.c | 15 ++++++++++----- archive-zip.c | 15 +++++++++------ archive.c | 38 ++++++++++++++++++++++---------------- archive.h | 14 +++++++++++--- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/archive-tar.c b/archive-tar.c index 3e4822b68409b8..5a2d42ff2290bd 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -18,6 +18,7 @@ static unsigned long offset; static int tar_umask = 002; static int write_tar_filter_archive(const struct archiver *ar, + struct repository *repo, struct archiver_args *args); /* @@ -246,7 +247,9 @@ static void write_extended_header(struct archiver_args *args, write_blocked(buffer, size); } -static int write_tar_entry(struct archiver_args *args, +static int write_tar_entry( + struct repository *repo, + struct archiver_args *args, const struct object_id *oid, const char *path, size_t pathlen, unsigned int mode, @@ -316,7 +319,7 @@ static int write_tar_entry(struct archiver_args *args, if (buffer) write_blocked(buffer, size); else - err = stream_blocked(args->repo, oid); + err = stream_blocked(repo, oid); } return err; } @@ -422,12 +425,13 @@ static int git_tar_config(const char *var, const char *value, void *cb) } static int write_tar_archive(const struct archiver *ar UNUSED, + struct repository *repo, struct archiver_args *args) { int err = 0; write_global_extended_header(args); - err = write_archive_entries(args, write_tar_entry); + err = write_archive_entries(repo, args, write_tar_entry); if (!err) write_trailer(); return err; @@ -462,6 +466,7 @@ static void tgz_write_block(const void *data) static const char internal_gzip_command[] = "git archive gzip"; static int write_tar_filter_archive(const struct archiver *ar, + struct repository *repo, struct archiver_args *args) { #if ZLIB_VERNUM >= 0x1221 @@ -484,7 +489,7 @@ static int write_tar_filter_archive(const struct archiver *ar, gzstream.next_out = outbuf; gzstream.avail_out = sizeof(outbuf); - r = write_tar_archive(ar, args); + r = write_tar_archive(ar, repo, args); tgz_deflate(Z_FINISH); git_deflate_end(&gzstream); @@ -506,7 +511,7 @@ static int write_tar_filter_archive(const struct archiver *ar, die_errno(_("unable to redirect descriptor")); close(filter.in); - r = write_tar_archive(ar, args); + r = write_tar_archive(ar, repo, args); close(1); if (finish_command(&filter) != 0) diff --git a/archive-zip.c b/archive-zip.c index 0456f1ebf15c83..2c1f943a6ccc91 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -283,7 +283,9 @@ static int entry_is_binary(struct index_state *istate, const char *path, #define STREAM_BUFFER_SIZE (1024 * 16) -static int write_zip_entry(struct archiver_args *args, +static int write_zip_entry( + struct repository *repo, + struct archiver_args *args, const struct object_id *oid, const char *path, size_t pathlen, unsigned int mode, @@ -340,7 +342,7 @@ static int write_zip_entry(struct archiver_args *args, if (!buffer) { enum object_type type; - stream = open_istream(args->repo, oid, &type, &size, + stream = open_istream(repo, oid, &type, &size, NULL); if (!stream) return error(_("cannot stream blob %s"), @@ -349,7 +351,7 @@ static int write_zip_entry(struct archiver_args *args, out = NULL; } else { crc = crc32(crc, buffer, size); - is_binary = entry_is_binary(args->repo->index, + is_binary = entry_is_binary(repo->index, path_without_prefix, buffer, size); out = buffer; @@ -426,7 +428,7 @@ static int write_zip_entry(struct archiver_args *args, break; crc = crc32(crc, buf, readlen); if (is_binary == -1) - is_binary = entry_is_binary(args->repo->index, + is_binary = entry_is_binary(repo->index, path_without_prefix, buf, readlen); write_or_die(1, buf, readlen); @@ -459,7 +461,7 @@ static int write_zip_entry(struct archiver_args *args, break; crc = crc32(crc, buf, readlen); if (is_binary == -1) - is_binary = entry_is_binary(args->repo->index, + is_binary = entry_is_binary(repo->index, path_without_prefix, buf, readlen); @@ -619,6 +621,7 @@ static int archive_zip_config(const char *var, const char *value, } static int write_zip_archive(const struct archiver *ar UNUSED, + struct repository *repo, struct archiver_args *args) { int err; @@ -629,7 +632,7 @@ static int write_zip_archive(const struct archiver *ar UNUSED, strbuf_init(&zip_dir, 0); - err = write_archive_entries(args, write_zip_entry); + err = write_archive_entries(repo, args, write_zip_entry); if (!err) write_zip_trailer(args->commit_oid); diff --git a/archive.c b/archive.c index 15f3ac92dfc48e..2cca7bc5c8acbd 100644 --- a/archive.c +++ b/archive.c @@ -134,7 +134,9 @@ static int check_attr_export_subst(const struct attr_check *check) return check && ATTR_TRUE(check->items[1].value); } -static int write_archive_entry(const struct object_id *oid, const char *base, +static int write_archive_entry( + struct repository *repo, + const struct object_id *oid, const char *base, int baselen, const char *filename, unsigned mode, void *context) { @@ -160,7 +162,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base, if (!S_ISDIR(mode)) { const struct attr_check *check; - check = get_archive_attrs(args->repo->index, path_without_prefix); + check = get_archive_attrs(repo->index, path_without_prefix); if (check_attr_export_ignore(check)) return 0; args->convert = check_attr_export_subst(check); @@ -169,7 +171,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base, if (S_ISDIR(mode) || S_ISGITLINK(mode)) { if (args->verbose) fprintf(stderr, "%.*s\n", (int)path.len, path.buf); - err = write_entry(args, oid, path.buf, path.len, mode, NULL, 0); + err = write_entry(repo, args, oid, path.buf, path.len, mode, NULL, 0); if (err) return err; return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); @@ -180,14 +182,14 @@ static int write_archive_entry(const struct object_id *oid, const char *base, /* Stream it? */ if (S_ISREG(mode) && !args->convert && - oid_object_info(args->repo, oid, &size) == OBJ_BLOB && + oid_object_info(repo, oid, &size) == OBJ_BLOB && size > big_file_threshold) - return write_entry(args, oid, path.buf, path.len, mode, NULL, size); + return write_entry(repo, args, oid, path.buf, path.len, mode, NULL, size); buffer = object_file_to_archive(args, path.buf, oid, mode, &type, &size); if (!buffer) return error(_("cannot read '%s'"), oid_to_hex(oid)); - err = write_entry(args, oid, path.buf, path.len, mode, buffer, size); + err = write_entry(repo, args, oid, path.buf, path.len, mode, buffer, size); free(buffer); return err; } @@ -207,7 +209,9 @@ static void queue_directory(const struct object_id *oid, oidcpy(&d->oid, oid); } -static int write_directory(struct archiver_context *c) +static int write_directory( + struct repository *repo, + struct archiver_context *c) { struct directory *d = c->bottom; int ret; @@ -217,8 +221,8 @@ static int write_directory(struct archiver_context *c) c->bottom = d->up; d->path[d->len - 1] = '\0'; /* no trailing slash */ ret = - write_directory(c) || - write_archive_entry(&d->oid, d->path, d->baselen, + write_directory(repo, c) || + write_archive_entry(repo, &d->oid, d->path, d->baselen, d->path + d->baselen, d->mode, c) != READ_TREE_RECURSIVE; free(d); @@ -257,9 +261,9 @@ static int queue_or_write_archive_entry( return READ_TREE_RECURSIVE; } - if (write_directory(c)) + if (write_directory(r, c)) return -1; - return write_archive_entry(oid, base->buf, base->len, filename, mode, + return write_archive_entry(r, oid, base->buf, base->len, filename, mode, context); } @@ -269,7 +273,9 @@ struct extra_file_info { void *content; }; -int write_archive_entries(struct archiver_args *args, +int write_archive_entries( + struct repository *repo, + struct archiver_args *args, write_archive_entry_fn_t write_entry) { struct archiver_context context; @@ -290,7 +296,7 @@ int write_archive_entries(struct archiver_args *args, len--; if (args->verbose) fprintf(stderr, "%.*s\n", (int)len, args->base); - err = write_entry(args, &args->tree->object.oid, args->base, + err = write_entry(repo, args, &args->tree->object.oid, args->base, len, 040777, NULL, 0); if (err) return err; @@ -345,12 +351,12 @@ int write_archive_entries(struct archiver_args *args, if (strbuf_read_file(&content, path, info->stat.st_size) < 0) err = error_errno(_("cannot read '%s'"), path); else - err = write_entry(args, &fake_oid, path_in_archive.buf, + err = write_entry(repo, args, &fake_oid, path_in_archive.buf, path_in_archive.len, canon_mode(info->stat.st_mode), content.buf, content.len); } else { - err = write_entry(args, &fake_oid, + err = write_entry(repo, args, &fake_oid, path, strlen(path), canon_mode(info->stat.st_mode), info->content, info->stat.st_size); @@ -711,7 +717,7 @@ int write_archive(int argc, const char **argv, const char *prefix, parse_treeish_arg(argv, &args, prefix, remote); parse_pathspec_arg(argv + 1, &args); - rc = ar->write_archive(ar, &args); + rc = ar->write_archive(ar, repo, &args); string_list_clear_func(&args.extra_files, extra_file_info_clear); free(args.refname); diff --git a/archive.h b/archive.h index 08bed3ed3af6b0..bfbbd3274bdcce 100644 --- a/archive.h +++ b/archive.h @@ -41,7 +41,10 @@ const char *archive_format_from_filename(const char *filename); #define ARCHIVER_HIGH_COMPRESSION_LEVELS 4 struct archiver { const char *name; - int (*write_archive)(const struct archiver *, struct archiver_args *); + int (*write_archive)( + const struct archiver *, + struct repository *, + struct archiver_args *); unsigned flags; char *filter_command; }; @@ -51,12 +54,17 @@ void init_tar_archiver(void); void init_zip_archiver(void); void init_archivers(void); -typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, +typedef int (*write_archive_entry_fn_t)( + struct repository *repo, + struct archiver_args *args, const struct object_id *oid, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size); -int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry); +int write_archive_entries( + struct repository *repo, + struct archiver_args *args, + write_archive_entry_fn_t write_entry); #endif /* ARCHIVE_H */ From 2443c9b1b6efce4340f01930c179d2fafbc5bbb3 Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sun, 16 Oct 2022 16:13:19 -0400 Subject: [PATCH 7/9] archive: remove global repository from archive_args Remove archive_args.repo to ensure all functions are using local repository instances. Since all functions now have access to repo, this access isn't needed anymore. The main issue is that submodules do not use the same repo as the subproject repo that is being passed around contextually. Signed-off-by: Heather Lapointe --- archive.c | 51 +++++++++++++++++++++++++++++---------------------- archive.h | 1 - 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/archive.c b/archive.c index 2cca7bc5c8acbd..34549d849f16e9 100644 --- a/archive.c +++ b/archive.c @@ -36,7 +36,9 @@ void init_archivers(void) init_zip_archiver(); } -static void format_subst(const struct commit *commit, +static void format_subst( + struct repository *repo, + const struct commit *commit, const char *src, size_t len, struct strbuf *buf, struct pretty_print_context *ctx) { @@ -59,7 +61,7 @@ static void format_subst(const struct commit *commit, strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf, ctx); + repo_format_commit_message(repo, commit, fmt.buf, buf, ctx); len -= c + 1 - src; src = c + 1; } @@ -68,7 +70,9 @@ static void format_subst(const struct commit *commit, free(to_free); } -static void *object_file_to_archive(const struct archiver_args *args, +static void *object_file_to_archive( + struct repository *repo, + const struct archiver_args *args, const char *path, const struct object_id *oid, unsigned int mode, @@ -84,15 +88,15 @@ static void *object_file_to_archive(const struct archiver_args *args, (args->tree ? &args->tree->object.oid : NULL), oid); path += args->baselen; - buffer = read_object_file(oid, type, sizep); + buffer = repo_read_object_file(repo, oid, type, sizep); if (buffer && S_ISREG(mode)) { struct strbuf buf = STRBUF_INIT; size_t size = 0; strbuf_attach(&buf, buffer, *sizep, *sizep + 1); - convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta); + convert_to_working_tree(repo->index, path, buf.buf, buf.len, &buf, &meta); if (commit) - format_subst(commit, buf.buf, buf.len, &buf, args->pretty_ctx); + format_subst(repo, commit, buf.buf, buf.len, &buf, args->pretty_ctx); buffer = strbuf_detach(&buf, &size); *sizep = size; } @@ -186,7 +190,7 @@ static int write_archive_entry( size > big_file_threshold) return write_entry(repo, args, oid, path.buf, path.len, mode, NULL, size); - buffer = object_file_to_archive(args, path.buf, oid, mode, &type, &size); + buffer = object_file_to_archive(repo, args, path.buf, oid, mode, &type, &size); if (!buffer) return error(_("cannot read '%s'"), oid_to_hex(oid)); err = write_entry(repo, args, oid, path.buf, path.len, mode, buffer, size); @@ -313,8 +317,8 @@ int write_archive_entries( memset(&opts, 0, sizeof(opts)); opts.index_only = 1; opts.head_idx = -1; - opts.src_index = args->repo->index; - opts.dst_index = args->repo->index; + opts.src_index = repo->index; + opts.dst_index = repo->index; opts.fn = oneway_merge; init_tree_desc(&t, args->tree->buffer, args->tree->size); if (unpack_trees(1, &t, &opts)) @@ -322,7 +326,7 @@ int write_archive_entries( git_attr_set_direction(GIT_ATTR_INDEX); } - err = read_tree(args->repo, args->tree, + err = read_tree(repo, args->tree, &args->pathspec, queue_or_write_archive_entry, &context); @@ -412,7 +416,7 @@ static int reject_entry( return ret; } -static int path_exists(struct archiver_args *args, const char *path) +static int path_exists(struct repository *repo, struct archiver_args *args, const char *path) { const char *paths[] = { path, NULL }; struct path_exists_context ctx; @@ -421,14 +425,16 @@ static int path_exists(struct archiver_args *args, const char *path) ctx.args = args; parse_pathspec(&ctx.pathspec, 0, 0, "", paths); ctx.pathspec.recursive = 1; - ret = read_tree(args->repo, args->tree, + ret = read_tree(repo, args->tree, &ctx.pathspec, reject_entry, &ctx); clear_pathspec(&ctx.pathspec); return ret != 0; } -static void parse_pathspec_arg(const char **pathspec, +static void parse_pathspec_arg( + struct repository *repo, + const char **pathspec, struct archiver_args *ar_args) { /* @@ -442,14 +448,16 @@ static void parse_pathspec_arg(const char **pathspec, ar_args->pathspec.recursive = 1; if (pathspec) { while (*pathspec) { - if (**pathspec && !path_exists(ar_args, *pathspec)) + if (**pathspec && !path_exists(repo, ar_args, *pathspec)) die(_("pathspec '%s' did not match any files"), *pathspec); pathspec++; } } } -static void parse_treeish_arg(const char **argv, +static void parse_treeish_arg( + struct repository *repo, + const char **argv, struct archiver_args *ar_args, const char *prefix, int remote) { @@ -475,7 +483,7 @@ static void parse_treeish_arg(const char **argv, if (get_oid(name, &oid)) die(_("not a valid object name: %s"), name); - commit = lookup_commit_reference_gently(ar_args->repo, &oid, 1); + commit = lookup_commit_reference_gently(repo, &oid, 1); if (commit) { commit_oid = &commit->object.oid; archive_time = commit->date; @@ -484,7 +492,7 @@ static void parse_treeish_arg(const char **argv, archive_time = time(NULL); } - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(repo, &oid); if (!tree) die(_("not a tree object: %s"), oid_to_hex(&oid)); @@ -493,14 +501,14 @@ static void parse_treeish_arg(const char **argv, unsigned short mode; int err; - err = get_tree_entry(ar_args->repo, + err = get_tree_entry(repo, &tree->object.oid, prefix, &tree_oid, &mode); if (err || !S_ISDIR(mode)) die(_("current working directory is untracked")); - tree = parse_tree_indirect(&tree_oid); + tree = repo_parse_tree_indirect(repo, &tree_oid); } ar_args->refname = ref; ar_args->tree = tree; @@ -701,7 +709,6 @@ int write_archive(int argc, const char **argv, const char *prefix, ctx.abbrev = DEFAULT_ABBREV; ctx.describe_status = &describe_status; args.pretty_ctx = &ctx; - args.repo = repo; args.prefix = prefix; string_list_init_dup(&args.extra_files); argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote); @@ -714,8 +721,8 @@ int write_archive(int argc, const char **argv, const char *prefix, setup_git_directory(); } - parse_treeish_arg(argv, &args, prefix, remote); - parse_pathspec_arg(argv + 1, &args); + parse_treeish_arg(repo, argv, &args, prefix, remote); + parse_pathspec_arg(repo, argv + 1, &args); rc = ar->write_archive(ar, repo, &args); diff --git a/archive.h b/archive.h index bfbbd3274bdcce..540a3b12130ed1 100644 --- a/archive.h +++ b/archive.h @@ -8,7 +8,6 @@ struct repository; struct pretty_print_context; struct archiver_args { - struct repository *repo; char *refname; const char *prefix; const char *base; From 4672e3d958625cd76eb8056ab434e9a37f52661e Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sun, 16 Oct 2022 16:26:15 -0400 Subject: [PATCH 8/9] archive: add --recurse-submodules to git-archive command This makes it possible to include submodule contents in an archive command. The default behavior remains the same, do not write submodule contents to the resulting archive. Signed-off-by: Heather Lapointe --- Documentation/git-archive.txt | 6 +++++- archive.c | 36 +++++++++++++++++++++++++++++++++-- archive.h | 1 + 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 60c040988bb803..22f54428b98ac6 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git archive' [--format=] [--list] [--prefix=/] [] - [-o | --output=] [--worktree-attributes] + [-o | --output=] + [--recurse-submodules] [--worktree-attributes] [--remote= [--exec=]] [...] @@ -82,6 +83,9 @@ The file mode is limited to a regular file, and the option may be subject to platform-dependent command-line limits. For non-trivial cases, write an untracked file and use `--add-file` instead. +--recurse-submodules + Include submodules recursively in archive. + --worktree-attributes:: Look for attributes in .gitattributes files in the working tree as well (see <>). diff --git a/archive.c b/archive.c index 34549d849f16e9..f81ef741487567 100644 --- a/archive.c +++ b/archive.c @@ -10,6 +10,7 @@ #include "unpack-trees.h" #include "dir.h" #include "quote.h" +#include "submodule.h" static char const * const archive_usage[] = { N_("git archive [] [...]"), @@ -213,6 +214,25 @@ static void queue_directory(const struct object_id *oid, oidcpy(&d->oid, oid); } +static void queue_submodule( + struct repository *superproject, + const struct object_id *oid, + struct strbuf *base, const char *filename, + unsigned mode, struct archiver_context *c) +{ + struct repository subrepo; + + if (repo_submodule_init(&subrepo, superproject, filename, null_oid())) + return; + + if (repo_read_index(&subrepo) < 0) + die("index file corrupt"); + + queue_directory(oid, base, filename, mode, c); + + repo_clear(&subrepo); +} + static int write_directory( struct repository *repo, struct archiver_context *c) @@ -228,9 +248,11 @@ static int write_directory( write_directory(repo, c) || write_archive_entry(repo, &d->oid, d->path, d->baselen, d->path + d->baselen, d->mode, - c) != READ_TREE_RECURSIVE; + c); free(d); - return ret ? -1 : 0; + if (ret == READ_TREE_RECURSIVE) + return 0; + return ret; } static int queue_or_write_archive_entry( @@ -263,6 +285,11 @@ static int queue_or_write_archive_entry( return 0; queue_directory(oid, base, filename, mode, c); return READ_TREE_RECURSIVE; + } else if (c->args->recurse_submodules && S_ISGITLINK(mode)) { + if (is_submodule_active(r, filename)) { + queue_submodule(r, oid, base, filename, mode, c); + return READ_TREE_RECURSIVE; + } } if (write_directory(r, c)) @@ -446,6 +473,7 @@ static void parse_pathspec_arg( PATHSPEC_PREFER_FULL, "", pathspec); ar_args->pathspec.recursive = 1; + ar_args->pathspec.recurse_submodules = ar_args->recurse_submodules; if (pathspec) { while (*pathspec) { if (**pathspec && !path_exists(repo, ar_args, *pathspec)) @@ -609,6 +637,7 @@ static int parse_archive_args(int argc, const char **argv, int verbose = 0; int i; int list = 0; + int recurse_submodules = 0; int worktree_attributes = 0; struct option opts[] = { OPT_GROUP(""), @@ -623,6 +652,8 @@ static int parse_archive_args(int argc, const char **argv, add_file_cb, (intptr_t)&base }, OPT_STRING('o', "output", &output, N_("file"), N_("write the archive to this file")), + OPT_BOOL(0, "recurse-submodules", &recurse_submodules, + N_("include submodules in archive")), OPT_BOOL(0, "worktree-attributes", &worktree_attributes, N_("read .gitattributes in working directory")), OPT__VERBOSE(&verbose, N_("report archived files on stderr")), @@ -686,6 +717,7 @@ static int parse_archive_args(int argc, const char **argv, args->verbose = verbose; args->base = base; args->baselen = strlen(base); + args->recurse_submodules = recurse_submodules; args->worktree_attributes = worktree_attributes; return argc; diff --git a/archive.h b/archive.h index 540a3b12130ed1..1b21484dda6f19 100644 --- a/archive.h +++ b/archive.h @@ -18,6 +18,7 @@ struct archiver_args { timestamp_t time; struct pathspec pathspec; unsigned int verbose : 1; + unsigned int recurse_submodules : 1; unsigned int worktree_attributes : 1; unsigned int convert : 1; int compression_level; From f88ebbaf17cbf1a0b57336430bd43ade94406f38 Mon Sep 17 00:00:00 2001 From: Heather Lapointe Date: Sun, 16 Oct 2022 16:53:47 -0400 Subject: [PATCH 9/9] archive: add tests for git archive --recurse-submodules Ensuring functionality works with and without submodules. We expect --recurse-submodules to fail if there are uninitialized submodules present. Signed-off-by: Heather Lapointe --- archive.c | 2 +- t/t5005-archive-submodules.sh | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100755 t/t5005-archive-submodules.sh diff --git a/archive.c b/archive.c index f81ef741487567..b0a3181f7f5f81 100644 --- a/archive.c +++ b/archive.c @@ -179,7 +179,7 @@ static int write_archive_entry( err = write_entry(repo, args, oid, path.buf, path.len, mode, NULL, 0); if (err) return err; - return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); + return READ_TREE_RECURSIVE; } if (args->verbose) diff --git a/t/t5005-archive-submodules.sh b/t/t5005-archive-submodules.sh new file mode 100755 index 00000000000000..aad6cfd1082829 --- /dev/null +++ b/t/t5005-archive-submodules.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +test_description='git archive --recurse-submodules test' + +. ./test-lib.sh + +check_tar() { + tarfile=$1.tar + listfile=$1.lst + dir=$1 + dir_with_prefix=$dir/$2 + + test_expect_success ' extract tar archive' ' + (mkdir $dir && cd $dir && "$TAR" xf -) <$tarfile + ' +} + +check_added() { + dir=$1 + path_in_fs=$2 + path_in_archive=$3 + + test_expect_success " validate extra file $path_in_archive" ' + test -f $dir/$path_in_archive && + diff -r $path_in_fs $dir/$path_in_archive + ' +} + +check_not_added() { + dir=$1 + path_in_archive=$2 + + test_expect_success " validate unpresent file $path_in_archive" ' + ! test -f $dir/$path_in_archive && + ! test -d $dir/$path_in_archive + ' +} + +test_expect_success 'setup' ' + rm -rf repo_with_submodules submodule1 uninited_repo_with_submodules && + git init repo_with_submodules && + git init submodule1 && + ( + cd submodule1 && + echo "dir1/sub1/file1.txt" > "file1.txt" && + git add file1.txt && + git commit -m "initialize with file1.txt" + ) && + ( + cd repo_with_submodules && + echo "file2" > file2.txt && + git add file2.txt && + git commit -m "initialize with file2.txt" && + mkdir -p dir1 && + git submodule add ../submodule1 dir1/sub1 && + git commit -m "add submodule1" + ) && + git clone repo_with_submodules uninited_repo_with_submodules +' + +test_expect_success 'archive without recurse, non-init' ' + git -C uninited_repo_with_submodules archive -v HEAD >b.tar +' + +check_tar b +check_added b uninited_repo_with_submodules/file2.txt file2.txt +check_not_added b uninited_repo_with_submodules/dir1/sub1/file1.txt + +# It is expected that --recurse-submodules will not work if submodules are not +# initialized. +test_expect_success 'archive with recurse, non-init' ' + ! git -C uninited_repo_with_submodules archive --recurse-submodules -v HEAD >b2-err.tar +' + +test_expect_success 'archive with recurse, init' ' + git -C repo_with_submodules archive --recurse-submodules -v HEAD >b3.tar +' + +check_tar b3 +check_added b3 repo_with_submodules/file2.txt file2.txt +check_added b3 repo_with_submodules/dir1/sub1/file1.txt dir1/sub1/file1.txt + +test_done