|
|
|
#include "git-compat-util.h"
|
|
|
|
#include "cache.h"
|
|
|
|
#include "pkt-line.h"
|
|
|
|
#include "quote.h"
|
|
|
|
#include "refs.h"
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <signal.h>
|
|
|
|
|
|
|
|
static char *server_capabilities;
|
|
|
|
|
Improve git-peek-remote
This makes git-peek-remote able to basically do everything that
git-ls-remote does (but obviously just for the native protocol, so no
http[s]: or rsync: support).
The default behaviour is the same, but you can now give a mixture of
"--refs", "--tags" and "--heads" flags, where "--refs" forces
git-peek-remote to only show real refs (ie none of the fakey tag lookups,
but also not the special pseudo-refs like HEAD and MERGE_HEAD).
The "--tags" and "--heads" flags respectively limit the output to just
regular tags and heads, of course.
You can still also ask to limit them by name too.
You can combine the flags, so
git peek-remote --refs --tags .
will show all local _true_ tags, without the generated tag lookups
(compare the output without the "--refs" flag).
And "--tags --heads" will show both tags and heads, but will avoid (for
example) any special refs outside of the standard locations.
I'm also planning on adding a "--ignore-local" flag that allows us to ask
it to ignore any refs that we already have in the local tree, but that's
an independent thing.
All this is obviously gearing up to making "git fetch" cheaper.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
19 years ago
|
|
|
static int check_ref(const char *name, int len, unsigned int flags)
|
|
|
|
{
|
|
|
|
if (!flags)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (len < 5 || memcmp(name, "refs/", 5))
|
Improve git-peek-remote
This makes git-peek-remote able to basically do everything that
git-ls-remote does (but obviously just for the native protocol, so no
http[s]: or rsync: support).
The default behaviour is the same, but you can now give a mixture of
"--refs", "--tags" and "--heads" flags, where "--refs" forces
git-peek-remote to only show real refs (ie none of the fakey tag lookups,
but also not the special pseudo-refs like HEAD and MERGE_HEAD).
The "--tags" and "--heads" flags respectively limit the output to just
regular tags and heads, of course.
You can still also ask to limit them by name too.
You can combine the flags, so
git peek-remote --refs --tags .
will show all local _true_ tags, without the generated tag lookups
(compare the output without the "--refs" flag).
And "--tags --heads" will show both tags and heads, but will avoid (for
example) any special refs outside of the standard locations.
I'm also planning on adding a "--ignore-local" flag that allows us to ask
it to ignore any refs that we already have in the local tree, but that's
an independent thing.
All this is obviously gearing up to making "git fetch" cheaper.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
19 years ago
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Skip the "refs/" part */
|
|
|
|
name += 5;
|
|
|
|
len -= 5;
|
|
|
|
|
|
|
|
/* REF_NORMAL means that we don't want the magic fake tag refs */
|
|
|
|
if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* REF_HEADS means that we want regular branch heads */
|
|
|
|
if ((flags & REF_HEADS) && !memcmp(name, "heads/", 6))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* REF_TAGS means that we want tags */
|
|
|
|
if ((flags & REF_TAGS) && !memcmp(name, "tags/", 5))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* All type bits clear means that we are ok with anything */
|
|
|
|
return !(flags & ~REF_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read all the refs from the other end
|
|
|
|
*/
|
|
|
|
struct ref **get_remote_heads(int in, struct ref **list,
|
Improve git-peek-remote
This makes git-peek-remote able to basically do everything that
git-ls-remote does (but obviously just for the native protocol, so no
http[s]: or rsync: support).
The default behaviour is the same, but you can now give a mixture of
"--refs", "--tags" and "--heads" flags, where "--refs" forces
git-peek-remote to only show real refs (ie none of the fakey tag lookups,
but also not the special pseudo-refs like HEAD and MERGE_HEAD).
The "--tags" and "--heads" flags respectively limit the output to just
regular tags and heads, of course.
You can still also ask to limit them by name too.
You can combine the flags, so
git peek-remote --refs --tags .
will show all local _true_ tags, without the generated tag lookups
(compare the output without the "--refs" flag).
And "--tags --heads" will show both tags and heads, but will avoid (for
example) any special refs outside of the standard locations.
I'm also planning on adding a "--ignore-local" flag that allows us to ask
it to ignore any refs that we already have in the local tree, but that's
an independent thing.
All this is obviously gearing up to making "git fetch" cheaper.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
19 years ago
|
|
|
int nr_match, char **match,
|
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
*list = NULL;
|
|
|
|
for (;;) {
|
|
|
|
struct ref *ref;
|
|
|
|
unsigned char old_sha1[20];
|
|
|
|
static char buffer[1000];
|
|
|
|
char *name;
|
|
|
|
int len, name_len;
|
|
|
|
|
|
|
|
len = packet_read_line(in, buffer, sizeof(buffer));
|
|
|
|
if (!len)
|
|
|
|
break;
|
|
|
|
if (buffer[len-1] == '\n')
|
|
|
|
buffer[--len] = 0;
|
|
|
|
|
|
|
|
if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
|
|
|
|
die("protocol error: expected sha/ref, got '%s'", buffer);
|
|
|
|
name = buffer + 41;
|
|
|
|
|
|
|
|
name_len = strlen(name);
|
|
|
|
if (len != name_len + 41) {
|
|
|
|
if (server_capabilities)
|
|
|
|
free(server_capabilities);
|
|
|
|
server_capabilities = xstrdup(name + name_len + 1);
|
|
|
|
}
|
|
|
|
|
Improve git-peek-remote
This makes git-peek-remote able to basically do everything that
git-ls-remote does (but obviously just for the native protocol, so no
http[s]: or rsync: support).
The default behaviour is the same, but you can now give a mixture of
"--refs", "--tags" and "--heads" flags, where "--refs" forces
git-peek-remote to only show real refs (ie none of the fakey tag lookups,
but also not the special pseudo-refs like HEAD and MERGE_HEAD).
The "--tags" and "--heads" flags respectively limit the output to just
regular tags and heads, of course.
You can still also ask to limit them by name too.
You can combine the flags, so
git peek-remote --refs --tags .
will show all local _true_ tags, without the generated tag lookups
(compare the output without the "--refs" flag).
And "--tags --heads" will show both tags and heads, but will avoid (for
example) any special refs outside of the standard locations.
I'm also planning on adding a "--ignore-local" flag that allows us to ask
it to ignore any refs that we already have in the local tree, but that's
an independent thing.
All this is obviously gearing up to making "git fetch" cheaper.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
19 years ago
|
|
|
if (!check_ref(name, name_len, flags))
|
|
|
|
continue;
|
|
|
|
if (nr_match && !path_match(name, nr_match, match))
|
|
|
|
continue;
|
|
|
|
ref = xcalloc(1, sizeof(*ref) + len - 40);
|
|
|
|
hashcpy(ref->old_sha1, old_sha1);
|
|
|
|
memcpy(ref->name, buffer + 41, len - 40);
|
|
|
|
*list = ref;
|
|
|
|
list = &ref->next;
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
int server_supports(const char *feature)
|
|
|
|
{
|
|
|
|
return server_capabilities &&
|
|
|
|
strstr(server_capabilities, feature) != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int get_ack(int fd, unsigned char *result_sha1)
|
|
|
|
{
|
|
|
|
static char line[1000];
|
|
|
|
int len = packet_read_line(fd, line, sizeof(line));
|
|
|
|
|
|
|
|
if (!len)
|
|
|
|
die("git-fetch-pack: expected ACK/NAK, got EOF");
|
|
|
|
if (line[len-1] == '\n')
|
|
|
|
line[--len] = 0;
|
|
|
|
if (!strcmp(line, "NAK"))
|
|
|
|
return 0;
|
|
|
|
if (!strncmp(line, "ACK ", 4)) {
|
|
|
|
if (!get_sha1_hex(line+4, result_sha1)) {
|
|
|
|
if (strstr(line+45, "continue"))
|
|
|
|
return 2;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
|
|
|
|
}
|
|
|
|
|
|
|
|
int path_match(const char *path, int nr, char **match)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int pathlen = strlen(path);
|
|
|
|
|
|
|
|
for (i = 0; i < nr; i++) {
|
|
|
|
char *s = match[i];
|
|
|
|
int len = strlen(s);
|
|
|
|
|
|
|
|
if (!len || len > pathlen)
|
|
|
|
continue;
|
|
|
|
if (memcmp(path + pathlen - len, s, len))
|
|
|
|
continue;
|
|
|
|
if (pathlen > len && path[pathlen - len - 1] != '/')
|
|
|
|
continue;
|
|
|
|
*s = 0;
|
|
|
|
return (i + 1);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct refspec {
|
|
|
|
char *src;
|
|
|
|
char *dst;
|
|
|
|
char force;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A:B means fast forward remote B with local A.
|
|
|
|
* +A:B means overwrite remote B with local A.
|
|
|
|
* +A is a shorthand for +A:A.
|
|
|
|
* A is a shorthand for A:A.
|
|
|
|
*/
|
|
|
|
static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct refspec *rs = xcalloc(sizeof(*rs), (nr_refspec + 1));
|
|
|
|
for (i = 0; i < nr_refspec; i++) {
|
|
|
|
char *sp, *dp, *ep;
|
|
|
|
sp = refspec[i];
|
|
|
|
if (*sp == '+') {
|
|
|
|
rs[i].force = 1;
|
|
|
|
sp++;
|
|
|
|
}
|
|
|
|
ep = strchr(sp, ':');
|
|
|
|
if (ep) {
|
|
|
|
dp = ep + 1;
|
|
|
|
*ep = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
dp = sp;
|
|
|
|
rs[i].src = sp;
|
|
|
|
rs[i].dst = dp;
|
|
|
|
}
|
|
|
|
rs[nr_refspec].src = rs[nr_refspec].dst = NULL;
|
|
|
|
return rs;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int count_refspec_match(const char *pattern,
|
|
|
|
struct ref *refs,
|
|
|
|
struct ref **matched_ref)
|
|
|
|
{
|
|
|
|
int match;
|
|
|
|
int patlen = strlen(pattern);
|
|
|
|
|
|
|
|
for (match = 0; refs; refs = refs->next) {
|
|
|
|
char *name = refs->name;
|
|
|
|
int namelen = strlen(name);
|
|
|
|
if (namelen < patlen ||
|
|
|
|
memcmp(name + namelen - patlen, pattern, patlen))
|
|
|
|
continue;
|
|
|
|
if (namelen != patlen && name[namelen - patlen - 1] != '/')
|
|
|
|
continue;
|
|
|
|
match++;
|
|
|
|
*matched_ref = refs;
|
|
|
|
}
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void link_dst_tail(struct ref *ref, struct ref ***tail)
|
|
|
|
{
|
|
|
|
**tail = ref;
|
|
|
|
*tail = &ref->next;
|
|
|
|
**tail = NULL;
|
|
|
|
}
|
|
|
|
|
send-pack: allow generic sha1 expression on the source side.
This extends the source side semantics to match what Linus
suggested.
An example:
$ git-send-pack kernel.org:/pub/scm/git/git.git pu^^:master pu
would allow me to push the current pu into pu, and the
commit two commits before it into master, on my public
repository.
The revised rule for updating remote heads is as follows.
$ git-send-pack [--all] <remote> [<ref>...]
- When no <ref> is specified:
- with '--all', it is the same as specifying the full refs/*
path for all local refs;
- without '--all', it is the same as specifying the full
refs/* path for refs that exist on both ends;
- When one or more <ref>s are specified:
- a single token <ref> (i.e. no colon) must be a pattern that
tail-matches refs/* path for an existing local ref. It is
an error for the pattern to match no local ref, or more
than one local refs. The matching ref is pushed to the
remote end under the same name.
- <src>:<dst> can have different cases. <src> is first tried
as the tail-matching pattern for refs/* path.
- If more than one matches are found, it is an error.
- If one match is found, <dst> must either match no remote
ref and start with "refs/", or match exactly one remote
ref. That remote ref is updated with the sha1 value
obtained from the <src> sha1.
- If no match is found, it is given to get_extended_sha1();
it is an error if get_extended_sha1() does not find an
object name. If it succeeds, <dst> must either match
no remote ref and start with "refs/" or match exactly
one remote ref. That remote ref is updated with the sha1
value.
Signed-off-by: Junio C Hamano <junkio@cox.net>
20 years ago
|
|
|
static struct ref *try_explicit_object_name(const char *name)
|
|
|
|
{
|
|
|
|
unsigned char sha1[20];
|
|
|
|
struct ref *ref;
|
|
|
|
int len;
|
|
|
|
if (get_sha1(name, sha1))
|
|
|
|
return NULL;
|
|
|
|
len = strlen(name) + 1;
|
|
|
|
ref = xcalloc(1, sizeof(*ref) + len);
|
|
|
|
memcpy(ref->name, name, len);
|
|
|
|
hashcpy(ref->new_sha1, sha1);
|
send-pack: allow generic sha1 expression on the source side.
This extends the source side semantics to match what Linus
suggested.
An example:
$ git-send-pack kernel.org:/pub/scm/git/git.git pu^^:master pu
would allow me to push the current pu into pu, and the
commit two commits before it into master, on my public
repository.
The revised rule for updating remote heads is as follows.
$ git-send-pack [--all] <remote> [<ref>...]
- When no <ref> is specified:
- with '--all', it is the same as specifying the full refs/*
path for all local refs;
- without '--all', it is the same as specifying the full
refs/* path for refs that exist on both ends;
- When one or more <ref>s are specified:
- a single token <ref> (i.e. no colon) must be a pattern that
tail-matches refs/* path for an existing local ref. It is
an error for the pattern to match no local ref, or more
than one local refs. The matching ref is pushed to the
remote end under the same name.
- <src>:<dst> can have different cases. <src> is first tried
as the tail-matching pattern for refs/* path.
- If more than one matches are found, it is an error.
- If one match is found, <dst> must either match no remote
ref and start with "refs/", or match exactly one remote
ref. That remote ref is updated with the sha1 value
obtained from the <src> sha1.
- If no match is found, it is given to get_extended_sha1();
it is an error if get_extended_sha1() does not find an
object name. If it succeeds, <dst> must either match
no remote ref and start with "refs/" or match exactly
one remote ref. That remote ref is updated with the sha1
value.
Signed-off-by: Junio C Hamano <junkio@cox.net>
20 years ago
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int match_explicit_refs(struct ref *src, struct ref *dst,
|
|
|
|
struct ref ***dst_tail, struct refspec *rs)
|
|
|
|
{
|
|
|
|
int i, errs;
|
|
|
|
for (i = errs = 0; rs[i].src; i++) {
|
|
|
|
struct ref *matched_src, *matched_dst;
|
|
|
|
|
|
|
|
matched_src = matched_dst = NULL;
|
|
|
|
switch (count_refspec_match(rs[i].src, src, &matched_src)) {
|
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 0:
|
send-pack: allow generic sha1 expression on the source side.
This extends the source side semantics to match what Linus
suggested.
An example:
$ git-send-pack kernel.org:/pub/scm/git/git.git pu^^:master pu
would allow me to push the current pu into pu, and the
commit two commits before it into master, on my public
repository.
The revised rule for updating remote heads is as follows.
$ git-send-pack [--all] <remote> [<ref>...]
- When no <ref> is specified:
- with '--all', it is the same as specifying the full refs/*
path for all local refs;
- without '--all', it is the same as specifying the full
refs/* path for refs that exist on both ends;
- When one or more <ref>s are specified:
- a single token <ref> (i.e. no colon) must be a pattern that
tail-matches refs/* path for an existing local ref. It is
an error for the pattern to match no local ref, or more
than one local refs. The matching ref is pushed to the
remote end under the same name.
- <src>:<dst> can have different cases. <src> is first tried
as the tail-matching pattern for refs/* path.
- If more than one matches are found, it is an error.
- If one match is found, <dst> must either match no remote
ref and start with "refs/", or match exactly one remote
ref. That remote ref is updated with the sha1 value
obtained from the <src> sha1.
- If no match is found, it is given to get_extended_sha1();
it is an error if get_extended_sha1() does not find an
object name. If it succeeds, <dst> must either match
no remote ref and start with "refs/" or match exactly
one remote ref. That remote ref is updated with the sha1
value.
Signed-off-by: Junio C Hamano <junkio@cox.net>
20 years ago
|
|
|
/* The source could be in the get_sha1() format
|
|
|
|
* not a reference name.
|
|
|
|
*/
|
|
|
|
matched_src = try_explicit_object_name(rs[i].src);
|
|
|
|
if (matched_src)
|
|
|
|
break;
|
|
|
|
errs = 1;
|
|
|
|
error("src refspec %s does not match any.",
|
|
|
|
rs[i].src);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
errs = 1;
|
|
|
|
error("src refspec %s matches more than one.",
|
|
|
|
rs[i].src);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) {
|
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
if (!memcmp(rs[i].dst, "refs/", 5)) {
|
|
|
|
int len = strlen(rs[i].dst) + 1;
|
|
|
|
matched_dst = xcalloc(1, sizeof(*dst) + len);
|
|
|
|
memcpy(matched_dst->name, rs[i].dst, len);
|
|
|
|
link_dst_tail(matched_dst, dst_tail);
|
|
|
|
}
|
|
|
|
else if (!strcmp(rs[i].src, rs[i].dst) &&
|
|
|
|
matched_src) {
|
|
|
|
/* pushing "master:master" when
|
|
|
|
* remote does not have master yet.
|
|
|
|
*/
|
|
|
|
int len = strlen(matched_src->name) + 1;
|
|
|
|
matched_dst = xcalloc(1, sizeof(*dst) + len);
|
|
|
|
memcpy(matched_dst->name, matched_src->name,
|
|
|
|
len);
|
|
|
|
link_dst_tail(matched_dst, dst_tail);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
errs = 1;
|
|
|
|
error("dst refspec %s does not match any "
|
|
|
|
"existing ref on the remote and does "
|
|
|
|
"not start with refs/.", rs[i].dst);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
errs = 1;
|
|
|
|
error("dst refspec %s matches more than one.",
|
|
|
|
rs[i].dst);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (errs)
|
|
|
|
continue;
|
|
|
|
if (matched_dst->peer_ref) {
|
|
|
|
errs = 1;
|
|
|
|
error("dst ref %s receives from more than one src.",
|
|
|
|
matched_dst->name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
matched_dst->peer_ref = matched_src;
|
|
|
|
matched_dst->force = rs[i].force;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -errs;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ref *find_ref_by_name(struct ref *list, const char *name)
|
|
|
|
{
|
|
|
|
for ( ; list; list = list->next)
|
|
|
|
if (!strcmp(list->name, name))
|
|
|
|
return list;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
|
|
|
|
int nr_refspec, char **refspec, int all)
|
|
|
|
{
|
|
|
|
struct refspec *rs = parse_ref_spec(nr_refspec, refspec);
|
|
|
|
|
|
|
|
if (nr_refspec)
|
|
|
|
return match_explicit_refs(src, dst, dst_tail, rs);
|
|
|
|
|
|
|
|
/* pick the remainder */
|
|
|
|
for ( ; src; src = src->next) {
|
|
|
|
struct ref *dst_peer;
|
|
|
|
if (src->peer_ref)
|
|
|
|
continue;
|
|
|
|
dst_peer = find_ref_by_name(dst, src->name);
|
|
|
|
if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all))
|
|
|
|
continue;
|
|
|
|
if (!dst_peer) {
|
|
|
|
/* Create a new one and link it */
|
|
|
|
int len = strlen(src->name) + 1;
|
|
|
|
dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
|
|
|
|
memcpy(dst_peer->name, src->name, len);
|
|
|
|
hashcpy(dst_peer->new_sha1, src->new_sha1);
|
|
|
|
link_dst_tail(dst_peer, dst_tail);
|
|
|
|
}
|
|
|
|
dst_peer->peer_ref = src;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum protocol {
|
|
|
|
PROTO_LOCAL = 1,
|
|
|
|
PROTO_SSH,
|
|
|
|
PROTO_GIT,
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum protocol get_protocol(const char *name)
|
|
|
|
{
|
|
|
|
if (!strcmp(name, "ssh"))
|
|
|
|
return PROTO_SSH;
|
|
|
|
if (!strcmp(name, "git"))
|
|
|
|
return PROTO_GIT;
|
|
|
|
if (!strcmp(name, "git+ssh"))
|
|
|
|
return PROTO_SSH;
|
|
|
|
if (!strcmp(name, "ssh+git"))
|
|
|
|
return PROTO_SSH;
|
|
|
|
die("I don't handle protocol '%s'", name);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define STR_(s) # s
|
|
|
|
#define STR(s) STR_(s)
|
|
|
|
|
|
|
|
#ifndef NO_IPV6
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns a connected socket() fd, or else die()s.
|
|
|
|
*/
|
|
|
|
static int git_tcp_connect_sock(char *host)
|
|
|
|
{
|
|
|
|
int sockfd = -1, saved_errno = 0;
|
|
|
|
char *colon, *end;
|
|
|
|
const char *port = STR(DEFAULT_GIT_PORT);
|
|
|
|
struct addrinfo hints, *ai0, *ai;
|
|
|
|
int gai;
|
|
|
|
|
|
|
|
if (host[0] == '[') {
|
|
|
|
end = strchr(host + 1, ']');
|
|
|
|
if (end) {
|
|
|
|
*end = 0;
|
|
|
|
end++;
|
|
|
|
host++;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
colon = strchr(end, ':');
|
|
|
|
|
|
|
|
if (colon) {
|
|
|
|
*colon = 0;
|
|
|
|
port = colon + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
|
|
|
|
gai = getaddrinfo(host, port, &hints, &ai);
|
|
|
|
if (gai)
|
|
|
|
die("Unable to look up %s (%s)", host, gai_strerror(gai));
|
|
|
|
|
|
|
|
for (ai0 = ai; ai; ai = ai->ai_next) {
|
|
|
|
sockfd = socket(ai->ai_family,
|
|
|
|
ai->ai_socktype, ai->ai_protocol);
|
|
|
|
if (sockfd < 0) {
|
|
|
|
saved_errno = errno;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
|
|
|
|
saved_errno = errno;
|
|
|
|
close(sockfd);
|
|
|
|
sockfd = -1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
freeaddrinfo(ai0);
|
|
|
|
|
|
|
|
if (sockfd < 0)
|
|
|
|
die("unable to connect a socket (%s)", strerror(saved_errno));
|
|
|
|
|
|
|
|
return sockfd;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else /* NO_IPV6 */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns a connected socket() fd, or else die()s.
|
|
|
|
*/
|
|
|
|
static int git_tcp_connect_sock(char *host)
|
|
|
|
{
|
|
|
|
int sockfd = -1, saved_errno = 0;
|
|
|
|
char *colon, *end;
|
|
|
|
char *port = STR(DEFAULT_GIT_PORT), *ep;
|
|
|
|
struct hostent *he;
|
|
|
|
struct sockaddr_in sa;
|
|
|
|
char **ap;
|
|
|
|
unsigned int nport;
|
|
|
|
|
|
|
|
if (host[0] == '[') {
|
|
|
|
end = strchr(host + 1, ']');
|
|
|
|
if (end) {
|
|
|
|
*end = 0;
|
|
|
|
end++;
|
|
|
|
host++;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
colon = strchr(end, ':');
|
|
|
|
|
|
|
|
if (colon) {
|
|
|
|
*colon = 0;
|
|
|
|
port = colon + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
he = gethostbyname(host);
|
|
|
|
if (!he)
|
|
|
|
die("Unable to look up %s (%s)", host, hstrerror(h_errno));
|
|
|
|
nport = strtoul(port, &ep, 10);
|
|
|
|
if ( ep == port || *ep ) {
|
|
|
|
/* Not numeric */
|
|
|
|
struct servent *se = getservbyname(port,"tcp");
|
|
|
|
if ( !se )
|
|
|
|
die("Unknown port %s\n", port);
|
|
|
|
nport = se->s_port;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ap = he->h_addr_list; *ap; ap++) {
|
|
|
|
sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
|
|
|
|
if (sockfd < 0) {
|
|
|
|
saved_errno = errno;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&sa, 0, sizeof sa);
|
|
|
|
sa.sin_family = he->h_addrtype;
|
|
|
|
sa.sin_port = htons(nport);
|
|
|
|
memcpy(&sa.sin_addr, *ap, he->h_length);
|
|
|
|
|
|
|
|
if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
|
|
|
|
saved_errno = errno;
|
|
|
|
close(sockfd);
|
|
|
|
sockfd = -1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sockfd < 0)
|
|
|
|
die("unable to connect a socket (%s)", strerror(saved_errno));
|
|
|
|
|
|
|
|
return sockfd;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* NO_IPV6 */
|
|
|
|
|
|
|
|
|
|
|
|
static void git_tcp_connect(int fd[2], char *host)
|
|
|
|
{
|
|
|
|
int sockfd = git_tcp_connect_sock(host);
|
|
|
|
|
|
|
|
fd[0] = sockfd;
|
|
|
|
fd[1] = sockfd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static char *git_proxy_command;
|
|
|
|
static const char *rhost_name;
|
|
|
|
static int rhost_len;
|
|
|
|
|
|
|
|
static int git_proxy_command_options(const char *var, const char *value)
|
|
|
|
{
|
|
|
|
if (!strcmp(var, "core.gitproxy")) {
|
|
|
|
const char *for_pos;
|
|
|
|
int matchlen = -1;
|
|
|
|
int hostlen;
|
|
|
|
|
|
|
|
if (git_proxy_command)
|
|
|
|
return 0;
|
|
|
|
/* [core]
|
|
|
|
* ;# matches www.kernel.org as well
|
|
|
|
* gitproxy = netcatter-1 for kernel.org
|
|
|
|
* gitproxy = netcatter-2 for sample.xz
|
|
|
|
* gitproxy = netcatter-default
|
|
|
|
*/
|
|
|
|
for_pos = strstr(value, " for ");
|
|
|
|
if (!for_pos)
|
|
|
|
/* matches everybody */
|
|
|
|
matchlen = strlen(value);
|
|
|
|
else {
|
|
|
|
hostlen = strlen(for_pos + 5);
|
|
|
|
if (rhost_len < hostlen)
|
|
|
|
matchlen = -1;
|
|
|
|
else if (!strncmp(for_pos + 5,
|
|
|
|
rhost_name + rhost_len - hostlen,
|
|
|
|
hostlen) &&
|
|
|
|
((rhost_len == hostlen) ||
|
|
|
|
rhost_name[rhost_len - hostlen -1] == '.'))
|
|
|
|
matchlen = for_pos - value;
|
|
|
|
else
|
|
|
|
matchlen = -1;
|
|
|
|
}
|
|
|
|
if (0 <= matchlen) {
|
|
|
|
/* core.gitproxy = none for kernel.org */
|
|
|
|
if (matchlen == 4 &&
|
|
|
|
!memcmp(value, "none", 4))
|
|
|
|
matchlen = 0;
|
|
|
|
git_proxy_command = xmalloc(matchlen + 1);
|
|
|
|
memcpy(git_proxy_command, value, matchlen);
|
|
|
|
git_proxy_command[matchlen] = 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return git_default_config(var, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int git_use_proxy(const char *host)
|
|
|
|
{
|
|
|
|
rhost_name = host;
|
|
|
|
rhost_len = strlen(host);
|
|
|
|
git_proxy_command = getenv("GIT_PROXY_COMMAND");
|
|
|
|
git_config(git_proxy_command_options);
|
|
|
|
rhost_name = NULL;
|
|
|
|
return (git_proxy_command && *git_proxy_command);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void git_proxy_connect(int fd[2], char *host)
|
|
|
|
{
|
|
|
|
const char *port = STR(DEFAULT_GIT_PORT);
|
|
|
|
char *colon, *end;
|
|
|
|
int pipefd[2][2];
|
|
|
|
pid_t pid;
|
|
|
|
|
|
|
|
if (host[0] == '[') {
|
|
|
|
end = strchr(host + 1, ']');
|
|
|
|
if (end) {
|
|
|
|
*end = 0;
|
|
|
|
end++;
|
|
|
|
host++;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
colon = strchr(end, ':');
|
|
|
|
|
|
|
|
if (colon) {
|
|
|
|
*colon = 0;
|
|
|
|
port = colon + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
|
|
|
|
die("unable to create pipe pair for communication");
|
|
|
|
pid = fork();
|
|
|
|
if (!pid) {
|
|
|
|
dup2(pipefd[1][0], 0);
|
|
|
|
dup2(pipefd[0][1], 1);
|
|
|
|
close(pipefd[0][0]);
|
|
|
|
close(pipefd[0][1]);
|
|
|
|
close(pipefd[1][0]);
|
|
|
|
close(pipefd[1][1]);
|
|
|
|
execlp(git_proxy_command, git_proxy_command, host, port, NULL);
|
|
|
|
die("exec failed");
|
|
|
|
}
|
|
|
|
if (pid < 0)
|
|
|
|
die("fork failed");
|
|
|
|
fd[0] = pipefd[0][0];
|
|
|
|
fd[1] = pipefd[1][1];
|
|
|
|
close(pipefd[0][1]);
|
|
|
|
close(pipefd[1][0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_CMD_LEN 1024
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This returns 0 if the transport protocol does not need fork(2),
|
|
|
|
* or a process id if it does. Once done, finish the connection
|
|
|
|
* with finish_connect() with the value returned from this function
|
|
|
|
* (it is safe to call finish_connect() with 0 to support the former
|
|
|
|
* case).
|
|
|
|
*
|
|
|
|
* Does not return a negative value on error; it just dies.
|
|
|
|
*/
|
|
|
|
pid_t git_connect(int fd[2], char *url, const char *prog)
|
|
|
|
{
|
|
|
|
char *host, *path = url;
|
|
|
|
char *end;
|
|
|
|
int c;
|
|
|
|
int pipefd[2][2];
|
|
|
|
pid_t pid;
|
|
|
|
enum protocol protocol = PROTO_LOCAL;
|
|
|
|
int free_path = 0;
|
|
|
|
|
|
|
|
/* Without this we cannot rely on waitpid() to tell
|
|
|
|
* what happened to our children.
|
|
|
|
*/
|
|
|
|
signal(SIGCHLD, SIG_DFL);
|
|
|
|
|
|
|
|
host = strstr(url, "://");
|
|
|
|
if(host) {
|
|
|
|
*host = '\0';
|
|
|
|
protocol = get_protocol(url);
|
|
|
|
host += 3;
|
|
|
|
c = '/';
|
|
|
|
} else {
|
|
|
|
host = url;
|
|
|
|
c = ':';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (host[0] == '[') {
|
|
|
|
end = strchr(host + 1, ']');
|
|
|
|
if (end) {
|
|
|
|
*end = 0;
|
|
|
|
end++;
|
|
|
|
host++;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
} else
|
|
|
|
end = host;
|
|
|
|
|
|
|
|
path = strchr(end, c);
|
|
|
|
if (c == ':') {
|
|
|
|
if (path) {
|
|
|
|
protocol = PROTO_SSH;
|
|
|
|
*path++ = '\0';
|
|
|
|
} else
|
|
|
|
path = host;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!path || !*path)
|
|
|
|
die("No path specified. See 'man git-pull' for valid url syntax");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* null-terminate hostname and point path to ~ for URL's like this:
|
|
|
|
* ssh://host.xz/~user/repo
|
|
|
|
*/
|
|
|
|
if (protocol != PROTO_LOCAL && host != url) {
|
|
|
|
char *ptr = path;
|
|
|
|
if (path[1] == '~')
|
|
|
|
path++;
|
|
|
|
else {
|
|
|
|
path = xstrdup(ptr);
|
|
|
|
free_path = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (protocol == PROTO_GIT) {
|
|
|
|
/* These underlying connection commands die() if they
|
|
|
|
* cannot connect.
|
|
|
|
*/
|
|
|
|
char *target_host = xstrdup(host);
|
|
|
|
if (git_use_proxy(host))
|
|
|
|
git_proxy_connect(fd, host);
|
|
|
|
else
|
|
|
|
git_tcp_connect(fd, host);
|
|
|
|
/*
|
|
|
|
* Separate original protocol components prog and path
|
|
|
|
* from extended components with a NUL byte.
|
|
|
|
*/
|
|
|
|
packet_write(fd[1],
|
|
|
|
"%s %s%chost=%s%c",
|
|
|
|
prog, path, 0,
|
|
|
|
target_host, 0);
|
|
|
|
free(target_host);
|
|
|
|
if (free_path)
|
|
|
|
free(path);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
|
|
|
|
die("unable to create pipe pair for communication");
|
|
|
|
pid = fork();
|
|
|
|
if (pid < 0)
|
|
|
|
die("unable to fork");
|
|
|
|
if (!pid) {
|
|
|
|
char command[MAX_CMD_LEN];
|
|
|
|
char *posn = command;
|
|
|
|
int size = MAX_CMD_LEN;
|
|
|
|
int of = 0;
|
|
|
|
|
|
|
|
of |= add_to_string(&posn, &size, prog, 0);
|
|
|
|
of |= add_to_string(&posn, &size, " ", 0);
|
|
|
|
of |= add_to_string(&posn, &size, path, 1);
|
|
|
|
|
|
|
|
if (of)
|
|
|
|
die("command line too long");
|
|
|
|
|
|
|
|
dup2(pipefd[1][0], 0);
|
|
|
|
dup2(pipefd[0][1], 1);
|
|
|
|
close(pipefd[0][0]);
|
|
|
|
close(pipefd[0][1]);
|
|
|
|
close(pipefd[1][0]);
|
|
|
|
close(pipefd[1][1]);
|
|
|
|
if (protocol == PROTO_SSH) {
|
|
|
|
const char *ssh, *ssh_basename;
|
|
|
|
ssh = getenv("GIT_SSH");
|
|
|
|
if (!ssh) ssh = "ssh";
|
|
|
|
ssh_basename = strrchr(ssh, '/');
|
|
|
|
if (!ssh_basename)
|
|
|
|
ssh_basename = ssh;
|
|
|
|
else
|
|
|
|
ssh_basename++;
|
|
|
|
execlp(ssh, ssh_basename, host, command, NULL);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
unsetenv(ALTERNATE_DB_ENVIRONMENT);
|
|
|
|
unsetenv(DB_ENVIRONMENT);
|
|
|
|
unsetenv(GIT_DIR_ENVIRONMENT);
|
|
|
|
unsetenv(GRAFT_ENVIRONMENT);
|
|
|
|
unsetenv(INDEX_ENVIRONMENT);
|
|
|
|
execlp("sh", "sh", "-c", command, NULL);
|
|
|
|
}
|
|
|
|
die("exec failed");
|
|
|
|
}
|
|
|
|
fd[0] = pipefd[0][0];
|
|
|
|
fd[1] = pipefd[1][1];
|
|
|
|
close(pipefd[0][1]);
|
|
|
|
close(pipefd[1][0]);
|
|
|
|
if (free_path)
|
|
|
|
free(path);
|
|
|
|
return pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
int finish_connect(pid_t pid)
|
|
|
|
{
|
|
|
|
if (pid == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
while (waitpid(pid, NULL, 0) < 0) {
|
|
|
|
if (errno != EINTR)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|