diff mbox series

shallow.c: use 'reset_repository_shallow' when appropriate

Message ID 8d295389ea43c6b7e008514067b7af6eacba64a5.1587492422.git.me@ttaylorr.com (mailing list archive)
State New, archived
Headers show
Series shallow.c: use 'reset_repository_shallow' when appropriate | expand

Commit Message

Taylor Blau April 21, 2020, 6:09 p.m. UTC
In bd0b42aed3 (fetch-pack: do not take shallow lock unnecessarily,
2019-01-10), the author noted that 'is_repository_shallow' produces
visible side-effect(s) by setting 'is_shallow' and 'shallow_stat'.

This is a problem for e.g., fetching with '--update-shallow' in a
shallow repsoitory with 'fetch.writeCommitGraph' enabled, since the
update to '.git/shallow' will cause Git to think that the repository
isn't shallow when it is, thereby circumventing the commit-graph
compatability check.

This causes problems in shallow repositories with at least shallow refs
that have at least one ancestor (since the client won't have those
objects, and therefore can't take the reachability closure over commits
when writing a commit-graph).

Address this by introducing 'reset_repository_shallow()', and calling
it whenever the shallow files is updated. This happens in two cases:

  * during 'update_shallow', when either the repository is
    un-shallowing, or after commit_lock_file, when the contents of
    .git/shallow is changing, and

  * in 'prune_shallow', when the repository can go from shallow to
    un-shallow when the shallow file is updated, forcing
    'is_repository_shallow' to re-evaluate whether the repository is
    still shallow after fetching in the above scenario.

As a result of the second change, 'prune_shallow' can now only be called
once (since 'check_shallow_file_for_update' will die after calling
'reset_repository_shallow'). But, this is OK since we only call
'prune_shallow' at most once per process.

Helped-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Taylor Blau <me@ttaylorr.com>
---

Here's a cleaned up version of the patch that we were originally
discussing in
https://lore.kernel.org/git/20200414235057.GA6863@syl.local/, which
addresses some of Jonathan's feedback and adds a test to make sure that
the new behavior is working correctly.

 commit.h                 |  1 +
 fetch-pack.c             |  1 +
 shallow.c                | 15 ++++++++-------
 t/t5537-fetch-shallow.sh | 36 ++++++++++++++++++++++++++++++++++++
 4 files changed, 46 insertions(+), 7 deletions(-)

--
2.26.2.108.g048abe1751

Comments

Junio C Hamano April 21, 2020, 8:41 p.m. UTC | #1
Taylor Blau <me@ttaylorr.com> writes:

> In bd0b42aed3 (fetch-pack: do not take shallow lock unnecessarily,
> 2019-01-10), the author noted that 'is_repository_shallow' produces
> visible side-effect(s) by setting 'is_shallow' and 'shallow_stat'.
>
> This is a problem for e.g., fetching with '--update-shallow' in a
> shallow repsoitory with 'fetch.writeCommitGraph' enabled, since the

repository.

> update to '.git/shallow' will cause Git to think that the repository
> isn't shallow when it is, thereby circumventing the commit-graph
> compatability check.
>
> This causes problems in shallow repositories with at least shallow refs
> that have at least one ancestor (since the client won't have those
> objects, and therefore can't take the reachability closure over commits
> when writing a commit-graph).

OK.

