Skip to content

Commit 1a11195

Browse files
committed
Merge branch 'tb/clone-ssh-with-colon-for-port' into maint
Remote repository URL expressed in scp-style host:path notation are parsed more carefully (e.g. "foo/bar:baz" is local, "[::1]:/~user" asks to connect to user's home directory on host at address ::1. * tb/clone-ssh-with-colon-for-port: git_connect(): use common return point connect.c: refactor url parsing git_connect(): refactor the port handling for ssh git fetch: support host:/~repo t5500: add test cases for diag-url git fetch-pack: add --diag-url git_connect: factor out discovery of the protocol and its parts git_connect: remove artificial limit of a remote command t5601: add tests for ssh t5601: remove clear_ssh, refactor setup_ssh_wrapper
2 parents bf03d6e + a2036d7 commit 1a11195

File tree

7 files changed

+350
-147
lines changed

7 files changed

+350
-147
lines changed

builtin/fetch-pack.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
static const char fetch_pack_usage[] =
88
"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
99
"[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
10-
"[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
10+
"[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]";
1111

1212
static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc,
1313
const char *name, int namelen)
@@ -81,6 +81,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
8181
args.stdin_refs = 1;
8282
continue;
8383
}
84+
if (!strcmp("--diag-url", arg)) {
85+
args.diag_url = 1;
86+
continue;
87+
}
8488
if (!strcmp("-v", arg)) {
8589
args.verbose = 1;
8690
continue;
@@ -146,10 +150,14 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
146150
fd[0] = 0;
147151
fd[1] = 1;
148152
} else {
153+
int flags = args.verbose ? CONNECT_VERBOSE : 0;
154+
if (args.diag_url)
155+
flags |= CONNECT_DIAG_URL;
149156
conn = git_connect(fd, dest, args.uploadpack,
150-
args.verbose ? CONNECT_VERBOSE : 0);
157+
flags);
158+
if (!conn)
159+
return args.diag_url ? 0 : 1;
151160
}
152-
153161
get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL);
154162

155163
ref = fetch_pack(&args, fd, conn, ref, dest,

connect.c

Lines changed: 136 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,34 @@ int server_supports(const char *feature)
232232

233233
enum protocol {
234234
PROTO_LOCAL = 1,
235+
PROTO_FILE,
235236
PROTO_SSH,
236237
PROTO_GIT
237238
};
238239

240+
int url_is_local_not_ssh(const char *url)
241+
{
242+
const char *colon = strchr(url, ':');
243+
const char *slash = strchr(url, '/');
244+
return !colon || (slash && slash < colon) ||
245+
has_dos_drive_prefix(url);
246+
}
247+
248+
static const char *prot_name(enum protocol protocol)
249+
{
250+
switch (protocol) {
251+
case PROTO_LOCAL:
252+
case PROTO_FILE:
253+
return "file";
254+
case PROTO_SSH:
255+
return "ssh";
256+
case PROTO_GIT:
257+
return "git";
258+
default:
259+
return "unkown protocol";
260+
}
261+
}
262+
239263
static enum protocol get_protocol(const char *name)
240264
{
241265
if (!strcmp(name, "ssh"))
@@ -247,7 +271,7 @@ static enum protocol get_protocol(const char *name)
247271
if (!strcmp(name, "ssh+git"))
248272
return PROTO_SSH;
249273
if (!strcmp(name, "file"))
250-
return PROTO_LOCAL;
274+
return PROTO_FILE;
251275
die("I don't handle protocol '%s'", name);
252276
}
253277

@@ -527,55 +551,31 @@ static struct child_process *git_proxy_connect(int fd[2], char *host)
527551
return proxy;
528552
}
529553

530-
#define MAX_CMD_LEN 1024
531-
532-
static char *get_port(char *host)
554+
static const char *get_port_numeric(const char *p)
533555
{
534556
char *end;
535-
char *p = strchr(host, ':');
536-
537557
if (p) {
538558
long port = strtol(p + 1, &end, 10);
539559
if (end != p + 1 && *end == '\0' && 0 <= port && port < 65536) {
540-
*p = '\0';
541-
return p+1;
560+
return p;
542561
}
543562
}
544563

545564
return NULL;
546565
}
547566

