All of lore.kernel.org
 help / color / mirror / Atom feed
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>
To: Bert Wesarg <bert.wesarg@googlemail.com>
Cc: git@vger.kernel.org, Junio C Hamano <gitster@pobox.com>,
	Matthew Rogers <mattr94@gmail.com>
Subject: Re: [PATCH v4] remote rename/remove: gently handle remote.pushDefault config
Date: Sun, 2 Feb 2020 21:54:31 +0100 (CET)	[thread overview]
Message-ID: <nycvar.QRO.7.76.6.2002021911260.46@tvgsbejvaqbjf.bet> (raw)
In-Reply-To: <04a8673c3cb80802ee20fa4376872cb5ee464264.1580549512.git.bert.wesarg@googlemail.com>

Hi Bert,

On Sat, 1 Feb 2020, Bert Wesarg wrote:

> When renaming a remote with
>
>     git remote rename X Y
>     git remote remove X
>
> Git already renames or removes any branch.<name>.remote and
> branch.<name>.pushRemote configurations if their value is X.
>
> However remote.pushDefault needs a more gentle approach, as this may be
> set in a non-repo configuration file. In such a case only a warning is
> printed, such as:
>
> warning: The global configuration remote.pushDefault in:
> 	$HOME/.gitconfig:35
> now names the non-existent remote origin
>
> It is changed to remote.pushDefault = Y or removed when set in a repo
> configuration though.
>
> Signed-off-by: Bert Wesarg <bert.wesarg@googlemail.com>

Very clear commit message. Thank you!