> Address this by introducing 'reset_repository_shallow()', and calling
> it whenever the shallow files is updated. This happens in two cases:
>
>   * during 'update_shallow', when either the repository is
>     un-shallowing, or after commit_lock_file, when the contents of
>     .git/shallow is changing, and
>
>   * in 'prune_shallow', when the repository can go from shallow to
>     un-shallow when the shallow file is updated, forcing
>     'is_repository_shallow' to re-evaluate whether the repository is
>     still shallow after fetching in the above scenario.
>
> As a result of the second change, 'prune_shallow' can now only be called
> once (since 'check_shallow_file_for_update' will die after calling
> 'reset_repository_shallow'). But, this is OK since we only call
> 'prune_shallow' at most once per process.
>
> Helped-by: Jonathan Tan <jonathantanmy@google.com>
> Signed-off-by: Taylor Blau <me@ttaylorr.com>
> ---
>
> Here's a cleaned up version of the patch that we were originally
> discussing in
> https://lore.kernel.org/git/20200414235057.GA6863@syl.local/, which
> addresses some of Jonathan's feedback and adds a test to make sure that
> the new behavior is working correctly.
>
>  commit.h                 |  1 +
>  fetch-pack.c             |  1 +
>  shallow.c                | 15 ++++++++-------
>  t/t5537-fetch-shallow.sh | 36 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 46 insertions(+), 7 deletions(-)
>
> diff --git a/commit.h b/commit.h
> index 008a0fa4a0..ee1ba139d4 100644
> --- a/commit.h
> +++ b/commit.h
> @@ -251,6 +251,7 @@ int register_shallow(struct repository *r, const struct object_id *oid);
>  int unregister_shallow(const struct object_id *oid);
>  int for_each_commit_graft(each_commit_graft_fn, void *);
>  int is_repository_shallow(struct repository *r);
> +void reset_repository_shallow(struct repository *r);
>  struct commit_list *get_shallow_commits(struct object_array *heads,
>  					int depth, int shallow_flag, int not_shallow_flag);
>  struct commit_list *get_shallow_commits_by_rev_list(
> diff --git a/fetch-pack.c b/fetch-pack.c
> index 1734a573b0..684868bc17 100644
> --- a/fetch-pack.c
> +++ b/fetch-pack.c
> @@ -1632,6 +1632,7 @@ static void update_shallow(struct fetch_pack_args *args,
>  			rollback_lock_file(&shallow_lock);
>  		} else
>  			commit_lock_file(&shallow_lock);
> +		reset_repository_shallow(the_repository);
>  		alternate_shallow_file = NULL;
>  		return;
>  	}

So, after updating shallow file with "fetch --update-shallow" (or
failing to do so), we reset the in-core data.

> +void reset_repository_shallow(struct repository *r)
> +{
> +	r->parsed_objects->is_shallow = -1;
> +	stat_validity_clear(r->parsed_objects->shallow_stat);
> +}

OK.

> @@ -362,6 +361,7 @@ void setup_alternate_shallow(struct lock_file *shallow_lock,
>  		 * shallow file".
>  		 */
>  		*alternate_shallow_file = "";
> +	reset_repository_shallow(the_repository);
>  	strbuf_release(&sb);
>  }

And also after writing out the alternate shallow file (whether it is
empty or polulated).

> @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
>  	} else {
>  		unlink(git_path_shallow(the_repository));
>  		rollback_lock_file(&shallow_lock);
> +		reset_repository_shallow(the_repository);
>  	}

Here, we reset only after we realize we cannot write the updated
shallow file.  Intended?

> +	cat <<EOF >expect.refs &&
> +refs/remotes/shallow/master
> +refs/remotes/shallow/no-shallow
> +refs/tags/heavy-tag
> +refs/tags/heavy-tag-for-graph
> +refs/tags/light-tag
> +refs/tags/light-tag-for-graph
> +EOF

	cat <<-EOF >expect.refs &&
	... body can be indented by any number of TAB
	... to make it easier to view
	EOF

> +	test_cmp expect.refs actual.refs &&
> +	git log --format=%s shallow/master >actual &&
> +	cat <<EOF >expect &&

Likewise.
Taylor Blau April 21, 2020, 8:45 p.m. UTC | #2
Hi Junio,