548-
static struct child_process no_fork;
549-
550567
/*
551-
* This returns a dummy child_process if the transport protocol does not
552-
* need fork(2), or a struct child_process object if it does. Once done,
553-
* finish the connection with finish_connect() with the value returned from
554-
* this function (it is safe to call finish_connect() with NULL to support
555-
* the former case).
556-
*
557-
* If it returns, the connect is successful; it just dies on errors (this
558-
* will hopefully be changed in a libification effort, to return NULL when
559-
* the connection failed).
568+
* Extract protocol and relevant parts from the specified connection URL.
569+
* The caller must free() the returned strings.
560570
*/
561-
struct child_process *git_connect(int fd[2], const char *url_orig,
562-
const char *prog, int flags)
571+
static enum protocol parse_connect_url(const char *url_orig, char **ret_host,
572+
char **ret_path)
563573
{
564574
char *url;
565575
char *host, *path;
566576
char *end;
567-
int c;
568-
struct child_process *conn = &no_fork;
577+
int separator = '/';
569578
enum protocol protocol = PROTO_LOCAL;
570-
int free_path = 0;
571-
char *port = NULL;
572-
const char **arg;
573-
struct strbuf cmd;
574-
575-
/* Without this we cannot rely on waitpid() to tell
576-
* what happened to our children.
577-
*/
578-
signal(SIGCHLD, SIG_DFL);
579579

580580
if (is_url(url_orig))
581581
url = url_decode(url_orig);
@@ -587,40 +587,33 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
587587
*host = '\0';
588588
protocol = get_protocol(url);
589589
host += 3;
590-
c = '/';
591590
} else {
592591
host = url;
593-
c = ':';
592+
if (!url_is_local_not_ssh(url)) {
593+
protocol = PROTO_SSH;
594+
separator = ':';
595+
}
594596
}
595597

596598
/*
597-
* Don't do destructive transforms with git:// as that
598-
* protocol code does '[]' unwrapping of its own.
599+
* Don't do destructive transforms as protocol code does
600+
* '[]' unwrapping in get_host_and_port()
599601
*/
600602
if (host[0] == '[') {
601603
end = strchr(host + 1, ']');
602604
if (end) {
603-
if (protocol != PROTO_GIT) {
604-
*end = 0;
605-
host++;
606-
}
607605
end++;
608606
} else
609607
end = host;
610608
} else
611609
end = host;
612610

613-
path = strchr(end, c);
614-
if (path && !has_dos_drive_prefix(end)) {
615-
if (c == ':') {
616-
if (host != url || path < strchrnul(host, '/')) {
617-
protocol = PROTO_SSH;
618-
*path++ = '\0';
619-
} else /* '/' in the host part, assume local path */
620-
path = end;
621-
}
622-
} else
611+
if (protocol == PROTO_LOCAL)
623612
path = end;
613+
else if (protocol == PROTO_FILE && has_dos_drive_prefix(end))
614+
path = end; /* "file://$(pwd)" may be "file://C:/projects/repo" */
615+
else
616+
path = strchr(end, separator);
624617