> diff --git a/builtin/remote.c b/builtin/remote.c
> index a2379a14bf..5af06b74a7 100644
> --- a/builtin/remote.c
> +++ b/builtin/remote.c
> @@ -615,6 +615,55 @@ static int migrate_file(struct remote *remote)
>  	return 0;
>  }
>
> +struct push_default_info
> +{
> +	const char *old_name;
> +	enum config_scope scope;
> +	struct strbuf origin;
> +	int linenr;
> +};
> +
> +static int config_read_push_default(const char *key, const char *value,
> +	void *cb)
> +{
> +	struct push_default_info* info = cb;
> +	if (strcmp(key, "remote.pushdefault") || strcmp(value, info->old_name))
> +		return 0;

We will have to be careful to not segfault if a user has this in their
config:

	[remote]
		pushDefault

i.e. we have to insert `!value || ` before the call to `strcmp()`.

It does not make much sense not to specify a value, of course, but we
should not segfault in such a case, either.

> +
> +	info->scope = current_config_scope();

Do we want to care about the case where above-mentioned invalid
`remote.pushDefault` is configured _and_ overrides an otherwise-valid
setting in `~/.gitconfig`?

Or for that matter, shouldn't we be careful to handle the case where `git
config --global remote.pushDefault` returns `old_name` but that is
overridden by a different `git config --local remote.pushDefault`?

Concretely, I believe that the patched code will misbehave in this
scenario:

	git config --global remote.pushDefault january
	git config remote.pushDefault february
	git remote rename january march

If I read the patch right, this will incorrectly warn about the
`pushDefault` setting in the user-wide config.

> +	strbuf_reset(&info->origin);
> +	strbuf_addstr(&info->origin, current_config_name());
> +	info->linenr = current_config_line();
> +
> +	return 0;
> +}
> +
> +static void handle_push_default(const char* old_name, const char* new_name)

That name probably wants to convey better that the push default is handled
in the `mv`/`rm` commands here, not in any other command. Maybe
`handle_modified_push_default_remote()`?

> +{
> +	struct push_default_info push_default = {
> +		old_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 };

Personally, I would prefer the closing bracket to be on a new line,
followed by an empty line to separate the variable declaration from the
following statements.

> +	git_config(config_read_push_default, &push_default);
> +	if (push_default.scope >= CONFIG_SCOPE_COMMAND)
> +		; /* pass */
> +	else if (push_default.scope >= CONFIG_SCOPE_LOCAL) {
> +		int result = git_config_set_gently("remote.pushDefault",
> +						   new_name);
> +		if (new_name && result && result != CONFIG_NOTHING_SET)
> +			die(_("could not set '%s'"), "remote.pushDefault");

Isn't this more like a `BUG()`? Or do you see any valid scenario where
this could happen? If you do, it may make a lot of sense to call
`die_errno()` here, to give the user _some_ sort of an actionable insight
as to what went wrong.

> +		else if (!new_name && result && result != CONFIG_NOTHING_SET)
> +			die(_("could not unset '%s'"), "remote.pushDefault");

Same here.

> +	} else if (push_default.scope >= CONFIG_SCOPE_SYSTEM) {
> +		/* warn */
> +		warning(_("The %s configuration remote.pushDefault in:\n"
> +			  "\t%s:%d\n"
> +			  "now names the non-existent remote '%s'"),
> +			config_scope_name(push_default.scope),
> +			push_default.origin.buf, push_default.linenr,
> +			old_name);
> +	}
> +}
> +
> +
>  static int mv(int argc, const char **argv)
>  {
>  	struct option options[] = {
> @@ -750,6 +799,9 @@ static int mv(int argc, const char **argv)
>  			die(_("creating '%s' failed"), buf.buf);
>  	}
>  	string_list_clear(&remote_branches, 1);
> +
> +	handle_push_default(rename.old_name, rename.new_name);
> +
>  	return 0;
>  }
>
> @@ -835,6 +887,8 @@ static int rm(int argc, const char **argv)
>  		strbuf_addf(&buf, "remote.%s", remote->name);
>  		if (git_config_rename_section(buf.buf, NULL) < 1)
>  			return error(_("Could not remove config section '%s'"), buf.buf);
> +
> +		handle_push_default(remote->name, NULL);
>  	}
>
>  	return result;
> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
> index 082042b05a..dda81b7d07 100755
> --- a/t/t5505-remote.sh
> +++ b/t/t5505-remote.sh
> @@ -734,6 +734,7 @@ test_expect_success 'reject adding remote with an invalid name' '
>  # the last two ones check if the config is updated.
>
>  test_expect_success 'rename a remote' '
> +	test_config_global remote.pushDefault origin &&
>  	git clone one four &&
>  	(
>  		cd four &&
> @@ -744,7 +745,42 @@ test_expect_success 'rename a remote' '
>  		test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
>  		test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
>  		test "$(git config branch.master.remote)" = "upstream" &&
> -		test "$(git config branch.master.pushRemote)" = "upstream"
> +		test "$(git config branch.master.pushRemote)" = "upstream" &&
> +		test "$(git config --global remote.pushDefault)" = "origin"
> +	)
> +'
> +
> +test_expect_success 'rename a remote renames repo remote.pushDefault' '
> +	git clone one four.1 &&

I am not sure that a full clone is warranted here. Maybe use
`--no-checkout` here and in the subsequent test cases? Omitting that
option makes the tests only slower for no gain.

> +	(
> +		cd four.1 &&
> +		git config remote.pushDefault origin &&
> +		git remote rename origin upstream &&
> +		test "$(git config --local remote.pushDefault)" = "upstream"
> +	)
> +'
> +
> +test_expect_success 'rename a remote renames repo remote.pushDefault but ignores global' '
> +	test_config_global remote.pushDefault other &&
> +	git clone one four.2 &&
> +	(
> +		cd four.2 &&
> +		git config remote.pushDefault origin &&
> +		git remote rename origin upstream &&
> +		test "$(git config --global remote.pushDefault)" = "other" &&
> +		test "$(git config --local remote.pushDefault)" = "upstream"
> +	)
> +'
> +
> +test_expect_success 'rename a remote renames repo remote.pushDefault but keeps global' '
> +	test_config_global remote.pushDefault origin &&
> +	git clone one four.3 &&
> +	(
> +		cd four.3 &&
> +		git config remote.pushDefault origin &&
> +		git remote rename origin upstream &&
> +		test "$(git config --global remote.pushDefault)" = "origin" &&
> +		test "$(git config --local remote.pushDefault)" = "upstream"

A lot of tests added. Personally, I would have probably only extended the
existing `rename a remote` to verify that a repository-local
`remote.pushDefault` _is_ renamed, and then added one test case that
verifies that not only is a user-wide `remote.pushDefault` left alone but
also warned about.

>  	)
>  '
>
> @@ -787,6 +823,7 @@ test_expect_success 'rename succeeds with existing remote.<target>.prune' '
>  '
>
>  test_expect_success 'remove a remote' '
> +	test_config_global remote.pushDefault origin &&
>  	git clone one four.five &&
>  	(
>  		cd four.five &&
> @@ -794,7 +831,42 @@ test_expect_success 'remove a remote' '
>  		git remote remove origin &&
>  		test -z "$(git for-each-ref refs/remotes/origin)" &&
>  		test_must_fail git config branch.master.remote &&
> -		test_must_fail git config branch.master.pushRemote
> +		test_must_fail git config branch.master.pushRemote &&
> +		test "$(git config --global remote.pushDefault)" = "origin"
> +	)
> +'
> +
> +test_expect_success 'remove a remote removes repo remote.pushDefault' '
> +	git clone one four.five.1 &&
> +	(
> +		cd four.five.1 &&
> +		git config remote.pushDefault origin &&
> +		git remote remove origin &&
> +		test_must_fail git config --local remote.pushDefault

Now that I see this sort of "in action", I have to wonder whether I would
be okay with a `remote.pushDefault` simply vanishing. I think that would
puzzle me ("Didn't I set a push default before? I must be getting old and
delusional."). In the least, I would want to see a warning here ("As the
remote 'xyz' was deleted, so was the `remote.pushDefault = xyz` setting"
or some such).

It strikes me as a fundamentally different thing whether we simply
re-target or delete a `pushDefault` setting. The former needs no further
warning, but the latter does.

> +	)
> +'
> +
> +test_expect_success 'remove a remote removes repo remote.pushDefault but ignores global' '
> +	test_config_global remote.pushDefault other &&
> +	git clone one four.five.2 &&
> +	(
> +		cd four.five.2 &&
> +		git config remote.pushDefault origin &&
> +		git remote remove origin &&
> +		test "$(git config --global remote.pushDefault)" = "other" &&
> +		test_must_fail git config --local remote.pushDefault
> +	)
> +'
> +
> +test_expect_success 'remove a remote removes repo remote.pushDefault but keeps global' '
> +	test_config_global remote.pushDefault origin &&
> +	git clone one four.five.3 &&
> +	(
> +		cd four.five.3 &&
> +		git config remote.pushDefault origin &&
> +		git remote remove origin &&
> +		test "$(git config --global remote.pushDefault)" = "origin" &&
> +		test_must_fail git config --local remote.pushDefault

Since the `mv` case already covers those code paths, I would be a lot more
parsimonious in adding test cases for `rm`.

There are voices claiming that adding regression tests is always a good
thing, but I would counter that it has to strike a balance between
coverage and runtime. We see a more and more contributions -- even from
long-time contributors -- where the test suite obviously has not been run
(because the CI builds are failing, and there is no doubt that seasoned
contributors in particular would fix those failures before contributing
their patches, _if_ they were aware of those test failures), proving just
how costly our test suite has become.

Other than that, the patch looks fine to me. Thank you,
Dscho

>  	)
>  '
>
> --
> 2.25.0.30.g00ce2e43d4
>
>

  reply	other threads:[~2020-02-02 20:54 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-01  9:34 [PATCH v4] remote rename/remove: gently handle remote.pushDefault config Bert Wesarg
2020-02-02 20:54 ` Johannes Schindelin [this message]
2020-02-04 20:11   ` Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=nycvar.QRO.7.76.6.2002021911260.46@tvgsbejvaqbjf.bet \
    --to=johannes.schindelin@gmx.de \
    --cc=bert.wesarg@googlemail.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=mattr94@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.