On Tue, Apr 21, 2020 at 01:41:56PM -0700, Junio C Hamano wrote:
> Taylor Blau <me@ttaylorr.com> writes:
>
> > In bd0b42aed3 (fetch-pack: do not take shallow lock unnecessarily,
> > 2019-01-10), the author noted that 'is_repository_shallow' produces
> > visible side-effect(s) by setting 'is_shallow' and 'shallow_stat'.
> >
> > This is a problem for e.g., fetching with '--update-shallow' in a
> > shallow repsoitory with 'fetch.writeCommitGraph' enabled, since the
>
> repository.

Whoops, sorry about that.

> > update to '.git/shallow' will cause Git to think that the repository
> > isn't shallow when it is, thereby circumventing the commit-graph
> > compatability check.
> >
> > This causes problems in shallow repositories with at least shallow refs
> > that have at least one ancestor (since the client won't have those
> > objects, and therefore can't take the reachability closure over commits
> > when writing a commit-graph).
>
> OK.
>
> > Address this by introducing 'reset_repository_shallow()', and calling
> > it whenever the shallow files is updated. This happens in two cases:
> >
> >   * during 'update_shallow', when either the repository is
> >     un-shallowing, or after commit_lock_file, when the contents of
> >     .git/shallow is changing, and
> >
> >   * in 'prune_shallow', when the repository can go from shallow to
> >     un-shallow when the shallow file is updated, forcing
> >     'is_repository_shallow' to re-evaluate whether the repository is
> >     still shallow after fetching in the above scenario.
> >
> > As a result of the second change, 'prune_shallow' can now only be called
> > once (since 'check_shallow_file_for_update' will die after calling
> > 'reset_repository_shallow'). But, this is OK since we only call
> > 'prune_shallow' at most once per process.
> >
> > Helped-by: Jonathan Tan <jonathantanmy@google.com>
> > Signed-off-by: Taylor Blau <me@ttaylorr.com>
> > ---
> >
> > Here's a cleaned up version of the patch that we were originally
> > discussing in
> > https://lore.kernel.org/git/20200414235057.GA6863@syl.local/, which
> > addresses some of Jonathan's feedback and adds a test to make sure that
> > the new behavior is working correctly.
> >
> >  commit.h                 |  1 +
> >  fetch-pack.c             |  1 +
> >  shallow.c                | 15 ++++++++-------
> >  t/t5537-fetch-shallow.sh | 36 ++++++++++++++++++++++++++++++++++++
> >  4 files changed, 46 insertions(+), 7 deletions(-)
> >
> > diff --git a/commit.h b/commit.h
> > index 008a0fa4a0..ee1ba139d4 100644
> > --- a/commit.h
> > +++ b/commit.h
> > @@ -251,6 +251,7 @@ int register_shallow(struct repository *r, const struct object_id *oid);
> >  int unregister_shallow(const struct object_id *oid);
> >  int for_each_commit_graft(each_commit_graft_fn, void *);
> >  int is_repository_shallow(struct repository *r);
> > +void reset_repository_shallow(struct repository *r);
> >  struct commit_list *get_shallow_commits(struct object_array *heads,
> >  					int depth, int shallow_flag, int not_shallow_flag);
> >  struct commit_list *get_shallow_commits_by_rev_list(
> > diff --git a/fetch-pack.c b/fetch-pack.c
> > index 1734a573b0..684868bc17 100644
> > --- a/fetch-pack.c
> > +++ b/fetch-pack.c
> > @@ -1632,6 +1632,7 @@ static void update_shallow(struct fetch_pack_args *args,
> >  			rollback_lock_file(&shallow_lock);
> >  		} else
> >  			commit_lock_file(&shallow_lock);
> > +		reset_repository_shallow(the_repository);
> >  		alternate_shallow_file = NULL;
> >  		return;
> >  	}
>
> So, after updating shallow file with "fetch --update-shallow" (or
> failing to do so), we reset the in-core data.
>
> > +void reset_repository_shallow(struct repository *r)
> > +{
> > +	r->parsed_objects->is_shallow = -1;
> > +	stat_validity_clear(r->parsed_objects->shallow_stat);
> > +}
>
> OK.
>
> > @@ -362,6 +361,7 @@ void setup_alternate_shallow(struct lock_file *shallow_lock,
> >  		 * shallow file".
> >  		 */
> >  		*alternate_shallow_file = "";
> > +	reset_repository_shallow(the_repository);
> >  	strbuf_release(&sb);
> >  }
>
> And also after writing out the alternate shallow file (whether it is
> empty or polulated).
>
> > @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
> >  	} else {
> >  		unlink(git_path_shallow(the_repository));
> >  		rollback_lock_file(&shallow_lock);
> > +		reset_repository_shallow(the_repository);
> >  	}
>
> Here, we reset only after we realize we cannot write the updated
> shallow file.  Intended?

