Git development
 help / color / mirror / Atom feed
From: Wasswa Edson <edsonwasswa54@gmail.com>
To: git@vger.kernel.org
Cc: tangajoris@gmail.com, Wasswa Edson <edsonwasswa54@gmail.com>
Subject: [PATCH v2 0/3] credential: parse subcommands with OPT_SUBCOMMAND
Date: Sat, 14 Nov 2026 21:47:49 +0300	[thread overview]
Message-ID: <20261114184752.1417-1-edsonwasswa54@gmail.com> (raw)
In-Reply-To: <20261104202228.24642-1-edsonwasswa54@gmail.com>

"git credential" dispatches its subcommands by hand with strcmp().
This series converts it to OPT_SUBCOMMAND(), so that each subcommand
gets its own "-h" usage and the subcommand names become available
to completion.

Changes since v1:

 - Split into three patches. The pre-existing mismatch between the
   "-h" output and the SYNOPSIS (1/3) and a refactor moving each
   subcommand into its own function (2/3) are now separate from the
   switch to OPT_SUBCOMMAND() (3/3).

 - Factor the repeated parse_options() call and argument check into
   a helper, which rejects leftover arguments with "too many
   arguments" (Tanga).

 - Add a test for the rejection of extra arguments (Tanga).

 - Drop the claim that the code gets shorter (Tanga).

v1: https://lore.kernel.org/git/20261104202228.24642-1-edsonwasswa54@gmail.com/

Wasswa Edson (3):
  credential: make -h output and SYNOPSIS agree
  credential: move each subcommand into its own function
  credential: parse subcommands with OPT_SUBCOMMAND

 Documentation/git-credential.adoc |   3 +-
 builtin/credential.c              | 143 +++++++++++++++++++++++-------
 git.c                             |   2 +-
 t/t0300-credentials.sh            |  27 ++++++
 t/t0450/adoc-help-mismatches      |   1 -
 5 files changed, 142 insertions(+), 34 deletions(-)

