7915691377
It's usually a bad idea for a builtin's cmd_foo() to ignore the "prefix" argument it gets, as it needs to prepend that string when accessing any paths given by the user. But if a builtin does not ask for the git wrapper to run repository setup (via the RUN_SETUP or RUN_SETUP_GENTLY flags), then we know the prefix will always be NULL (it is adjusting for the chdir() done during repo setup, but there cannot be one if we did not set up the repo). In those cases it's OK to ignore "prefix", but it's worth annotating for a few reasons: 1. It serves as documentation to somebody reading the code about what we expect. 2. If the flags in git.c ever change, the run-time assertion may help detect the problem (though only if the command is run from a subdirectory of the repository). 3. It notes to the compiler that we are OK ignoring "prefix". In particular, this silences -Wunused-parameter. It _could_ also help the compiler generate better code (because it will know the prefix is NULL), but in practice this is quite unlikely to matter. Note that I've only added this annotation to commands which triggered -Wunused-parameter. It would be correct to add it to any builtin which doesn't ask for RUN_SETUP, but most of the rest of them do the sensible thing with "prefix" by passing it to parse_options(). So they're much more likely to just work if they ever switched to RUN_SETUP, and aren't worth annotating. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
207 lines
4.6 KiB
C
207 lines
4.6 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)
|
|
{
|
|
BUG_ON_NON_EMPTY_PREFIX(prefix);
|
|
|
|
if (argc != 3)
|
|
usage(usage_msg);
|
|
|
|
return command_loop(argv[2]);
|
|
}
|