Files
git/builtin/remote-ext.c
Junio C Hamano f66d1423f5 builtin: send usage() help text to standard output
Using the show_usage_and_exit_if_asked() helper we introduced
earlier, fix callers of usage() that want to show the help text when
explicitly asked by the end-user.  The help text now goes to the
standard output stream for them.

These are the bog standard "if we got only '-h', then that is a
request for help" callers.  Their

	if (argc == 2 && !strcmp(argv[1], "-h"))
		usage(message);

are simply replaced with

	show_usage_and_exit_if_asked(argc, argv, message);

With this, the built-ins tested by t0012 all send their help text to
their standard output stream, so the check in t0012 that was half
tightened earlier is now fully tightened to insist on standard error
stream being empty.

Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-01-17 13:30:03 -08:00

212 lines
4.7 KiB
C

#include "builtin.h"
#include "transport.h"
#include "run-command.h"
#include "pkt-line.h"
static const char usage_msg[] =
"git remote-ext <remote> <url>";
/*
* URL syntax:
* 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
* Special characters:
* '% ': Literal space in argument.
* '%%': Literal percent sign.
* '%S': Name of service (git-upload-pack/git-upload-archive/
* git-receive-pack.
* '%s': Same as \s, but with possible git- prefix stripped.
* '%G': Only allowed as first 'character' of argument. Do not pass this
* Argument to command, instead send this as name of repository
* in in-line git://-style request (also activates sending this
* style of request).
* '%V': Only allowed as first 'character' of argument. Used in
* conjunction with '%G': Do not pass this argument to command,
* instead send this as vhost in git://-style request (note: does
* not activate sending git:// style request).
*/
static char *git_req;
static char *git_req_vhost;
static char *strip_escapes(const char *str, const char *service,
const char **next)
{
size_t rpos = 0;
int escape = 0;
char special = 0;
const char *service_noprefix = service;
struct strbuf ret = STRBUF_INIT;
skip_prefix(service_noprefix, "git-", &service_noprefix);
/* Pass the service to command. */
setenv("GIT_EXT_SERVICE", service, 1);
setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1);
/* Scan the length of argument. */
while (str[rpos] && (escape || str[rpos] != ' ')) {
if (escape) {
switch (str[rpos]) {
case ' ':
case '%':
case 's':
case 'S':
break;
case 'G':
case 'V':
special = str[rpos];
if (rpos == 1)
break;
/* fallthrough */
default:
die("Bad remote-ext placeholder '%%%c'.",
str[rpos]);
}
escape = 0;
} else
escape = (str[rpos] == '%');
rpos++;
}
if (escape && !str[rpos])
die("remote-ext command has incomplete placeholder");
*next = str + rpos;
if (**next == ' ')
++*next; /* Skip over space */
/*
* Do the actual placeholder substitution. The string will be short
* enough not to overflow integers.
*/
rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
escape = 0;
while (str[rpos] && (escape || str[rpos] != ' ')) {
if (escape) {
switch (str[rpos]) {
case ' ':
case '%':
strbuf_addch(&ret, str[rpos]);
break;
case 's':
strbuf_addstr(&ret, service_noprefix);
break;
case 'S':
strbuf_addstr(&ret, service);
break;
}
escape = 0;
} else
switch (str[rpos]) {
case '%':
escape = 1;
break;
default:
strbuf_addch(&ret, str[rpos]);
break;
}
rpos++;
}
switch (special) {
case 'G':
git_req = strbuf_detach(&ret, NULL);
return NULL;
case 'V':
git_req_vhost = strbuf_detach(&ret, NULL);
return NULL;
default:
return strbuf_detach(&ret, NULL);
}
}
static void parse_argv(struct strvec *out, const char *arg, const char *service)
{
while (*arg) {
char *expanded = strip_escapes(arg, service, &arg);
if (expanded)
strvec_push(out, expanded);
free(expanded);
}
}
static void send_git_request(int stdin_fd, const char *serv, const char *repo,
const char *vhost)
{
if (!vhost)
packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0);
else
packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
vhost, 0);
}
static int run_child(const char *arg, const char *service)
{
int r;
struct child_process child = CHILD_PROCESS_INIT;
child.in = -1;
child.out = -1;
child.err = 0;
parse_argv(&child.args, arg, service);
if (start_command(&child) < 0)
die("Can't run specified command");
if (git_req)
send_git_request(child.in, service, git_req, git_req_vhost);
r = bidirectional_transfer_loop(child.out, child.in);
if (!r)
r = finish_command(&child);
else
finish_command(&child);
return r;
}
#define MAXCOMMAND 4096
static int command_loop(const char *child)
{
char buffer[MAXCOMMAND];
while (1) {
size_t i;
const char *arg;
if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
if (ferror(stdin))
die("Command input error");
exit(0);
}
/* Strip end of line characters. */
i = strlen(buffer);
while (i > 0 && isspace(buffer[i - 1]))
buffer[--i] = 0;
if (!strcmp(buffer, "capabilities")) {
printf("*connect\n\n");
fflush(stdout);
} else if (skip_prefix(buffer, "connect ", &arg)) {
printf("\n");
fflush(stdout);
return run_child(child, arg);
} else {
fprintf(stderr, "Bad command");
return 1;
}
}
}
int cmd_remote_ext(int argc,
const char **argv,
const char *prefix,
struct repository *repo UNUSED)
{
BUG_ON_NON_EMPTY_PREFIX(prefix);
show_usage_if_asked(argc, argv, usage_msg);
if (argc != 3)
usage(usage_msg);
return command_loop(argv[2]);
}