Range-diff against v1:
-:  ---------- > 1:  d1bffb83df credential: make -h output and SYNOPSIS agree
-:  ---------- > 2:  c7708ac31b credential: move each subcommand into its own function
1:  85e3f2ce94 ! 3:  7c9842bb9e credential: parse subcommands with OPT_SUBCOMMAND
    @@ Metadata
      ## Commit message ##
         credential: parse subcommands with OPT_SUBCOMMAND
     
    -    "git credential" dispatches its four subcommands (fill, approve,
    -    reject, capability) with a strcmp() chain after an argc check,
    -    instead of using the parse-options API used by other builtins.
    +    Now that each subcommand of "git credential" lives in its own
    +    function, switch the dispatch to OPT_SUBCOMMAND(). This gives each
    +    subcommand its own usage string, so that "git credential <subcommand>
    +    -h" shows help for that subcommand, and makes the subcommand names
    +    available to the completion via --git-completion-helper.
     
    -    Convert the dispatch to OPT_SUBCOMMAND(). Besides making the code
    -    shorter and more idiomatic, each subcommand gets its own usage
    -    string, so "git credential <subcommand> -h" shows a targeted help
    -    text, and the subcommand names are reported via
    -    --git-completion-helper.
    +    Each subcommand takes neither options nor arguments; reject any
    +    with a common helper. Drop NO_PARSEOPT from the git.c entry, as the
    +    command now uses parse-options.
     
    -    Since the builtin now uses parse-options, drop NO_PARSEOPT from its
    -    git.c entry so "git --list-cmds=parseopt" includes it.
    +    This changes the behavior only on error paths:
     
    -    Two pre-existing inconsistencies are fixed while at it:
    +     - A missing or unknown subcommand is reported as such ("need a
    +       subcommand" / "unknown subcommand") before the usage text.
     
    -    - The usage string did not mention "capability" even though it is a
    -      documented subcommand; it now lists all four.
    -    - Documentation/git-credential.adoc marked its SYNOPSIS as a plain
    -      literal block, which t0450 cannot extract; mark it [verse] and drop
    -      "credential" from t0450/adoc-help-mismatches.
    +     - Extra arguments are reported as "too many arguments" before the
    +       usage text of the subcommand.
     
    -    Behavior changes are limited to error paths: a missing or unknown
    -    subcommand is reported as such ("need a subcommand" / "unknown
    -    subcommand") before the usage text, instead of only showing the
    -    usage text, and "git credential <subcommand> -h" exits 0 after
    -    showing the subcommand's usage. Exit codes for failures are
    -    unchanged (129).
    +     - "git credential <subcommand> -h" shows the usage of that
    +       subcommand and exits with 0, instead of failing with the usage of
    +       the whole command.
     
    -    Signed-off-by: Wasswa Edson <edsonwasswa54@gmail.com>
    +    Failures still exit with 129.
     
    - ## Documentation/git-credential.adoc ##
    -@@ Documentation/git-credential.adoc: git-credential - Retrieve and store user credentials
    - 
    - SYNOPSIS
    - --------
    --------------------
    -+[verse]
    - 'git credential' (fill|approve|reject|capability)
    --------------------
    - 
    - DESCRIPTION
    - -----------
    +    Helped-by: Tanga Joris <tangajoris@gmail.com>
    +    Signed-off-by: Wasswa Edson <edsonwasswa54@gmail.com>
     
      ## builtin/credential.c ##
     @@
    @@ builtin/credential.c
      #include "config.h"
     +#include "gettext.h"
     +#include "parse-options.h"
    - 
    --static const char usage_msg[] =
    --	"git credential (fill|approve|reject)";
    ++
     +static const char * const credential_usage[] = {
     +	N_("git credential (fill|approve|reject|capability)"),
     +	NULL,
     +};
    - 
    --int cmd_credential(int argc,
    --		   const char **argv,
    --		   const char *prefix UNUSED,
    --		   struct repository *repo UNUSED)
    ++
     +static const char * const credential_fill_usage[] = {
     +	N_("git credential fill"),
     +	NULL,
    @@ builtin/credential.c
     +	NULL,
     +};
     +
    ++static void parse_no_args(int argc, const char **argv, const char *prefix,
    ++			  const char * const *usagestr)
    ++{
    ++	struct option options[] = {
    ++		OPT_END()
    ++	};
    + 
    +-static const char usage_msg[] =
    +-	"git credential (fill|approve|reject|capability)";
    ++	if (parse_options(argc, argv, prefix, options, usagestr, 0))
    ++		usage_msg_opt(_("too many arguments"), usagestr, options);
    ++}
    + 
    +-static int cmd_credential_fill(int argc UNUSED, const char **argv UNUSED,
    +-			       const char *prefix UNUSED,
     +static int cmd_credential_fill(int argc, const char **argv, const char *prefix,
    -+			       struct repository *repo UNUSED)
    + 			       struct repository *repo UNUSED)
      {
    --	const char *op;
    -+	struct option options[] = { OPT_END() };
      	struct credential c = CREDENTIAL_INIT;
      
    --	repo_config(the_repository, git_default_config, NULL);
    -+	argc = parse_options(argc, argv, prefix, options,
    -+			     credential_fill_usage, 0);
    -+	if (argc)
    -+		usage_with_options(credential_fill_usage, options);
    -+
    -+	if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0)
    -+		die("unable to read credential from stdin");
    ++	parse_no_args(argc, argv, prefix, credential_fill_usage);
     +
    -+	credential_fill(the_repository, &c, 0);
    -+	credential_next_state(&c);
    -+	credential_write(&c, stdout, CREDENTIAL_OP_RESPONSE);
    -+
    -+	credential_clear(&c);
    -+	return 0;
    -+}
    + 	if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0)
    + 		die("unable to read credential from stdin");
      
    --	show_usage_if_asked(argc, argv, usage_msg);
    --	if (argc != 2)
    --		usage(usage_msg);
    --	op = argv[1];
    -+static int cmd_credential_approve(int argc, const char **argv,
    -+				const char *prefix,
    -+				struct repository *repo UNUSED)
    -+{
    -+	struct option options[] = { OPT_END() };
    -+	struct credential c = CREDENTIAL_INIT;
    +@@ builtin/credential.c: static int cmd_credential_fill(int argc UNUSED, const char **argv UNUSED,
    + 	return 0;
    + }
      
    --	if (!strcmp(op, "capability")) {
    --		credential_set_all_capabilities(&c, CREDENTIAL_OP_INITIAL);
    --		credential_announce_capabilities(&c, stdout);
    --		return 0;
    --	}
    -+	argc = parse_options(argc, argv, prefix, options,
    -+			     credential_approve_usage, 0);
    -+	if (argc)
    -+		usage_with_options(credential_approve_usage, options);
    +-static int cmd_credential_approve(int argc UNUSED, const char **argv UNUSED,
    +-				  const char *prefix UNUSED,
    ++static int cmd_credential_approve(int argc, const char **argv,
    ++				  const char *prefix,
    + 				  struct repository *repo UNUSED)
    + {
    + 	struct credential c = CREDENTIAL_INIT;
      
    ++	parse_no_args(argc, argv, prefix, credential_approve_usage);
    ++
      	if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0)
      		die("unable to read credential from stdin");
      
    --	if (!strcmp(op, "fill")) {
    --		credential_fill(the_repository, &c, 0);
    --		credential_next_state(&c);
    --		credential_write(&c, stdout, CREDENTIAL_OP_RESPONSE);
    --	} else if (!strcmp(op, "approve")) {
    --		credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER);
    --		credential_approve(the_repository, &c);
    --	} else if (!strcmp(op, "reject")) {
    --		credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER);
    --		credential_reject(the_repository, &c);
    --	} else {
    --		usage(usage_msg);
    --	}
    -+	credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER);
    -+	credential_approve(the_repository, &c);
    - 
    - 	credential_clear(&c);
    +@@ builtin/credential.c: static int cmd_credential_approve(int argc UNUSED, const char **argv UNUSED,
      	return 0;
      }
    -+
    + 
    +-static int cmd_credential_reject(int argc UNUSED, const char **argv UNUSED,
    +-				 const char *prefix UNUSED,
     +static int cmd_credential_reject(int argc, const char **argv,
     +				 const char *prefix,
    -+				 struct repository *repo UNUSED)
    -+{
    -+	struct option options[] = { OPT_END() };
    -+	struct credential c = CREDENTIAL_INIT;
    -+
    -+	argc = parse_options(argc, argv, prefix, options,
    -+			     credential_reject_usage, 0);
    -+	if (argc)
    -+		usage_with_options(credential_reject_usage, options);
    -+
    -+	if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0)
    -+		die("unable to read credential from stdin");
    -+
    -+	credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER);
    -+	credential_reject(the_repository, &c);
    -+
    -+	credential_clear(&c);
    -+	return 0;
    -+}
    + 				 struct repository *repo UNUSED)
    + {
    + 	struct credential c = CREDENTIAL_INIT;
    + 
    ++	parse_no_args(argc, argv, prefix, credential_reject_usage);
     +
    + 	if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0)
    + 		die("unable to read credential from stdin");
    + 
    +@@ builtin/credential.c: static int cmd_credential_reject(int argc UNUSED, const char **argv UNUSED,
    + 	return 0;
    + }
    + 
    +-static int cmd_credential_capability(int argc UNUSED,
    +-				     const char **argv UNUSED,
    +-				     const char *prefix UNUSED,
     +static int cmd_credential_capability(int argc, const char **argv,
     +				     const char *prefix,
    -+				     struct repository *repo UNUSED)
    -+{
    -+	struct option options[] = { OPT_END() };
    -+	struct credential c = CREDENTIAL_INIT;
    -+
    -+	argc = parse_options(argc, argv, prefix, options,
    -+			     credential_capability_usage, 0);
    -+	if (argc)
    -+		usage_with_options(credential_capability_usage, options);
    -+
    -+	credential_set_all_capabilities(&c, CREDENTIAL_OP_INITIAL);
    -+	credential_announce_capabilities(&c, stdout);
    -+
    -+	credential_clear(&c);
    -+	return 0;
    -+}
    + 				     struct repository *repo UNUSED)
    + {
    + 	struct credential c = CREDENTIAL_INIT;
    + 
    ++	parse_no_args(argc, argv, prefix, credential_capability_usage);
     +
    -+int cmd_credential(int argc,
    -+		   const char **argv,
    -+		   const char *prefix,
    -+		   struct repository *repo)
    -+{
    + 	credential_set_all_capabilities(&c, CREDENTIAL_OP_INITIAL);
    + 	credential_announce_capabilities(&c, stdout);
    + 
    +@@ builtin/credential.c: int cmd_credential(int argc,
    + 		   const char *prefix,
    + 		   struct repository *repo)
    + {
    +-	const char *op;
     +	parse_opt_subcommand_fn *fn = NULL;
     +	struct option options[] = {
     +		OPT_SUBCOMMAND("fill", &fn, cmd_credential_fill),
    @@ builtin/credential.c
     +		OPT_SUBCOMMAND("capability", &fn, cmd_credential_capability),
     +		OPT_END()
     +	};
    -+
    -+	repo_config(the_repository, git_default_config, NULL);
    -+
    + 
    + 	repo_config(the_repository, git_default_config, NULL);
    + 
    +-	show_usage_if_asked(argc, argv, usage_msg);
    +-	if (argc != 2)
    +-		usage(usage_msg);
    +-	op = argv[1];
    +-
    +-	if (!strcmp(op, "fill"))
    +-		return cmd_credential_fill(argc - 1, argv + 1, prefix, repo);
    +-	if (!strcmp(op, "approve"))
    +-		return cmd_credential_approve(argc - 1, argv + 1, prefix, repo);
    +-	if (!strcmp(op, "reject"))
    +-		return cmd_credential_reject(argc - 1, argv + 1, prefix, repo);
    +-	if (!strcmp(op, "capability"))
    +-		return cmd_credential_capability(argc - 1, argv + 1, prefix,
    +-						 repo);
    +-
    +-	usage(usage_msg);
     +	argc = parse_options(argc, argv, prefix, options, credential_usage, 0);
     +
     +	return fn(argc, argv, prefix, repo);
    -+}
    + }
     
      ## git.c ##
     @@ git.c: static struct cmd_struct commands[] = {
    @@ t/t0300-credentials.sh: test_expect_success 'interactive prompt is sanitized' '
     +	test_expect_code 129 git credential foo 2>err &&
     +	test_grep "unknown subcommand" err
     +'
    ++
    ++test_expect_success 'credential subcommands reject extra arguments' '
    ++	for sub in fill approve reject capability
    ++	do
    ++		test_expect_code 129 git credential $sub extra \
    ++			</dev/null 2>err &&
    ++		test_grep "too many arguments" err || return 1
    ++	done
    ++'
     +
      test_done
    -
    - ## t/t0450/adoc-help-mismatches ##
    -@@ t/t0450/adoc-help-mismatches: checkout
    - checkout-index
    - clone
    - column
    --credential
    - credential-cache
    - credential-store
    - fetch-pack

base-commit: 37b3475fb530c14109211faca33716a173db61ed
-- 
2.53.0


  parent reply	other threads:[~2026-11-14 18:48 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 20:22 [PATCH] credential: parse subcommands with OPT_SUBCOMMAND Wasswa Edson
2026-11-08 22:28 ` Tanga Joris
2026-11-14 18:47 ` Wasswa Edson [this message]
2026-11-14 18:47   ` [PATCH v2 1/3] credential: make -h output and SYNOPSIS agree Wasswa Edson
2026-11-14 18:47   ` [PATCH v2 2/3] credential: move each subcommand into its own function Wasswa Edson
2026-11-14 18:47   ` [PATCH v2 3/3] credential: parse subcommands with OPT_SUBCOMMAND Wasswa Edson

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=20261114184752.1417-1-edsonwasswa54@gmail.com \
    --to=edsonwasswa54@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=tangajoris@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox