Subject: Re: [PATCH 1/2] Teach send-pack & push to --send-up-to-date refs

From: Junio C Hamano <[email-masked]>
Date: Wed, 13 Mar 2024 16:38:58 -0700

Christopher Lindee <[email-masked]> writes:

> Subject: Re: [PATCH 1/2] Teach send-pack & push to --send-up-to-date refs

cf. Documentation/SubmittingPatches:describe-changes.

Perhaps

	[PATCH 1/2] push: allow pushing a no-op update to refs

or something?

> Provide an option that forces the local Git transport to transmit a local
> ref even when the receiving ref would not change (i.e. the local & remote
> refs point to the same object).  This is not done normally, as no changes
> would take place on the server in a vanilla Git setup.  However, some Git
> servers support push options and those push options decide which branches
> to operate on based on the refs that are transmitted alongside the option.
> This option ensures the refs listed on the command line are always sent.

Flip the order of description to give your observation of the
current behaviour first, your perceived problem in it next, and then
finally your solution.  That way, those who are totally unfamiliar
with the problem area can just read from start to end straight, and
those who know what we do currently can skip and start from your
problem description.

Also, it would be nice if you throw in the issue about missing ref
in the push certificate, which I mentioned in my response to the
cover letter.

>  Documentation/git-push.txt      | 8 +++++++-
>  Documentation/git-send-pack.txt | 7 +++++++
>  builtin/push.c                  | 1 +
>  builtin/send-pack.c             | 4 ++++
>  send-pack.c                     | 2 +-
>  send-pack.h                     | 3 ++-
>  transport-helper.c              | 7 ++++++-
>  transport.c                     | 1 +
>  transport.h                     | 1 +
>  9 files changed, 30 insertions(+), 4 deletions(-)

No tests?

>  'git push' [--all | --branches | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
>  	   [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-q | --quiet] [-v | --verbose]
> -	   [-u | --set-upstream] [-o <string> | --push-option=<string>]
> +	   [-u | --set-upstream] [-o <string> | --push-option=<string>] [--send-up-to-date]

I do not know if the new option is a name that is easy to
understand.  I am not great at naming, either but how does
"--force-no-op" sound?

> +	Usually, the command will not send a local ref when the remote ref
> +	already matches, as no change would occur if it did.  This flag

In the context of "push", 'match' is a verb that is used in
different contexts, like "'git push master' finds a ref that matches
'master' and updates refs/heads/master".  You would want to avoid it
when you can.

    Usually the command omits instructing the receiving end to
    update a ref to point at an object, if the target ref points at
    the exact object already, as no change ...

> +	disables that check.  This allows push options to be sent alongside
> +	up-to-date references on the remote.

Aside from options and push certificates, there may be other side
effects from this change.  I am not sure if we want to make sure we
enumerate the benefit all like this.  Perhaps drop "This allows ..."
altogether?

> diff --git a/send-pack.c b/send-pack.c
> index 37f59d4f66..30b0f2f276 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -313,7 +313,7 @@ static int check_to_send_update(const struct ref *ref, const struct send_pack_ar
>  	case REF_STATUS_REJECT_NODELETE:
>  		return CHECK_REF_STATUS_REJECTED;
>  	case REF_STATUS_UPTODATE:
> -		return CHECK_REF_UPTODATE;
> +		return args->send_uptodate ? 0 : CHECK_REF_UPTODATE;
>  
>  	default:
>  	case REF_STATUS_EXPECTING_REPORT:

Given the existing flow of this code, I would prefer to see it
written more like so:

diff --git i/send-pack.c w/send-pack.c
index 37f59d4f66..97d01726bb 100644
--- i/send-pack.c
+++ w/send-pack.c
@@ -313,8 +313,9 @@ static int check_to_send_update(const struct ref *ref, const struct send_pack_ar
 	case REF_STATUS_REJECT_NODELETE:
 		return CHECK_REF_STATUS_REJECTED;
 	case REF_STATUS_UPTODATE:
-		return CHECK_REF_UPTODATE;
-
+		if (!args->send_uptodate)
+			return CHECK_REF_UPTODATE;
+		/* fallthru */
 	default:
 	case REF_STATUS_EXPECTING_REPORT:
 		/* already passed checks on the local side */

to make it clear that the caller gets 0 ("go ahead and do it")
unless it is one of the cases explicitly listed abouve the "default"
label.

> +	int do_nop = flags & TRANSPORT_PUSH_SEND_UPTODATE;

Here we do call it "do_nop", showing that at least to you, "nop" is
a much more fitting word than "uptodate" for what we are trying to
achieve in this topic.  It would give us one piece of good input to
decide what the end-user facing name should be.  In fact it is where
I took inspiration from for "force-noop" I mentioned earlier.

> @@ -1010,7 +1011,11 @@ static int push_refs_with_push(struct transport *transport,
>  			} else
>  				continue;
>  		case REF_STATUS_UPTODATE:
> -			continue;
> +			if (do_nop) {
> +				; /* do nothing */
> +			}
> +			else
> +				continue;

Drop needless braces around a single-statement block.
Alternatively, we could write it like so:

			if (!do_nop)
				continue;
			/* fallthru */

but I think what you wrote, modulo the unnecessary braces, makes the
intent a bit more clear.

>  		default:
>  			; /* do nothing */
>  		}