Yes, see this earlier discussion I had about it with Jonathan:
https://lore.kernel.org/git/20200416020509.225014-1-jonathantanmy@google.com/.

> > +	cat <<EOF >expect.refs &&
> > +refs/remotes/shallow/master
> > +refs/remotes/shallow/no-shallow
> > +refs/tags/heavy-tag
> > +refs/tags/heavy-tag-for-graph
> > +refs/tags/light-tag
> > +refs/tags/light-tag-for-graph
> > +EOF
>
> 	cat <<-EOF >expect.refs &&
> 	... body can be indented by any number of TAB
> 	... to make it easier to view
> 	EOF
>
> > +	test_cmp expect.refs actual.refs &&
> > +	git log --format=%s shallow/master >actual &&
> > +	cat <<EOF >expect &&
>
> Likewise.

I'd be happy to update these, but I am matching the (poor) style of the
surrounding tests. Are you OK with the inconsistency?  Would you like
another preparatory patch to clean up the surrounding?

Thanks,
Taylor
Junio C Hamano April 21, 2020, 8:52 p.m. UTC | #3
Taylor Blau <me@ttaylorr.com> writes:

>> > @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
>> >  	} else {
>> >  		unlink(git_path_shallow(the_repository));
>> >  		rollback_lock_file(&shallow_lock);
>> > +		reset_repository_shallow(the_repository);
>> >  	}
>>
>> Here, we reset only after we realize we cannot write the updated
>> shallow file.  Intended?
>
> Yes, see this earlier discussion I had about it with Jonathan:
> https://lore.kernel.org/git/20200416020509.225014-1-jonathantanmy@google.com/.

I did, and then I asked the question, because I couldn't quite get
if JTan was asking a question similar to the one he asked earlier in
the message ("do you need a reset in the "else" branch as well?"),
or if he was saying what he sees there, "the opposite case", was
good.

Also, I was sort-of reacting to """In any case, I think the commit
message should discuss why reset_repository_shallow() is added only
on the unlink+rollback side in one "if" statement, but only on the
opposite "commit" side in another "if" statement.""" in that
message.

Thanks.
Taylor Blau April 21, 2020, 10:21 p.m. UTC | #4
On Tue, Apr 21, 2020 at 01:52:46PM -0700, Junio C Hamano wrote:
> Taylor Blau <me@ttaylorr.com> writes:
>
> >> > @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
> >> >  	} else {
> >> >  		unlink(git_path_shallow(the_repository));
> >> >  		rollback_lock_file(&shallow_lock);
> >> > +		reset_repository_shallow(the_repository);
> >> >  	}
> >>
> >> Here, we reset only after we realize we cannot write the updated
> >> shallow file.  Intended?
> >
> > Yes, see this earlier discussion I had about it with Jonathan:
> > https://lore.kernel.org/git/20200416020509.225014-1-jonathantanmy@google.com/.
>
> I did, and then I asked the question, because I couldn't quite get
> if JTan was asking a question similar to the one he asked earlier in
> the message ("do you need a reset in the "else" branch as well?"),
> or if he was saying what he sees there, "the opposite case", was
> good.

Sorry, I think that the linked message is confusing (at least, it
confused me the second time I read it because I wasn't sure which part
of the mail Jonathan was asking about: my patch, or his response to my
patch).