625618
if (!path || !*path)
626619
die("No path specified. See 'man git-pull' for valid url syntax");
@@ -629,33 +622,67 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
629622
* null-terminate hostname and point path to ~ for URL's like this:
630623
* ssh://host.xz/~user/repo
631624
*/
632-
if (protocol != PROTO_LOCAL && host != url) {
633-
char *ptr = path;
625+
626+
end = path; /* Need to \0 terminate host here */
627+
if (separator == ':')
628+
path++; /* path starts after ':' */
629+
if (protocol == PROTO_GIT || protocol == PROTO_SSH) {
634630
if (path[1] == '~')
635631
path++;
636-
else {
637-
path = xstrdup(ptr);
638-
free_path = 1;
639-
}
640-
641-
*ptr = '\0';
642632
}
643633

644-
/*
645-
* Add support for ssh port: ssh://host.xy:<port>/...
634+
path = xstrdup(path);
635+
*end = '\0';
636+
637+
*ret_host = xstrdup(host);
638+
*ret_path = path;
639+
free(url);
640+
return protocol;
641+
}
642+
643+
static struct child_process no_fork;
644+
645+
/*
646+
* This returns a dummy child_process if the transport protocol does not
647+
* need fork(2), or a struct child_process object if it does. Once done,
648+
* finish the connection with finish_connect() with the value returned from
649+
* this function (it is safe to call finish_connect() with NULL to support
650+
* the former case).
651+
*
652+
* If it returns, the connect is successful; it just dies on errors (this
653+
* will hopefully be changed in a libification effort, to return NULL when
654+
* the connection failed).
655+
*/
656+
struct child_process *git_connect(int fd[2], const char *url,
657+
const char *prog, int flags)
658+
{
659+
char *hostandport, *path;
660+
struct child_process *conn = &no_fork;
661+
enum protocol protocol;
662+
const char **arg;
663+
struct strbuf cmd = STRBUF_INIT;
664+
665+
/* Without this we cannot rely on waitpid() to tell
666+
* what happened to our children.
646667
*/
647-
if (protocol == PROTO_SSH && host != url)
648-
port = get_port(end);
668+
signal(SIGCHLD, SIG_DFL);
649669

650-
if (protocol == PROTO_GIT) {
670+
protocol = parse_connect_url(url, &hostandport, &path);
671+
if (flags & CONNECT_DIAG_URL) {
672+
printf("Diag: url=%s\n", url ? url : "NULL");
673+
printf("Diag: protocol=%s\n", prot_name(protocol));
674+
printf("Diag: hostandport=%s\n", hostandport ? hostandport : "NULL");
675+
printf("Diag: path=%s\n", path ? path : "NULL");
676+
conn = NULL;
677+
} else if (protocol == PROTO_GIT) {
651678
/* These underlying connection commands die() if they
652679
* cannot connect.
653680
*/
654-
char *target_host = xstrdup(host);
655-
if (git_use_proxy(host))
656-
conn = git_proxy_connect(fd, host);
681+
char *target_host = xstrdup(hostandport);
682+
if (git_use_proxy(hostandport))
683+
conn = git_proxy_connect(fd, hostandport);
657684
else
658-
git_tcp_connect(fd, host, flags);
685+
git_tcp_connect(fd, hostandport, flags);
659686
/*
660687
* Separate original protocol components prog and path
661688
* from extended host header with a NUL byte.
@@ -668,55 +695,51 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
668695
prog, path, 0,
669696
target_host, 0);
670697
free(target_host);
671-
free(url);
672-
if (free_path)
673-
free(path);
674-
return conn;
675-
}
676-
677-
conn = xcalloc(1, sizeof(*conn));
678-
679-
strbuf_init(&cmd, MAX_CMD_LEN);
680-
strbuf_addstr(&cmd, prog);
681-
strbuf_addch(&cmd, ' ');
682-
sq_quote_buf(&cmd, path);
683-
if (cmd.len >= MAX_CMD_LEN)
684-
die("command line too long");
685-
686-
conn->in = conn->out = -1;
687-
conn->argv = arg = xcalloc(7, sizeof(*arg));
688-
if (protocol == PROTO_SSH) {
689-
const char *ssh = getenv("GIT_SSH");
690-
int putty = ssh && strcasestr(ssh, "plink");
691-
if (!ssh) ssh = "ssh";
692-
693-
*arg++ = ssh;
694-
if (putty && !strcasestr(ssh, "tortoiseplink"))
695-
*arg++ = "-batch";
696-
if (port) {
697-
/* P is for PuTTY, p is for OpenSSH */
698-
*arg++ = putty ? "-P" : "-p";
699-
*arg++ = port;
698+
} else {
699+
conn = xcalloc(1, sizeof(*conn));
700+
701+
strbuf_addstr(&cmd, prog);
702+
strbuf_addch(&cmd, ' ');
703+
sq_quote_buf(&cmd, path);
704+
705+
conn->in = conn->out = -1;
706+
conn->argv = arg = xcalloc(7, sizeof(*arg));
707+
if (protocol == PROTO_SSH) {
708+
const char *ssh = getenv("GIT_SSH");
709+
int putty = ssh && strcasestr(ssh, "plink");
710+
char *ssh_host = hostandport;
711+
const char *port = NULL;
712+
get_host_and_port(&ssh_host, &port);
713+
port = get_port_numeric(port);
714+
715+
if (!ssh) ssh = "ssh";
716+
717+
*arg++ = ssh;
718+
if (putty && !strcasestr(ssh, "tortoiseplink"))
719+
*arg++ = "-batch";
720+
if (port) {
721+
/* P is for PuTTY, p is for OpenSSH */
722+
*arg++ = putty ? "-P" : "-p";
723+
*arg++ = port;
724+
}
725+
*arg++ = ssh_host;
726+
} else {
727+
/* remove repo-local variables from the environment */
728+
conn->env = local_repo_env;
729+
conn->use_shell = 1;
700730
}
701-
*arg++ = host;
702-
}
703-
else {
704-
/* remove repo-local variables from the environment */
705-
conn->env = local_repo_env;
706-
conn->use_shell = 1;
707-
}
708-
*arg++ = cmd.buf;
709-
*arg = NULL;
731+
*arg++ = cmd.buf;
732+
*arg = NULL;
710733

711-
if (start_command(conn))
712-
die("unable to fork");
734+
if (start_command(conn))
735+
die("unable to fork");
713736

714-
fd[0] = conn->out; /* read from child's stdout */
715-
fd[1] = conn->in; /* write to child's stdin */
716-
strbuf_release(&cmd);
717-
free(url);
718-
if (free_path)
719-
free(path);
737+
fd[0] = conn->out; /* read from child's stdout */
738+
fd[1] = conn->in; /* write to child's stdin */
739+
strbuf_release(&cmd);
740+
}
741+
free(hostandport);
742+
free(path);
720743
return conn;
721744
}
722745

connect.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
#define CONNECT_H
33

44
#define CONNECT_VERBOSE (1u << 0)
5+
#define CONNECT_DIAG_URL (1u << 1)
56
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
67
extern int finish_connect(struct child_process *conn);
78
extern int git_connection_is_socket(struct child_process *conn);
89
extern int server_supports(const char *feature);
910
extern int parse_feature_request(const char *features, const char *feature);
1011
extern const char *server_feature_value(const char *feature, int *len_ret);
12+
extern int url_is_local_not_ssh(const char *url);
1113

1214
#endif

0 commit comments

Comments
 (0)