{"thread":{"id":"50053","subject":"[PATCH] shallow: clear shallow cache when committing lock","startedAt":"2018-12-18T01:08:16Z","lastAt":"2018-12-20T19:53:56Z","messageCount":3,"participants":["Jonathan Tan","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"365513","messageId":"20181218010811.143608-1-jonathantanmy@google.com","threadId":"50053","inReplyTo":null,"subject":"[PATCH] shallow: clear shallow cache when committing lock","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2018-12-18T01:08:11Z","receivedAt":"2018-12-18T01:08:16Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When setup_alternate_shallow() is invoked a second time in the same\nprocess, it fails with the error \"shallow file has changed since we read\nit\". One way of reproducing this is to fetch using protocol v2 in such a\nway that backfill_tags() is required, and that the responses to both\nrequests contain a \"shallow-info\" section.\n\nThis is because when the shallow lock is committed, the stat information\nof the shallow file is not cleared. Ensure that this information is\nalways cleared whenever the shallow lock is committed by introducing a\nnew API that hides the shallow lock behind a custom struct.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\nThis was discovered with the help of Aevar's GIT_TEST_PROTOCOL_VERSION\npatches.\n\nIf rebased onto Aevar's GIT_TEST_PROTOCOL_VERSION patches [1], all tests\nstill pass with GIT_TEST_PROTOCOL_VERSION=2 and without. Also, this\npatch allows the following diff to be applied:\n\n> diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\n> index 8b1217ea26..8baa57cf84 100755\n> --- a/t/t5500-fetch-pack.sh\n> +++ b/t/t5500-fetch-pack.sh\n> @@ -791,7 +791,7 @@ test_expect_success 'shallow clone exclude tag two' '\n>  '\n>\n>  test_expect_success 'fetch exclude tag one' '\n> -       GIT_TEST_PROTOCOL_VERSION= git -C shallow12 fetch --shallow-exclude one origin &&\n> +       git -C shallow12 fetch --shallow-exclude one origin &&\n>         git -C shallow12 log --pretty=tformat:%s origin/master >actual &&\n>         test_write_lines three two >expected &&\n>         test_cmp expected actual\n\n(There is still one more instance of GIT_TEST_PROTOCOL_VERSION in that\nfile that is still not fixed.)\n\nI couldn't figure out if the test case included in this patch can be\nreduced - if any one has any idea, help is appreciated.\n\nI'm also not sure why this issue only occurs with protocol v2. It's true\nthat, when using protocol v0, the server can communicate shallow\ninformation both before and after \"want\"s are received, and if shallow\ncommunication is only communicated before, the client will invoke\nsetup_temporary_shallow() instead of setup_alternate_shallow(). (In\nprotocol v2, shallow information is always communicated after \"want\"s,\nthus demonstrating the issue.) But even in protocol v0, writes to the\nshallow file go through setup_alternate_shallow() anyway (in\nupdate_shallow()), so I would think that the issue would occur, but it\ndoesn't.\n\n[1] https://public-inbox.org/git/20181213155817.27666-1-avarab@gmail.com/\n---\n builtin/receive-pack.c |  7 +++----\n commit.h               |  8 +++++++-\n fetch-pack.c           | 13 ++++++-------\n shallow.c              | 25 +++++++++++++++++++++----\n t/t5702-protocol-v2.sh | 16 ++++++++++++++++\n 5 files changed, 53 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..ab7bc5ceb1 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1,7 +1,6 @@\n #include \"builtin.h\"\n #include \"repository.h\"\n #include \"config.h\"\n-#include \"lockfile.h\"\n #include \"pack.h\"\n #include \"refs.h\"\n #include \"pkt-line.h\"\n@@ -864,7 +863,7 @@ static void refuse_unconfigured_deny_delete_current(void)\n static int command_singleton_iterator(void *cb_data, struct object_id *oid);\n static int update_shallow_ref(struct command *cmd, struct shallow_info *si)\n {\n-\tstruct lock_file shallow_lock = LOCK_INIT;\n+\tstruct shallow_lock shallow_lock;\n \tstruct oid_array extra = OID_ARRAY_INIT;\n \tstruct check_connected_options opt = CHECK_CONNECTED_INIT;\n \tuint32_t mask = 1 << (cmd->index % 32);\n@@ -881,12 +880,12 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)\n \topt.env = tmp_objdir_env(tmp_objdir);\n \tsetup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);\n \tif (check_connected(command_singleton_iterator, cmd, &opt)) {\n-\t\trollback_lock_file(&shallow_lock);\n+\t\trollback_shallow_lock(&shallow_lock);\n \t\toid_array_clear(&extra);\n \t\treturn -1;\n \t}\n \n-\tcommit_lock_file(&shallow_lock);\n+\tcommit_shallow_lock(&shallow_lock);\n \n \t/*\n \t * Make sure setup_alternate_shallow() for the next ref does\ndiff --git a/commit.h b/commit.h\nindex 98664536cb..233a30ceef 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -9,6 +9,7 @@\n #include \"string-list.h\"\n #include \"pretty.h\"\n #include \"commit-slab.h\"\n+#include \"lockfile.h\"\n \n #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF\n #define GENERATION_NUMBER_INFINITY 0xFFFFFFFF\n@@ -224,9 +225,14 @@ extern struct commit_list *get_shallow_commits_by_rev_list(\n extern void set_alternate_shallow_file(struct repository *r, const char *path, int override);\n extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol,\n \t\t\t\t const struct oid_array *extra);\n-extern void setup_alternate_shallow(struct lock_file *shallow_lock,\n+struct shallow_lock {\n+\tstruct lock_file lock;\n+};\n+extern void setup_alternate_shallow(struct shallow_lock *shallow_lock,\n \t\t\t\t    const char **alternate_shallow_file,\n \t\t\t\t    const struct oid_array *extra);\n+extern int commit_shallow_lock(struct shallow_lock *shallow_lock);\n+extern void rollback_shallow_lock(struct shallow_lock *shallow_lock);\n extern const char *setup_temporary_shallow(const struct oid_array *extra);\n extern void advertise_shallow_grafts(int);\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 9691046e64..1ae1cf225a 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1,7 +1,6 @@\n #include \"cache.h\"\n #include \"repository.h\"\n #include \"config.h\"\n-#include \"lockfile.h\"\n #include \"refs.h\"\n #include \"pkt-line.h\"\n #include \"commit.h\"\n@@ -34,7 +33,7 @@ static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n static int server_supports_filtering;\n-static struct lock_file shallow_lock;\n+static struct shallow_lock shallow_lock;\n static const char *alternate_shallow_file;\n static char *negotiation_algorithm;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n@@ -1512,9 +1511,9 @@ static void update_shallow(struct fetch_pack_args *args,\n \tif (args->deepen && alternate_shallow_file) {\n \t\tif (*alternate_shallow_file == '\\0') { /* --unshallow */\n \t\t\tunlink_or_warn(git_path_shallow(the_repository));\n-\t\t\trollback_lock_file(&shallow_lock);\n+\t\t\trollback_shallow_lock(&shallow_lock);\n \t\t} else\n-\t\t\tcommit_lock_file(&shallow_lock);\n+\t\t\tcommit_shallow_lock(&shallow_lock);\n \t\treturn;\n \t}\n \n@@ -1537,7 +1536,7 @@ static void update_shallow(struct fetch_pack_args *args,\n \t\t\tsetup_alternate_shallow(&shallow_lock,\n \t\t\t\t\t\t&alternate_shallow_file,\n \t\t\t\t\t\t&extra);\n-\t\t\tcommit_lock_file(&shallow_lock);\n+\t\t\tcommit_shallow_lock(&shallow_lock);\n \t\t}\n \t\toid_array_clear(&extra);\n \t\treturn;\n@@ -1574,7 +1573,7 @@ static void update_shallow(struct fetch_pack_args *args,\n \t\tsetup_alternate_shallow(&shallow_lock,\n \t\t\t\t\t&alternate_shallow_file,\n \t\t\t\t\t&extra);\n-\t\tcommit_lock_file(&shallow_lock);\n+\t\tcommit_shallow_lock(&shallow_lock);\n \t\toid_array_clear(&extra);\n \t\toid_array_clear(&ref);\n \t\treturn;\n@@ -1660,7 +1659,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,\n \t\t\terror(_(\"remote did not send all necessary objects\"));\n \t\t\tfree_refs(ref_cpy);\n \t\t\tref_cpy = NULL;\n-\t\t\trollback_lock_file(&shallow_lock);\n+\t\t\trollback_shallow_lock(&shallow_lock);\n \t\t\tgoto cleanup;\n \t\t}\n \t\targs->connectivity_checked = 1;\ndiff --git a/shallow.c b/shallow.c\nindex 02fdbfc554..0d39906419 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -333,22 +333,23 @@ const char *setup_temporary_shallow(const struct oid_array *extra)\n \treturn \"\";\n }\n \n-void setup_alternate_shallow(struct lock_file *shallow_lock,\n+void setup_alternate_shallow(struct shallow_lock *shallow_lock,\n \t\t\t     const char **alternate_shallow_file,\n \t\t\t     const struct oid_array *extra)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tint fd;\n \n-\tfd = hold_lock_file_for_update(shallow_lock,\n+\tfd = hold_lock_file_for_update(&shallow_lock->lock,\n \t\t\t\t       git_path_shallow(the_repository),\n \t\t\t\t       LOCK_DIE_ON_ERROR);\n \tcheck_shallow_file_for_update(the_repository);\n \tif (write_shallow_commits(&sb, 0, extra)) {\n \t\tif (write_in_full(fd, sb.buf, sb.len) < 0)\n \t\t\tdie_errno(\"failed to write to %s\",\n-\t\t\t\t  get_lock_file_path(shallow_lock));\n-\t\t*alternate_shallow_file = get_lock_file_path(shallow_lock);\n+\t\t\t\t  get_lock_file_path(&shallow_lock->lock));\n+\t\t*alternate_shallow_file =\n+\t\t\tget_lock_file_path(&shallow_lock->lock);\n \t} else\n \t\t/*\n \t\t * is_repository_shallow() sees empty string as \"no\n@@ -358,6 +359,22 @@ void setup_alternate_shallow(struct lock_file *shallow_lock,\n \tstrbuf_release(&sb);\n }\n \n+int commit_shallow_lock(struct shallow_lock *shallow_lock)\n+{\n+\tint ret;\n+\n+\tif ((ret = commit_lock_file(&shallow_lock->lock)))\n+\t\treturn ret;\n+\tthe_repository->parsed_objects->is_shallow = -1;\n+\tstat_validity_clear(the_repository->parsed_objects->shallow_stat);\n+\treturn 0;\n+}\n+\n+void rollback_shallow_lock(struct shallow_lock *shallow_lock)\n+{\n+\trollback_lock_file(&shallow_lock->lock);\n+}\n+\n static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *cb)\n {\n \tint fd = *(int *)cb;\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 0f2b09ebb8..390a71399d 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -471,6 +471,22 @@ test_expect_success 'upload-pack respects client shallows' '\n \tgrep \"fetch< version 2\" trace\n '\n \n+test_expect_success '2 fetches in one process updating shallow' '\n+\trm -rf server client trace &&\n+\n+\ttest_create_repo server &&\n+\ttest_commit -C server one &&\n+\ttest_commit -C server two &&\n+\ttest_commit -C server three &&\n+\tgit clone --shallow-exclude two \"file://$(pwd)/server\" client &&\n+\n+\t# Triggers tag following (thus, 2 fetches in one process)\n+\tGIT_TRACE_PACKET=\"$(pwd)/trace\" git -C client -c protocol.version=2 \\\n+\t\tfetch --shallow-exclude one origin &&\n+\t# Ensure that protocol v2 is used\n+\tgrep \"fetch< version 2\" trace\n+'\n+\n # Test protocol v2 with 'http://' transport\n #\n . \"$TEST_DIRECTORY\"/lib-httpd.sh\n-- \n2.19.0.271.gfe8321ec05.dirty\n\n"},{"id":"365515","messageId":"20181218034648.GA221428@google.com","threadId":"50053","inReplyTo":"20181218010811.143608-1-jonathantanmy@google.com","subject":"Re: [PATCH] shallow: clear shallow cache when committing lock","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-12-18T03:46:48Z","receivedAt":"2018-12-18T03:46:53Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nJonathan Tan wrote:\n\n> When setup_alternate_shallow() is invoked a second time in the same\n> process, it fails with the error \"shallow file has changed since we read\n> it\". One way of reproducing this is to fetch using protocol v2 in such a\n> way that backfill_tags() is required, and that the responses to both\n> requests contain a \"shallow-info\" section.\n>\n> This is because when the shallow lock is committed, the stat information\n> of the shallow file is not cleared. Ensure that this information is\n> always cleared whenever the shallow lock is committed by introducing a\n> new API that hides the shallow lock behind a custom struct.\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n> ---\n> This was discovered with the help of Aevar's GIT_TEST_PROTOCOL_VERSION\n> patches.\n\nGood catch.  I think the above note should go in the commit message,\nsince it would be very useful to anyone looking back at this commit\nmessage and trying to find a quick reproduction recipe.\n\n[...]\n> I couldn't figure out if the test case included in this patch can be\n> reduced - if any one has any idea, help is appreciated.\n\nIt's short enough to be comprehensible, so I wouldn't worry too\nmuch. :)\n\n> I'm also not sure why this issue only occurs with protocol v2. It's true\n> that, when using protocol v0, the server can communicate shallow\n> information both before and after \"want\"s are received, and if shallow\n> communication is only communicated before, the client will invoke\n> setup_temporary_shallow() instead of setup_alternate_shallow(). (In\n> protocol v2, shallow information is always communicated after \"want\"s,\n> thus demonstrating the issue.) But even in protocol v0, writes to the\n> shallow file go through setup_alternate_shallow() anyway (in\n> update_shallow()), so I would think that the issue would occur, but it\n> doesn't.\n\nGood question.  I'll try to poke more tomorrow morning, too.\n\n[...]\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -1,7 +1,6 @@\n>  #include \"builtin.h\"\n>  #include \"repository.h\"\n>  #include \"config.h\"\n> -#include \"lockfile.h\"\n>  #include \"pack.h\"\n>  #include \"refs.h\"\n>  #include \"pkt-line.h\"\n> @@ -864,7 +863,7 @@ static void refuse_unconfigured_deny_delete_current(void)\n>  static int command_singleton_iterator(void *cb_data, struct object_id *oid);\n>  static int update_shallow_ref(struct command *cmd, struct shallow_info *si)\n>  {\n> -\tstruct lock_file shallow_lock = LOCK_INIT;\n> +\tstruct shallow_lock shallow_lock;\n\nInteresting.  Is there another name that can convey what this object\ndoes more clearly?  At first glance I would expect this to be a less\ndeep kind of lock.\n\nI'm awful at naming, but a couple of ideas to get things started:\n\n- 'struct shallow_update', since this represents a pending update to\n  the .git/shallow file?\n\n- struct lock_file_for_shallow?\n\n- struct locked_shallow_file?\n\n- \"struct shallow_file\" or \"struct shallow_commits_file\"?  This is a\n  handle representing the state of what will become .git/shallow file\n  once the caller commits the update.\n\n[...]\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n[...]\n> @@ -1660,7 +1659,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,\n>  \t\t\terror(_(\"remote did not send all necessary objects\"));\n>  \t\t\tfree_refs(ref_cpy);\n>  \t\t\tref_cpy = NULL;\n> -\t\t\trollback_lock_file(&shallow_lock);\n> +\t\t\trollback_shallow_lock(&shallow_lock);\n\nFor a moment, I was worried that this line could be reached without\nsetup_alternate_shallow having been called first.  Fortunately,\nshallow_lock is static so it is zero-initialized automatically, making\nthat harmless.\n\n[...]\n> --- a/shallow.c\n> +++ b/shallow.c\n[...]\n> @@ -358,6 +359,22 @@ void setup_alternate_shallow(struct lock_file *shallow_lock,\n>  \tstrbuf_release(&sb);\n>  }\n>  \n> +int commit_shallow_lock(struct shallow_lock *shallow_lock)\n> +{\n> +\tint ret;\n> +\n> +\tif ((ret = commit_lock_file(&shallow_lock->lock)))\n> +\t\treturn ret;\n> +\tthe_repository->parsed_objects->is_shallow = -1;\n> +\tstat_validity_clear(the_repository->parsed_objects->shallow_stat);\n\nIn 'next', some other functions in this file handle an arbitrary\nrepository argument 'r'.  Should this one as well?  (I.e. can this\npatch come with a conflict-resolution patch to handle that?)\n\n[...]\n> --- a/t/t5702-protocol-v2.sh\n> +++ b/t/t5702-protocol-v2.sh\n> @@ -471,6 +471,22 @@ test_expect_success 'upload-pack respects client shallows' '\n\nYay, thanks for the test.  I'll study it more closely tomorrow.\n\nThanks and hope that helps,\nJonathan\n"},{"id":"365698","messageId":"20181220195349.92214-1-jonathantanmy@google.com","threadId":"50053","inReplyTo":"20181218010811.143608-1-jonathantanmy@google.com","subject":"[RFC PATCH] fetch-pack: respect --no-update-shallow in v2","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2018-12-20T19:53:49Z","receivedAt":"2018-12-20T19:53:56Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"While investigating t5537's failure revealed by Aevar's\nGIT_TEST_PROTOCOL_VERSION patches [1], I found an answer to my confusion\nthat I described in [2]. Quoting the relevant part:\n\n> I'm also not sure why this issue only occurs with protocol v2. It's true\n> that, when using protocol v0, the server can communicate shallow\n> information both before and after \"want\"s are received, and if shallow\n> communication is only communicated before, the client will invoke\n> setup_temporary_shallow() instead of setup_alternate_shallow(). (In\n> protocol v2, shallow information is always communicated after \"want\"s,\n> thus demonstrating the issue.) But even in protocol v0, writes to the\n> shallow file go through setup_alternate_shallow() anyway (in\n> update_shallow()), so I would think that the issue would occur, but it\n> doesn't.\n\nIt turns out that update_shallow() doesn't write pre-\"want\" shallow\ninformation unless --update-shallow is set. But post-\"want\" shallow\ninformation is always written regardless of --update-shallow. (So maybe\n--update-shallow is not the best name for it, but that is another\nissue.)\n\nWhich raises the question: in protocol v2, all shallow information is\npost-\"want\", so how should we handle this? My current inclination is to\ntreat them like v0 pre-\"want\" information if no depth-like argument is\ngiven by the user (that is, args->deepen is 0), and like v0 post-\"want\"\ninformation otherwise. (Right now, it is treated always like the\nlatter.) This patch does it by delaying initialization of the struct\nshallow_info until we fully know what's going on, and not only fixes the\nbug revealed by t5537, but the bug that [2] fixes as well (eliminating\nthe need for [2]).\n\nThis does mean that there will be some difference in functionality\nbetween v0 and v2: if fetching from a shallow repository with our own\ndepth arguments, v0 would write shallows resulting from our own depth\narguments but not those that are there because the remote is shallow; v2\nwould write all shallows. But this is the best solution I can think of\nso far.\n\nAny thoughts? The code can be written more clearly but I wanted to\ndiscuss the design first.\n\n[1] https://public-inbox.org/git/20181213155817.27666-1-avarab@gmail.com/\n[2] https://public-inbox.org/git/20181218010811.143608-1-jonathantanmy@google.com/\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n fetch-pack.c           | 34 ++++++++++++++++++++++++++--------\n t/t5702-protocol-v2.sh | 16 ++++++++++++++++\n 2 files changed, 42 insertions(+), 8 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 9691046e64..0a89a210b0 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1265,8 +1265,11 @@ static int process_acks(struct fetch_negotiator *negotiator,\n }\n \n static void receive_shallow_info(struct fetch_pack_args *args,\n-\t\t\t\t struct packet_reader *reader)\n+\t\t\t\t struct packet_reader *reader,\n+\t\t\t\t struct shallow_info *si)\n {\n+\tstruct oid_array shallows = OID_ARRAY_INIT;\n+\n \tprocess_section_header(reader, \"shallow-info\", 0);\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n \t\tconst char *arg;\n@@ -1275,7 +1278,9 @@ static void receive_shallow_info(struct fetch_pack_args *args,\n \t\tif (skip_prefix(reader->line, \"shallow \", &arg)) {\n \t\t\tif (get_oid_hex(arg, &oid))\n \t\t\t\tdie(_(\"invalid shallow line: %s\"), reader->line);\n-\t\t\tregister_shallow(the_repository, &oid);\n+\t\t\tif (args->deepen)\n+\t\t\t\tregister_shallow(the_repository, &oid);\n+\t\t\toid_array_append(&shallows, &oid);\n \t\t\tcontinue;\n \t\t}\n \t\tif (skip_prefix(reader->line, \"unshallow \", &arg)) {\n@@ -1297,8 +1302,17 @@ static void receive_shallow_info(struct fetch_pack_args *args,\n \t    reader->status != PACKET_READ_DELIM)\n \t\tdie(_(\"error processing shallow info: %d\"), reader->status);\n \n-\tsetup_alternate_shallow(&shallow_lock, &alternate_shallow_file, NULL);\n-\targs->deepen = 1;\n+\tif (args->deepen) {\n+\t\tprepare_shallow_info(si, NULL);\n+\t\tsetup_alternate_shallow(&shallow_lock, &alternate_shallow_file,\n+\t\t\t\t\tNULL);\n+\t} else {\n+\t\tprepare_shallow_info(si, &shallows);\n+\t\tif (si->nr_ours || si->nr_theirs)\n+\t\t\talternate_shallow_file = setup_temporary_shallow(si->shallow);\n+\t\telse\n+\t\t\talternate_shallow_file = NULL;\n+\t}\n }\n \n static void receive_wanted_refs(struct packet_reader *reader,\n@@ -1340,6 +1354,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\t\t\t    int fd[2],\n \t\t\t\t    const struct ref *orig_ref,\n \t\t\t\t    struct ref **sought, int nr_sought,\n+\t\t\t\t    struct shallow_info *si,\n \t\t\t\t    char **pack_lockfile)\n {\n \tstruct ref *ref = copy_ref_list(orig_ref);\n@@ -1407,7 +1422,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\tcase FETCH_GET_PACK:\n \t\t\t/* Check for shallow-info section */\n \t\t\tif (process_section_header(&reader, \"shallow-info\", 1))\n-\t\t\t\treceive_shallow_info(args, &reader);\n+\t\t\t\treceive_shallow_info(args, &reader, si);\n+\t\t\telse\n+\t\t\t\tprepare_shallow_info(si, NULL);\n \n \t\t\tif (process_section_header(&reader, \"wanted-refs\", 1))\n \t\t\t\treceive_wanted_refs(&reader, sought, nr_sought);\n@@ -1641,13 +1658,14 @@ struct ref *fetch_pack(struct fetch_pack_args *args,\n \t\tpacket_flush(fd[1]);\n \t\tdie(_(\"no matching remote head\"));\n \t}\n-\tprepare_shallow_info(&si, shallow);\n \tif (version == protocol_v2)\n \t\tref_cpy = do_fetch_pack_v2(args, fd, ref, sought, nr_sought,\n-\t\t\t\t\t   pack_lockfile);\n-\telse\n+\t\t\t\t\t   &si, pack_lockfile);\n+\telse {\n+\t\tprepare_shallow_info(&si, shallow);\n \t\tref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,\n \t\t\t\t\t&si, pack_lockfile);\n+\t}\n \treprepare_packed_git(the_repository);\n \n \tif (!args->cloning && args->deepen) {\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 0f2b09ebb8..390a71399d 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -471,6 +471,22 @@ test_expect_success 'upload-pack respects client shallows' '\n \tgrep \"fetch< version 2\" trace\n '\n \n+test_expect_success '2 fetches in one process updating shallow' '\n+\trm -rf server client trace &&\n+\n+\ttest_create_repo server &&\n+\ttest_commit -C server one &&\n+\ttest_commit -C server two &&\n+\ttest_commit -C server three &&\n+\tgit clone --shallow-exclude two \"file://$(pwd)/server\" client &&\n+\n+\t# Triggers tag following (thus, 2 fetches in one process)\n+\tGIT_TRACE_PACKET=\"$(pwd)/trace\" git -C client -c protocol.version=2 \\\n+\t\tfetch --shallow-exclude one origin &&\n+\t# Ensure that protocol v2 is used\n+\tgrep \"fetch< version 2\" trace\n+'\n+\n # Test protocol v2 with 'http://' transport\n #\n . \"$TEST_DIRECTORY\"/lib-httpd.sh\n-- \n2.19.0.271.gfe8321ec05.dirty\n\n"}]}