I think that he was referring to how I had it in the original patch I
sent in that thread, which was wrong. Based on my understanding of his
message, his recommendations match what I have in _this_ patch.

> Also, I was sort-of reacting to """In any case, I think the commit
> message should discuss why reset_repository_shallow() is added only
> on the unlink+rollback side in one "if" statement, but only on the
> opposite "commit" side in another "if" statement.""" in that
> message.

Is the description that I attached in the earlier patch sufficient? I
could certainly add more detail if it's not.

In either case, I'm sitting on another patch locally to improve the
style of the surrounding tests, which is done as a preparatory step
before this patch. I'll re-send after hearing back from you.

> Thanks.

Thanks,
Taylor
Junio C Hamano April 21, 2020, 11:06 p.m. UTC | #5
Taylor Blau <me@ttaylorr.com> writes:

> Sorry, I think that the linked message is confusing (at least, it
> confused me the second time I read it because I wasn't sure which part
> of the mail Jonathan was asking about: my patch, or his response to my
> patch).
>
> I think that he was referring to how I had it in the original patch I
> sent in that thread, which was wrong. Based on my understanding of his
> message, his recommendations match what I have in _this_ patch.

Thanks for a clarification.

> In either case, I'm sitting on another patch locally to improve the
> style of the surrounding tests, which is done as a preparatory step
> before this patch. I'll re-send after hearing back from you.

Alright.  Thanks.
Jonathan Tan April 22, 2020, 6:02 p.m. UTC | #6
> Address this by introducing 'reset_repository_shallow()', and calling
> it whenever the shallow files is updated. This happens in two cases:
> 
>   * during 'update_shallow', when either the repository is
>     un-shallowing, or after commit_lock_file, when the contents of
>     .git/shallow is changing, and
> 
>   * in 'prune_shallow', when the repository can go from shallow to
>     un-shallow when the shallow file is updated, forcing
>     'is_repository_shallow' to re-evaluate whether the repository is
>     still shallow after fetching in the above scenario.

From a cursory reading of the code, it seems that this happens in
fetch-pack and receive-pack. Looking at those files, I found some more
occasions when this happens. I have outlined them in the patch after the
scissors (I hope I used the scissors correctly).

Maybe instead of enumerating the cases (of which there are quite a few),
say that we do this when we unlink the shallow file, we modify or create
the shallow file, or when we change the value of alternate_shallow_file.

> @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
>  	} else {
>  		unlink(git_path_shallow(the_repository));
>  		rollback_lock_file(&shallow_lock);
> +		reset_repository_shallow(the_repository);
>  	}
>  	strbuf_release(&sb);
>  }

The "if" part (not quoted here) commits the shallow lock file, and thus
possibly modifies (or creates) the shallow file, so I think we need to
put reset_repository_shallow() outside the whole "if" block. I have done
that in the patch after the scissors.

> +test_expect_success 'fetch --update-shallow (with fetch.writeCommitGraph)' '
> +	(
> +	cd shallow &&
> +	git checkout master &&
> +	commit 8 &&
> +	git tag -m foo heavy-tag-for-graph HEAD^ &&
> +	git tag light-tag-for-graph HEAD^:tracked
> +	) &&
> +	(
> +	cd notshallow &&
> +	test_config fetch.writeCommitGraph true &&

When I patched onto master, this line causes the test to fail with a
warning that test_when_finished doesn't work in a subshell. I've
replaced it with a regular "git config" and it works.

Here is the patch containing what I tried. I think that most of the new
reset_repository_shallow() invocations don't change any functionality
(we don't usually read shallow files so many times in a process), so
they can't be tested, but I think that it's better to include them for
completeness, and to close the open question mentioned in bd0b42aed3
("fetch-pack: do not take shallow lock unnecessarily", 2019-01-10)
(about the full solution involving clearing shallow information whenever
we commit the shallow lock - we find here that the full solution
involves this and also clearing shallow information in other cases too).

-- 8< --
From 46a69a133db2e8e948d2bf296294656c9902e5ae Mon Sep 17 00:00:00 2001
From: Jonathan Tan <jonathantanmy@google.com>
Date: Wed, 22 Apr 2020 10:53:30 -0700
Subject: [PATCH] fixup

Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
---
 builtin/receive-pack.c   | 1 +
 fetch-pack.c             | 2 ++
 shallow.c                | 2 +-
 t/t5537-fetch-shallow.sh | 2 +-
 4 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 2cc18bbffd..d61cbf60e2 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -878,6 +878,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
 	}
 
 	commit_lock_file(&shallow_lock);
+	reset_repository_shallow(the_repository);
 
 	/*
 	 * Make sure setup_alternate_shallow() for the next ref does
diff --git a/fetch-pack.c b/fetch-pack.c
index 684868bc17..9a1cec470c 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1657,6 +1657,7 @@ static void update_shallow(struct fetch_pack_args *args,
 						&alternate_shallow_file,
 						&extra);
 			commit_lock_file(&shallow_lock);
+			reset_repository_shallow(the_repository);
 			alternate_shallow_file = NULL;
 		}
 		oid_array_clear(&extra);
@@ -1695,6 +1696,7 @@ static void update_shallow(struct fetch_pack_args *args,
 					&alternate_shallow_file,
 					&extra);
 		commit_lock_file(&shallow_lock);
+		reset_repository_shallow(the_repository);
 		oid_array_clear(&extra);
 		oid_array_clear(&ref);
 		alternate_shallow_file = NULL;
diff --git a/shallow.c b/shallow.c
index 9d1304e786..1a1ca71ffe 100644
--- a/shallow.c
+++ b/shallow.c
@@ -414,8 +414,8 @@ void prune_shallow(unsigned options)
 	} else {
 		unlink(git_path_shallow(the_repository));
 		rollback_lock_file(&shallow_lock);
-		reset_repository_shallow(the_repository);
 	}
+	reset_repository_shallow(the_repository);
 	strbuf_release(&sb);
 }
 
diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh
index c9c731c7a9..c5c40fb8e7 100755
--- a/t/t5537-fetch-shallow.sh
+++ b/t/t5537-fetch-shallow.sh
@@ -187,7 +187,7 @@ test_expect_success 'fetch --update-shallow (with fetch.writeCommitGraph)' '
 	) &&
 	(
 	cd notshallow &&
-	test_config fetch.writeCommitGraph true &&
+	git config fetch.writeCommitGraph true &&
 	git fetch --update-shallow ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
 	git fsck &&
 	git for-each-ref --sort=refname --format="%(refname)" >actual.refs &&
Jonathan Tan April 22, 2020, 6:05 p.m. UTC | #7
> Taylor Blau <me@ttaylorr.com> writes:
> 
> >> > @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
> >> >  	} else {
> >> >  		unlink(git_path_shallow(the_repository));
> >> >  		rollback_lock_file(&shallow_lock);
> >> > +		reset_repository_shallow(the_repository);
> >> >  	}
> >>
> >> Here, we reset only after we realize we cannot write the updated
> >> shallow file.  Intended?
> >
> > Yes, see this earlier discussion I had about it with Jonathan:
> > https://lore.kernel.org/git/20200416020509.225014-1-jonathantanmy@google.com/.
> 
> I did, and then I asked the question, because I couldn't quite get
> if JTan was asking a question similar to the one he asked earlier in
> the message ("do you need a reset in the "else" branch as well?"),
> or if he was saying what he sees there, "the opposite case", was
> good.

Sorry for not being clear. My intention was to ask a question similar to
the earlier one - in this case, and in the previous case, I think that
the reset should happen no matter whether we execute the "if" case or
the "else" case, so we should just put it right after the entire "if"
statement.
Junio C Hamano April 22, 2020, 6:15 p.m. UTC | #8
Jonathan Tan <jonathantanmy@google.com> writes:

>> @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
>>  	} else {
>>  		unlink(git_path_shallow(the_repository));
>>  		rollback_lock_file(&shallow_lock);
>> +		reset_repository_shallow(the_repository);
>>  	}
>>  	strbuf_release(&sb);
>>  }
>
> The "if" part (not quoted here) commits the shallow lock file, and thus
> possibly modifies (or creates) the shallow file, so I think we need to
> put reset_repository_shallow() outside the whole "if" block. I have done
> that in the patch after the scissors.

Is there any rollback_lock_file() or commit_lock_file() call on the
shallow lock file in the files involved in this patch that does not
need a call to reset_repository_shallow() left after your work?

What I am trying to get at is if it would be safer to have a pair of
thin wrapper for rolling back or committing a new version of new
shallow file, e.g. rollback_shallow_file() + commit_shallow_file(),
and replace calls to {rollback,commit}_lock_file() with calls to
them.
Taylor Blau April 23, 2020, 12:14 a.m. UTC | #9
On Wed, Apr 22, 2020 at 11:15:33AM -0700, Junio C Hamano wrote:
> Jonathan Tan <jonathantanmy@google.com> writes:
>
> >> @@ -414,6 +414,7 @@ void prune_shallow(unsigned options)
> >>  	} else {
> >>  		unlink(git_path_shallow(the_repository));
> >>  		rollback_lock_file(&shallow_lock);
> >> +		reset_repository_shallow(the_repository);
> >>  	}
> >>  	strbuf_release(&sb);
> >>  }
> >
> > The "if" part (not quoted here) commits the shallow lock file, and thus
> > possibly modifies (or creates) the shallow file, so I think we need to
> > put reset_repository_shallow() outside the whole "if" block. I have done
> > that in the patch after the scissors.
>
> Is there any rollback_lock_file() or commit_lock_file() call on the
> shallow lock file in the files involved in this patch that does not
> need a call to reset_repository_shallow() left after your work?
>
> What I am trying to get at is if it would be safer to have a pair of
> thin wrapper for rolling back or committing a new version of new
> shallow file, e.g. rollback_shallow_file() + commit_shallow_file(),
> and replace calls to {rollback,commit}_lock_file() with calls to
> them.

Very elegant. Thanks for an excellent suggestion. v2 incoming just as
soon as 'make test' finishes...

Thanks,
Taylor
Junio C Hamano April 23, 2020, 7:05 p.m. UTC | #10
Taylor Blau <me@ttaylorr.com> writes:

>> What I am trying to get at is if it would be safer to have a pair of
>> thin wrapper for rolling back or committing a new version of new
>> shallow file, e.g. rollback_shallow_file() + commit_shallow_file(),
>> and replace calls to {rollback,commit}_lock_file() with calls to
>> them.
>
> Very elegant. Thanks for an excellent suggestion. v2 incoming just as
> soon as 'make test' finishes...

Note that I didn't verify there is a case where we want not to call
reset_repository_shallow() after committing or rolling back, either
for performance or correctness purposes.  As long as the experts on
the codepaths involved are happy with the idea, I'd be happy, too.

JNieder raised the idea of using a different type to avoid calling
the bare rollback/commit functions by mistake.  It appears that, in
addition to these two functions, setup_alternate_shallow() needs to
be updated if we wanted to go that route, and it sounds like a good
idea to gain safety with minimal cost.

Thanks.
diff mbox series

Patch

diff --git a/commit.h b/commit.h
index 008a0fa4a0..ee1ba139d4 100644
--- a/commit.h
+++ b/commit.h
@@ -251,6 +251,7 @@  int register_shallow(struct repository *r, const struct object_id *oid);
 int unregister_shallow(const struct object_id *oid);
 int for_each_commit_graft(each_commit_graft_fn, void *);
 int is_repository_shallow(struct repository *r);
+void reset_repository_shallow(struct repository *r);
 struct commit_list *get_shallow_commits(struct object_array *heads,
 					int depth, int shallow_flag, int not_shallow_flag);
 struct commit_list *get_shallow_commits_by_rev_list(
diff --git a/fetch-pack.c b/fetch-pack.c
index 1734a573b0..684868bc17 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1632,6 +1632,7 @@  static void update_shallow(struct fetch_pack_args *args,
 			rollback_lock_file(&shallow_lock);
 		} else
 			commit_lock_file(&shallow_lock);
+		reset_repository_shallow(the_repository);
 		alternate_shallow_file = NULL;
 		return;
 	}
diff --git a/shallow.c b/shallow.c
index 7fd04afed1..9d1304e786 100644
--- a/shallow.c
+++ b/shallow.c
@@ -40,13 +40,6 @@  int register_shallow(struct repository *r, const struct object_id *oid)

 int is_repository_shallow(struct repository *r)
 {
-	/*
-	 * NEEDSWORK: This function updates
-	 * r->parsed_objects->{is_shallow,shallow_stat} as a side effect but
-	 * there is no corresponding function to clear them when the shallow
-	 * file is updated.
-	 */
-
 	FILE *fp;
 	char buf[1024];
 	const char *path = r->parsed_objects->alternate_shallow_file;
@@ -79,6 +72,12 @@  int is_repository_shallow(struct repository *r)
 	return r->parsed_objects->is_shallow;
 }

+void reset_repository_shallow(struct repository *r)
+{
+	r->parsed_objects->is_shallow = -1;
+	stat_validity_clear(r->parsed_objects->shallow_stat);
+}
+
 /*
  * TODO: use "int" elemtype instead of "int *" when/if commit-slab
  * supports a "valid" flag.
@@ -362,6 +361,7 @@  void setup_alternate_shallow(struct lock_file *shallow_lock,
 		 * shallow file".
 		 */
 		*alternate_shallow_file = "";
+	reset_repository_shallow(the_repository);
 	strbuf_release(&sb);
 }

@@ -414,6 +414,7 @@  void prune_shallow(unsigned options)
 	} else {
 		unlink(git_path_shallow(the_repository));
 		rollback_lock_file(&shallow_lock);
+		reset_repository_shallow(the_repository);
 	}
 	strbuf_release(&sb);
 }
diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh
index 4f681dbbe1..c9c731c7a9 100755
--- a/t/t5537-fetch-shallow.sh
+++ b/t/t5537-fetch-shallow.sh
@@ -177,6 +177,42 @@  EOF
 	)
 '

+test_expect_success 'fetch --update-shallow (with fetch.writeCommitGraph)' '
+	(
+	cd shallow &&
+	git checkout master &&
+	commit 8 &&
+	git tag -m foo heavy-tag-for-graph HEAD^ &&
+	git tag light-tag-for-graph HEAD^:tracked
+	) &&
+	(
+	cd notshallow &&
+	test_config fetch.writeCommitGraph true &&
+	git fetch --update-shallow ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
+	git fsck &&
+	git for-each-ref --sort=refname --format="%(refname)" >actual.refs &&
+	cat <<EOF >expect.refs &&
+refs/remotes/shallow/master
+refs/remotes/shallow/no-shallow
+refs/tags/heavy-tag
+refs/tags/heavy-tag-for-graph
+refs/tags/light-tag
+refs/tags/light-tag-for-graph
+EOF
+	test_cmp expect.refs actual.refs &&
+	git log --format=%s shallow/master >actual &&
+	cat <<EOF >expect &&
+8
+7
+6
+5
+4
+3
+EOF
+	test_cmp expect actual
+	)
+'
+
 test_expect_success POSIXPERM,SANITY 'shallow fetch from a read-only repo' '
 	cp -R .git read-only.git &&
 	test_when_finished "find read-only.git -type d -print | xargs chmod +w" &&