diff --git a/include/git2.h b/include/git2.h index ff1c1185a..b5c693a82 100644 --- a/include/git2.h +++ b/include/git2.h @@ -53,5 +53,13 @@ #include "git2/index.h" #include "git2/config.h" +#include "git2/remote.h" + +#include "git2/remote.h" +#include "git2/refspec.h" + +#include "git2/net.h" +#include "git2/transport.h" +#include "git2/pkt.h" #endif diff --git a/include/git2/branch.h b/include/git2/branch.h new file mode 100644 index 000000000..456b7d1ac --- /dev/null +++ b/include/git2/branch.h @@ -0,0 +1,9 @@ +#ifndef INCLUDE_branch_h__ +#define INCLUDE_branch_h__ + +struct git_branch { + char *remote; /* TODO: Make this a git_remote */ + char *merge; +}; + +#endif diff --git a/include/git2/errors.h b/include/git2/errors.h index 253cb6ae2..710ac244b 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -125,6 +125,12 @@ typedef enum { /** Skip and passthrough the given ODB backend */ GIT_EPASSTHROUGH = -30, + + /** The path pattern and string did not match */ + GIT_ENOMATCH = -31, + + /** The buffer is too short to satisfy the request */ + GIT_ESHORTBUFFER = -32, } git_error; /** diff --git a/include/git2/net.h b/include/git2/net.h new file mode 100644 index 000000000..4bef90509 --- /dev/null +++ b/include/git2/net.h @@ -0,0 +1,33 @@ +#ifndef INCLUDE_net_h__ +#define INCLUDE_net_h__ + +#include "common.h" +#include "oid.h" +#include "types.h" + +#define GIT_DEFAULT_PORT "9418" + +/* + * We need this because we need to know whether we should call + * git-upload-pack or git-receive-pack on the remote end when get_refs + * gets called. + */ + +#define GIT_DIR_FETCH 0 +#define GIT_DIR_PUSH 1 + +/* + * This is what we give out on ->ls() + */ + +struct git_remote_head { + git_oid oid; + char *name; +}; + +struct git_headarray { + unsigned int len; + struct git_remote_head **heads; +}; + +#endif diff --git a/include/git2/pkt.h b/include/git2/pkt.h new file mode 100644 index 000000000..0b17b3eed --- /dev/null +++ b/include/git2/pkt.h @@ -0,0 +1,56 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "git2/net.h" + +enum git_pkt_type { + GIT_PKT_CMD, + GIT_PKT_FLUSH, + GIT_PKT_REF, + GIT_PKT_HAVE, +}; + +/* This would be a flush pkt */ +struct git_pkt { + enum git_pkt_type type; +}; + +struct git_pkt_cmd { + enum git_pkt_type type; + char *cmd; + char *path; + char *host; +}; + +/* This is a pkt-line with some info in it */ +struct git_pkt_ref { + enum git_pkt_type type; + git_remote_head head; + char *capabilities; +}; + +int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); +int git_pkt_send_flush(int s); +void git_pkt_free(git_pkt *pkt); diff --git a/include/git2/refspec.h b/include/git2/refspec.h new file mode 100644 index 000000000..0cbe42ff7 --- /dev/null +++ b/include/git2/refspec.h @@ -0,0 +1,42 @@ +#ifndef INCLUDE_git_refspec_h__ +#define INCLUDE_git_refspec_h__ + +#include "git2/types.h" + +/** + * Get the source specifier + * + * @param refspec the refspec + * @return the refspec's source specifier + */ +const char *git_refspec_src(const git_refspec *refspec); + +/** + * Get the destination specifier + * + * @param refspec the refspec + * @return the refspec's destination specifier + */ +const char *git_refspec_dst(const git_refspec *refspec); + +/** + * Match a refspec's source descriptor with a reference name + * + * @param refspec the refspec + * @param refname the name of the reference to check + * @return GIT_SUCCESS on successful match; GIT_ENOMACH on match + * failure or an error code on other failure + */ +int git_refspec_src_match(const git_refspec *refspec, const char *refname); + +/** + * Transform a reference to its target following the refspec's rules + * + * @param out where to store the target name + * @param in the source reference + * @param spec the refspec + * @param len the length of the out buffer + * @preturn GIT_SUCCESS, GIT_ESHORTBUFFER or another error + */ +int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name); +#endif diff --git a/include/git2/remote.h b/include/git2/remote.h new file mode 100644 index 000000000..03e459569 --- /dev/null +++ b/include/git2/remote.h @@ -0,0 +1,87 @@ +#ifndef INCLUDE_git_remote_h__ +#define INCLUDE_git_remote_h__ + +#include "git2/common.h" +#include "git2/repository.h" +#include "git2/refspec.h" + +/* + * TODO: This functions still need to be implemented: + * - _listcb/_foreach + * - _add + * - _rename + * - _del (needs support from config) + */ + +/** + * Get the information for a particular remote + * + * @param out pointer to the new remote object + * @param cfg the repository's configuration + * @param name the remote's name + * @return 0 on success; error value otherwise + */ +GIT_EXTERN(int) git_remote_get(struct git_remote **out, struct git_config *cfg, const char *name); + +/** + * Get the remote's name + * + * @param remote the remote + * @return a pointer to the name + */ +GIT_EXTERN(const char *) git_remote_name(struct git_remote *remote); + +/** + * Get the remote's url + * + * @param remote the remote + * @return a pointer to the url + */ +GIT_EXTERN(const char *) git_remote_url(struct git_remote *remote); + +/** + * Get the fetch refspec + * + * @param remote the remote + * @return a pointer to the fetch refspec or NULL if it doesn't exist + */ +GIT_EXTERN(const git_refspec *) git_remote_fetchspec(struct git_remote *remote); + +/** + * Get the push refspec + * + * @param remote the remote + * @return a pointer to the push refspec or NULL if it doesn't exist + */ + +GIT_EXTERN(const git_refspec *) git_remote_fetchspec(struct git_remote *remote); + +/** + * Open a connection to a remote + * + * The transport is selected based on the URL + * + * @param remote the remote to connect to + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_remote_connect(struct git_remote *remote, int direction); + +/** + * Get a list of refs at the remote + * + * The remote (or more exactly its transport) must be connected. + * + * @param refs where to store the refs + * @param remote the remote + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headarray *refs); + +/** + * Free the memory associated with a remote + * + * @param remote the remote to free + */ +GIT_EXTERN(void) git_remote_free(struct git_remote *remote); + +#endif diff --git a/include/git2/transport.h b/include/git2/transport.h new file mode 100644 index 000000000..982b081f8 --- /dev/null +++ b/include/git2/transport.h @@ -0,0 +1,58 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDE_git_transport_h__ +#define INCLUDE_git_transport_h__ + +#include "common.h" +#include "types.h" +#include "net.h" + +/** + * @file git2/transport.h + * @brief Git protocol transport abstraction + * @defgroup git_transport Git protocol transport abstraction + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Get the appropriate transport for an URL. + * @param tranport the transport for the url + * @param url the url of the repo + */ +GIT_EXTERN(int) git_transport_new(git_transport **transport, const char *url); + +GIT_EXTERN(int) git_transport_connect(git_transport *transport, int direction); + +GIT_EXTERN(int) git_transport_ls(git_transport *transport, git_headarray *array); +GIT_EXTERN(int) git_transport_close(git_transport *transport); +GIT_EXTERN(void) git_transport_free(git_transport *transport); + +GIT_EXTERN(int) git_transport_add(git_transport *transport, const char *prefix); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/types.h b/include/git2/types.h index 85cf4ef78..bc1b8a6c7 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -167,6 +167,27 @@ typedef enum { GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC|GIT_REF_PACKED, } git_rtype; + +typedef struct git_refspec git_refspec; +typedef struct git_remote git_remote; + +/** A transport to use */ +typedef struct git_transport git_transport; + +/** Whether to push or pull */ +typedef enum git_net_direction git_net_direction; + +typedef int (*git_transport_cb)(git_transport **transport); + +typedef struct git_remote_head git_remote_head; +typedef struct git_headarray git_headarray; + +/* Several types of packets */ +typedef enum git_pkt_type git_pkt_type; +typedef struct git_pkt git_pkt; +typedef struct git_pkt_cmd git_pkt_cmd; +typedef struct git_pkt_ref git_pkt_ref; + /** @} */ GIT_END_DECL diff --git a/src/fnmatch.c b/src/fnmatch.c new file mode 100644 index 000000000..66e2c3395 --- /dev/null +++ b/src/fnmatch.c @@ -0,0 +1,489 @@ +/* Copyright (C) 1991, 92, 93, 96, 97, 98, 99 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#if HAVE_CONFIG_H +# include +#endif + +/* Enable GNU extensions in fnmatch.h. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif + +#include +#include +#include + +#if defined _MSC_VER +# define HAVE_STRING_H 1 +#endif + +#if HAVE_STRING_H || defined _LIBC +# include +#else +# include +#endif + +#if defined STDC_HEADERS || defined _LIBC +# include +#endif + +/* For platforms which support the ISO C amendment 1 functionality we + support user defined character classes. */ +#if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) +/* Solaris 2.5 has a bug: must be included before . */ +# include +# include +#endif + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined _LIBC || !defined __GNU_LIBRARY__ + + +# if defined STDC_HEADERS || !defined isascii +# define ISASCII(c) 1 +# else +# define ISASCII(c) isascii(c) +# endif + +# ifdef isblank +# define ISBLANK(c) (ISASCII (c) && isblank (c)) +# else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +# endif +# ifdef isgraph +# define ISGRAPH(c) (ISASCII (c) && isgraph (c)) +# else +# define ISGRAPH(c) (ISASCII (c) && isprint (c) && !isspace (c)) +# endif + +# define ISPRINT(c) (ISASCII (c) && isprint (c)) +# define ISDIGIT(c) (ISASCII (c) && isdigit (c)) +# define ISALNUM(c) (ISASCII (c) && isalnum (c)) +# define ISALPHA(c) (ISASCII (c) && isalpha (c)) +# define ISCNTRL(c) (ISASCII (c) && iscntrl (c)) +# define ISLOWER(c) (ISASCII (c) && islower (c)) +# define ISPUNCT(c) (ISASCII (c) && ispunct (c)) +# define ISSPACE(c) (ISASCII (c) && isspace (c)) +# define ISUPPER(c) (ISASCII (c) && isupper (c)) +# define ISXDIGIT(c) (ISASCII (c) && isxdigit (c)) + +# define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) + +# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) +/* The GNU C library provides support for user-defined character classes + and the functions from ISO C amendment 1. */ +# ifdef CHARCLASS_NAME_MAX +# define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX +# else +/* This shouldn't happen but some implementation might still have this + problem. Use a reasonable default value. */ +# define CHAR_CLASS_MAX_LENGTH 256 +# endif + +# ifdef _LIBC +# define IS_CHAR_CLASS(string) __wctype (string) +# else +# define IS_CHAR_CLASS(string) wctype (string) +# endif +# else +# define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ + +# define IS_CHAR_CLASS(string) \ + (STREQ (string, "alpha") || STREQ (string, "upper") \ + || STREQ (string, "lower") || STREQ (string, "digit") \ + || STREQ (string, "alnum") || STREQ (string, "xdigit") \ + || STREQ (string, "space") || STREQ (string, "print") \ + || STREQ (string, "punct") || STREQ (string, "graph") \ + || STREQ (string, "cntrl") || STREQ (string, "blank")) +# endif + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +# if !defined _LIBC && !defined getenv +extern char *getenv (); +# endif + +# ifndef errno +extern int errno; +# endif + +# ifndef NULL +# define NULL 0 +# endif + +/* This function doesn't exist on most systems. */ + +# if !defined HAVE___STRCHRNUL && !defined _LIBC +static char * +__strchrnul (const char *s, int c) +{ + char *result = strchr (s, c); + if (result == NULL) + result = strchr (s, '\0'); + return result; +} +# endif + +# ifndef internal_function +/* Inside GNU libc we mark some function in a special way. In other + environments simply ignore the marking. */ +# define internal_function +# endif + +/* Match STRING against the filename pattern PATTERN, returning zero if + it matches, nonzero if not. */ +static int internal_fnmatch __P ((const char *pattern, const char *string, + int no_leading_period, int flags)) + internal_function; +static int +internal_function +internal_fnmatch(const char *pattern, const char *string, + int no_leading_period ,int flags) +{ + register const char *p = pattern, *n = string; + register unsigned char c; + +/* Note that this evaluates C many times. */ +# ifdef _LIBC +# define FOLD(c) ((flags & FNM_CASEFOLD) ? tolower (c) : (c)) +# else +# define FOLD(c) ((flags & FNM_CASEFOLD) && ISUPPER (c) ? tolower (c) : (c)) +# endif + + while ((c = *p++) != '\0') + { + c = (unsigned char) FOLD (c); + + switch (c) + { + case '?': + if (*n == '\0') + return FNM_NOMATCH; + else if (*n == '/' && (flags & FNM_FILE_NAME)) + return FNM_NOMATCH; + else if (*n == '.' && no_leading_period + && (n == string + || (n[-1] == '/' && (flags & FNM_FILE_NAME)))) + return FNM_NOMATCH; + break; + + case '\\': + if (!(flags & FNM_NOESCAPE)) + { + c = *p++; + if (c == '\0') + /* Trailing \ loses. */ + return FNM_NOMATCH; + c = (unsigned char) FOLD (c); + } + if (FOLD ((unsigned char) *n) != c) + return FNM_NOMATCH; + break; + + case '*': + if (*n == '.' && no_leading_period + && (n == string + || (n[-1] == '/' && (flags & FNM_FILE_NAME)))) + return FNM_NOMATCH; + + for (c = *p++; c == '?' || c == '*'; c = *p++) + { + if (*n == '/' && (flags & FNM_FILE_NAME)) + /* A slash does not match a wildcard under FNM_FILE_NAME. */ + return FNM_NOMATCH; + else if (c == '?') + { + /* A ? needs to match one character. */ + if (*n == '\0') + /* There isn't another character; no match. */ + return FNM_NOMATCH; + else + /* One character of the string is consumed in matching + this ? wildcard, so *??? won't match if there are + less than three characters. */ + ++n; + } + } + + if (c == '\0') + /* The wildcard(s) is/are the last element of the pattern. + If the name is a file name and contains another slash + this does mean it cannot match. */ + return ((flags & FNM_FILE_NAME) && strchr (n, '/') != NULL + ? FNM_NOMATCH : 0); + else + { + const char *endp; + + endp = __strchrnul (n, (flags & FNM_FILE_NAME) ? '/' : '\0'); + + if (c == '[') + { + int flags2 = ((flags & FNM_FILE_NAME) + ? flags : (flags & ~FNM_PERIOD)); + + for (--p; n < endp; ++n) + if (internal_fnmatch (p, n, + (no_leading_period + && (n == string + || (n[-1] == '/' + && (flags + & FNM_FILE_NAME)))), + flags2) + == 0) + return 0; + } + else if (c == '/' && (flags & FNM_FILE_NAME)) + { + while (*n != '\0' && *n != '/') + ++n; + if (*n == '/' + && (internal_fnmatch (p, n + 1, flags & FNM_PERIOD, + flags) == 0)) + return 0; + } + else + { + int flags2 = ((flags & FNM_FILE_NAME) + ? flags : (flags & ~FNM_PERIOD)); + + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *p; + c = (unsigned char) FOLD (c); + for (--p; n < endp; ++n) + if (FOLD ((unsigned char) *n) == c + && (internal_fnmatch (p, n, + (no_leading_period + && (n == string + || (n[-1] == '/' + && (flags + & FNM_FILE_NAME)))), + flags2) == 0)) + return 0; + } + } + + /* If we come here no match is possible with the wildcard. */ + return FNM_NOMATCH; + + case '[': + { + /* Nonzero if the sense of the character class is inverted. */ + static int posixly_correct; + register int not; + char cold; + + if (posixly_correct == 0) + posixly_correct = getenv ("POSIXLY_CORRECT") != NULL ? 1 : -1; + + if (*n == '\0') + return FNM_NOMATCH; + + if (*n == '.' && no_leading_period && (n == string + || (n[-1] == '/' + && (flags + & FNM_FILE_NAME)))) + return FNM_NOMATCH; + + if (*n == '/' && (flags & FNM_FILE_NAME)) + /* `/' cannot be matched. */ + return FNM_NOMATCH; + + not = (*p == '!' || (posixly_correct < 0 && *p == '^')); + if (not) + ++p; + + c = *p++; + for (;;) + { + unsigned char fn; + fn = (unsigned char) FOLD ((unsigned char) *n); + + if (!(flags & FNM_NOESCAPE) && c == '\\') + { + if (*p == '\0') + return FNM_NOMATCH; + c = (unsigned char) FOLD ((unsigned char) *p); + ++p; + + if (c == fn) + goto matched; + } + else if (c == '[' && *p == ':') + { + /* Leave room for the null. */ + char str[CHAR_CLASS_MAX_LENGTH + 1]; + size_t c1 = 0; +# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) + wctype_t wt; +# endif + const char *startp = p; + + for (;;) + { + if (c1 == CHAR_CLASS_MAX_LENGTH) + /* The name is too long and therefore the pattern + is ill-formed. */ + return FNM_NOMATCH; + + c = *++p; + if (c == ':' && p[1] == ']') + { + p += 2; + break; + } + if (c < 'a' || c >= 'z') + { + /* This cannot possibly be a character class name. + Match it as a normal range. */ + p = startp; + c = '['; + goto normal_bracket; + } + str[c1++] = c; + } + str[c1] = '\0'; + +# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) + wt = IS_CHAR_CLASS (str); + if (wt == 0) + /* Invalid character class name. */ + return FNM_NOMATCH; + + if (__iswctype (__btowc ((unsigned char) *n), wt)) + goto matched; +# else + if ((STREQ (str, "alnum") && ISALNUM ((unsigned char) *n)) + || (STREQ (str, "alpha") && ISALPHA ((unsigned char) *n)) + || (STREQ (str, "blank") && ISBLANK ((unsigned char) *n)) + || (STREQ (str, "cntrl") && ISCNTRL ((unsigned char) *n)) + || (STREQ (str, "digit") && ISDIGIT ((unsigned char) *n)) + || (STREQ (str, "graph") && ISGRAPH ((unsigned char) *n)) + || (STREQ (str, "lower") && ISLOWER ((unsigned char) *n)) + || (STREQ (str, "print") && ISPRINT ((unsigned char) *n)) + || (STREQ (str, "punct") && ISPUNCT ((unsigned char) *n)) + || (STREQ (str, "space") && ISSPACE ((unsigned char) *n)) + || (STREQ (str, "upper") && ISUPPER ((unsigned char) *n)) + || (STREQ (str, "xdigit") && ISXDIGIT ((unsigned char) *n))) + goto matched; +# endif + } + else if (c == '\0') + /* [ (unterminated) loses. */ + return FNM_NOMATCH; + else + { + normal_bracket: + if (FOLD (c) == fn) + goto matched; + + cold = c; + c = *p++; + + if (c == '-' && *p != ']') + { + /* It is a range. */ + unsigned char cend = *p++; + if (!(flags & FNM_NOESCAPE) && cend == '\\') + cend = *p++; + if (cend == '\0') + return FNM_NOMATCH; + + if (cold <= fn && fn <= FOLD (cend)) + goto matched; + + c = *p++; + } + } + + if (c == ']') + break; + } + + if (!not) + return FNM_NOMATCH; + break; + + matched: + /* Skip the rest of the [...] that already matched. */ + while (c != ']') + { + if (c == '\0') + /* [... (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + if (!(flags & FNM_NOESCAPE) && c == '\\') + { + if (*p == '\0') + return FNM_NOMATCH; + /* XXX 1003.2d11 is unclear if this is right. */ + ++p; + } + else if (c == '[' && *p == ':') + { + do + if (*++p == '\0') + return FNM_NOMATCH; + while (*p != ':' || p[1] == ']'); + p += 2; + c = *p; + } + } + if (not) + return FNM_NOMATCH; + } + break; + + default: + if (c != FOLD ((unsigned char) *n)) + return FNM_NOMATCH; + } + + ++n; + } + + if (*n == '\0') + return 0; + + if ((flags & FNM_LEADING_DIR) && *n == '/') + /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */ + return 0; + + return FNM_NOMATCH; + +# undef FOLD +} + + +int +fnmatch(const char *pattern, const char *string, int flags) +{ + return internal_fnmatch (pattern, string, flags & FNM_PERIOD, flags); +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ diff --git a/src/fnmatch.h b/src/fnmatch.h new file mode 100644 index 000000000..cc3ec3794 --- /dev/null +++ b/src/fnmatch.h @@ -0,0 +1,84 @@ +/* Copyright (C) 1991, 92, 93, 96, 97, 98, 99 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef _FNMATCH_H +#define _FNMATCH_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32 +# if !defined __GLIBC__ || !defined __P +# undef __P +# define __P(protos) protos +# endif +#else /* Not C++ or ANSI C. */ +# undef __P +# define __P(protos) () +/* We can get away without defining `const' here only because in this file + it is used only inside the prototype for `fnmatch', which is elided in + non-ANSI C where `const' is problematical. */ +#endif /* C++ or ANSI C. */ + +#ifndef const +# if (defined __STDC__ && __STDC__) || defined __cplusplus +# define __const const +# else +# define __const +# endif +#endif + +/* We #undef these before defining them because some losing systems + (HP-UX A.08.07 for example) define these in . */ +#undef FNM_PATHNAME +#undef FNM_NOESCAPE +#undef FNM_PERIOD + +/* Bits set in the FLAGS argument to `fnmatch'. */ +#define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ +#define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ +#define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ + +#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _GNU_SOURCE +# define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */ +# define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */ +# define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */ +#endif + +/* Value returned by `fnmatch' if STRING does not match PATTERN. */ +#define FNM_NOMATCH 1 + +/* This value is returned if the implementation does not support + `fnmatch'. Since this is not the case here it will never be + returned but the conformance test suites still require the symbol + to be defined. */ +#ifdef _XOPEN_SOURCE +# define FNM_NOSYS (-1) +#endif + +/* Match NAME against the filename pattern PATTERN, + returning zero if it matches, FNM_NOMATCH if not. */ +extern int fnmatch __P ((__const char *__pattern, __const char *__name, + int __flags)); + +#ifdef __cplusplus +} +#endif + +#endif /* fnmatch.h */ diff --git a/src/netops.c b/src/netops.c new file mode 100644 index 000000000..613226d46 --- /dev/null +++ b/src/netops.c @@ -0,0 +1,144 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _MSC_VER +# include +# include +# include +#else +# include +# include +# pragma comment(lib, "Ws2_32.lib") +#endif + +#include "git2/errors.h" + +#include "common.h" +#include "netops.h" + +void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, int fd) +{ + memset(buf, 0x0, sizeof(gitno_buffer)); + memset(data, 0x0, len); + buf->data = data; + buf->len = len - 1; + buf->offset = 0; + buf->fd = fd; +} + +int gitno_recv(gitno_buffer *buf) +{ + int ret; + + ret = recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + if (ret < 0) + return git__throw(GIT_EOSERR, "Failed to receive data"); + if (ret == 0) /* Orderly shutdown, so exit */ + return GIT_SUCCESS; + + buf->offset += ret; + + return ret; +} + +/* Consume up to ptr and move the rest of the buffer to the beginning */ +void gitno_consume(gitno_buffer *buf, const char *ptr) +{ + int consumed; + + assert(ptr - buf->data <= (int) buf->len); + + consumed = ptr - buf->data; + + memmove(buf->data, ptr, buf->offset - consumed); + memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); + buf->offset -= consumed; +} + +/* Consume const bytes and move the rest of the buffer to the beginning */ +void gitno_consume_n(gitno_buffer *buf, unsigned int cons) +{ + memmove(buf->data, buf->data + cons, buf->len - buf->offset); + memset(buf->data + cons, 0x0, buf->len - buf->offset); + buf->offset -= cons; +} + +int gitno_connect(const char *host, const char *port) +{ + struct addrinfo *info, *p; + struct addrinfo hints; + int ret, error = GIT_SUCCESS; + int s; + + memset(&hints, 0x0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + ret = getaddrinfo(host, port, &hints, &info); + if (ret != 0) { + error = GIT_EOSERR; + goto cleanup; + } + + for (p = info; p != NULL; p = p->ai_next) { + s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (s < 0) { + error = GIT_EOSERR; + goto cleanup; + } + + ret = connect(s, p->ai_addr, p->ai_addrlen); + /* If we can't connect, try the next one */ + if (ret < 0) { + continue; + } + + /* Return the socket */ + error = s; + goto cleanup; + } + + /* Oops, we couldn't connect to any address */ + error = GIT_EOSERR; + +cleanup: + freeaddrinfo(info); + return error; +} + +int gitno_send(int s, const char *msg, int len, int flags) +{ + int ret, off = 0; + + while (off < len) { + ret = send(s, msg + off, len - off, flags); + if (ret < 0) + return GIT_EOSERR; + + off += ret; + } + + return off; +} diff --git a/src/netops.h b/src/netops.h new file mode 100644 index 000000000..c828ed9f3 --- /dev/null +++ b/src/netops.h @@ -0,0 +1,22 @@ +/* + * netops.h - convencience functions for networking + */ +#ifndef INCLUDE_netops_h__ +#define INCLUDE_netops_h__ + +typedef struct gitno_buffer { + char *data; + unsigned int len; + unsigned int offset; + int fd; +} gitno_buffer; + +void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, int fd); +int gitno_recv(gitno_buffer *buf); +void gitno_consume(gitno_buffer *buf, const char *ptr); +void gitno_consume_n(gitno_buffer *buf, unsigned int cons); + +int gitno_connect(const char *host, const char *port); +int gitno_send(int s, const char *msg, int len, int flags); + +#endif diff --git a/src/pkt.c b/src/pkt.c new file mode 100644 index 000000000..f9ba8d0bc --- /dev/null +++ b/src/pkt.c @@ -0,0 +1,210 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "git2/pkt.h" +#include "git2/types.h" +#include "git2/errors.h" + +#include "common.h" +#include "util.h" +#include "netops.h" + +#include + +#define PKT_LEN_SIZE 4 + +static int flush_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + if (pkt == NULL) + return GIT_ENOMEM; + + pkt->type = GIT_PKT_FLUSH; + *out = pkt; + + return GIT_SUCCESS; +} + +/* + * Parse an other-ref line. + */ +int ref_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ref *pkt; + int error, has_caps = 0; + + pkt = git__malloc(sizeof(git_pkt_ref)); + if (pkt == NULL) + return GIT_ENOMEM; + + memset(pkt, 0x0, sizeof(git_pkt_ref)); + pkt->type = GIT_PKT_REF; + error = git_oid_fromstr(&pkt->head.oid, line); + if (error < GIT_SUCCESS) { + error = git__throw(error, "Failed to parse reference ID"); + goto out; + } + + /* Check for a bit of consistency */ + if (line[GIT_OID_HEXSZ] != ' ') { + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ref. No SP"); + goto out; + } + + /* Jump from the name */ + line += GIT_OID_HEXSZ + 1; + len -= (GIT_OID_HEXSZ + 1); + + if (strlen(line) < len) + has_caps = 1; + + if (line[len - 1] == '\n') + --len; + + pkt->head.name = git__malloc(len + 1); + if (pkt->head.name == NULL) { + error = GIT_ENOMEM; + goto out; + } + memcpy(pkt->head.name, line, len); + pkt->head.name[len] = '\0'; + + if (has_caps) { + pkt->capabilities = strchr(pkt->head.name, '\0') + 1; + } + +out: + if (error < GIT_SUCCESS) + free(pkt); + else + *out = (git_pkt *)pkt; + + return error; +} + +static ssize_t parse_len(const char *line) +{ + char num[PKT_LEN_SIZE + 1]; + int i, error; + long len; + const char *num_end; + + memcpy(num, line, PKT_LEN_SIZE); + num[PKT_LEN_SIZE] = '\0'; + + for (i = 0; i < PKT_LEN_SIZE; ++i) { + if (!isxdigit(num[i])) + return GIT_ENOTNUM; + } + + error = git__strtol32(&len, num, &num_end, 16); + if (error < GIT_SUCCESS) { + return error; + } + + return (unsigned int) len; +} + +/* + * As per the documentation, the syntax is: + * + * pkt-line = data-pkt / flush-pkt + * data-pkt = pkt-len pkt-payload + * pkt-len = 4*(HEXDIG) + * pkt-payload = (pkt-len -4)*(OCTET) + * flush-pkt = "0000" + * + * Which means that the first four bytes are the length of the line, + * in ASCII hexadecimal (including itself) + */ + +int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t bufflen) +{ + int error = GIT_SUCCESS; + size_t len; + + /* Not even enough for the length */ + if (bufflen > 0 && bufflen < PKT_LEN_SIZE) + return GIT_ESHORTBUFFER; + + error = parse_len(line); + if (error < GIT_SUCCESS) { + return git__throw(error, "Failed to parse pkt length"); + } + + len = error; + + /* + * If we were given a buffer length, then make sure there is + * enough in the buffer to satisfy this line + */ + if (bufflen > 0 && bufflen < len) + return GIT_ESHORTBUFFER; + + line += PKT_LEN_SIZE; + /* + * TODO: How do we deal with empty lines? Try again? with the next + * line? + */ + if (len == PKT_LEN_SIZE) { + *out = line; + return GIT_SUCCESS; + } + + if (len == 0) { /* Flush pkt */ + *out = line; + return flush_pkt(head); + } + + len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ + + /* + * For now, we're just going to assume we're parsing references + */ + + error = ref_pkt(head, line, len); + *out = line + len; + + return error; +} + +void git_pkt_free(git_pkt *pkt) +{ + if(pkt->type == GIT_PKT_REF) { + git_pkt_ref *p = (git_pkt_ref *) pkt; + free(p->head.name); + } + + free(pkt); +} + +int git_pkt_send_flush(int s) +{ + char flush[] = "0000"; + + return gitno_send(s, flush, STRLEN(flush), 0); +} diff --git a/src/refspec.c b/src/refspec.c new file mode 100644 index 000000000..8500e07ea --- /dev/null +++ b/src/refspec.c @@ -0,0 +1,108 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "git2/errors.h" + +#include "common.h" +#include "refspec.h" +#include "util.h" + +int git_refspec_parse(git_refspec *refspec, const char *str) +{ + char *delim; + + memset(refspec, 0x0, sizeof(git_refspec)); + + if (*str == '+') { + refspec->force = 1; + str++; + } + + delim = strchr(str, ':'); + if (delim == NULL) + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse refspec. No ':'"); + + refspec->src = git__strndup(str, delim - str); + if (refspec->src == NULL) + return GIT_ENOMEM; + + refspec->dst = git__strdup(delim + 1); + if (refspec->dst == NULL) { + free(refspec->src); + refspec->src = NULL; + return GIT_ENOMEM; + } + + return GIT_SUCCESS; +} + +const char *git_refspec_src(const git_refspec *refspec) +{ + return refspec->src; +} + +const char *git_refspec_dst(const git_refspec *refspec) +{ + return refspec->dst; +} + +int git_refspec_src_match(const git_refspec *refspec, const char *refname) +{ + return git__fnmatch(refspec->src, refname, 0); +} + +int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name) +{ + size_t baselen, namelen; + + baselen = strlen(spec->dst); + if (outlen <= baselen) + return git__throw(GIT_EINVALIDREFNAME, "Reference name too long"); + + /* + * No '*' at the end means that it's mapped to one specific local + * branch, so no actual transformation is needed. + */ + if (spec->dst[baselen - 1] != '*') { + memcpy(out, spec->dst, baselen + 1); /* include '\0' */ + return GIT_SUCCESS; + } + + /* There's a '*' at the end, so remove its length */ + baselen--; + + /* skip the prefix, -1 is for the '*' */ + name += strlen(spec->src) - 1; + + namelen = strlen(name); + + if (outlen <= baselen + namelen) + return git__throw(GIT_EINVALIDREFNAME, "Reference name too long"); + + memcpy(out, spec->dst, baselen); + memcpy(out + baselen, name, namelen + 1); + + return GIT_SUCCESS; +} diff --git a/src/refspec.h b/src/refspec.h new file mode 100644 index 000000000..230135a4a --- /dev/null +++ b/src/refspec.h @@ -0,0 +1,14 @@ +#ifndef INCLUDE_refspec_h__ +#define INCLUDE_refspec_h__ + +#include "git2/refspec.h" + +struct git_refspec { + int force; + char *src; + char *dst; +}; + +int git_refspec_parse(struct git_refspec *refspec, const char *str); + +#endif diff --git a/src/remote.c b/src/remote.c new file mode 100644 index 000000000..2812f5de6 --- /dev/null +++ b/src/remote.c @@ -0,0 +1,218 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "git2/remote.h" +#include "git2/config.h" +#include "git2/types.h" + +#include "config.h" +#include "repository.h" +#include "remote.h" + +static int refspec_parse(git_refspec *refspec, const char *str) +{ + char *delim; + + memset(refspec, 0x0, sizeof(git_refspec)); + + if (*str == '+') { + refspec->force = 1; + str++; + } + + delim = strchr(str, ':'); + if (delim == NULL) + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse refspec. No ':'"); + + refspec->src = git__strndup(str, delim - str); + if (refspec->src == NULL) + return GIT_ENOMEM; + + refspec->dst = git__strdup(delim + 1); + if (refspec->dst == NULL) { + free(refspec->src); + refspec->src = NULL; + return GIT_ENOMEM; + } + + return GIT_SUCCESS; +} + +static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var) +{ + const char *val; + int error; + + error = git_config_get_string(cfg, var, &val); + if (error < GIT_SUCCESS) + return error; + + return refspec_parse(refspec, val); +} + +int git_remote_get(git_remote **out, git_config *cfg, const char *name) +{ + git_remote *remote; + char *buf = NULL; + const char *val; + int ret, error, buf_len; + + remote = git__malloc(sizeof(git_remote)); + if (remote == NULL) + return GIT_ENOMEM; + + memset(remote, 0x0, sizeof(git_remote)); + remote->name = git__strdup(name); + if (remote->name == NULL) { + error = GIT_ENOMEM; + goto cleanup; + } + + /* "fetch" is the longest var name we're interested in */ + buf_len = STRLEN("remote.") + STRLEN(".fetch") + strlen(name) + 1; + buf = git__malloc(buf_len); + if (buf == NULL) { + error = GIT_ENOMEM; + goto cleanup; + } + + ret = snprintf(buf, buf_len, "%s.%s.%s", "remote", name, "url"); + if (ret < 0) { + error = git__throw(GIT_EOSERR, "Failed to build config var name"); + goto cleanup; + } + + error = git_config_get_string(cfg, buf, &val); + if (error < GIT_SUCCESS) { + error = git__rethrow(error, "Remote's url doesn't exist"); + goto cleanup; + } + + remote->url = git__strdup(val); + if (remote->url == NULL) { + error = GIT_ENOMEM; + goto cleanup; + } + + ret = snprintf(buf, buf_len, "%s.%s.%s", "remote", name, "fetch"); + if (ret < 0) { + error = git__throw(GIT_EOSERR, "Failed to build config var name"); + goto cleanup; + } + + error = parse_remote_refspec(cfg, &remote->fetch, buf); + if (error < GIT_SUCCESS) { + error = git__rethrow(error, "Failed to get fetch refspec"); + goto cleanup; + } + + ret = snprintf(buf, buf_len, "%s.%s.%s", "remote", name, "push"); + if (ret < 0) { + error = git__throw(GIT_EOSERR, "Failed to build config var name"); + goto cleanup; + } + + error = parse_remote_refspec(cfg, &remote->push, buf); + /* Not finding push is fine */ + if (error == GIT_ENOTFOUND) + error = GIT_SUCCESS; + + if (error < GIT_SUCCESS) + goto cleanup; + + *out = remote; + +cleanup: + free(buf); + if (error < GIT_SUCCESS) + git_remote_free(remote); + + return error; +} + +const char *git_remote_name(struct git_remote *remote) +{ + return remote->name; +} + +const char *git_remote_url(struct git_remote *remote) +{ + return remote->url; +} + +const git_refspec *git_remote_fetchspec(struct git_remote *remote) +{ + return &remote->fetch; +} + +const git_refspec *git_remote_pushspec(struct git_remote *remote) +{ + return &remote->push; +} + +int git_remote_connect(git_remote *remote, int direction) +{ + int error; + git_transport *t; + + error = git_transport_new(&t, remote->url); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to create transport"); + + error = git_transport_connect(t, direction); + if (error < GIT_SUCCESS) { + error = git__rethrow(error, "Failed to connect the transport"); + goto cleanup; + } + + remote->transport = t; + +cleanup: + if (error < GIT_SUCCESS) + git_transport_free(t); + + return error; +} + +int git_remote_ls(git_remote *remote, git_headarray *refs) +{ + return git_transport_ls(remote->transport, refs); +} + +void git_remote_free(git_remote *remote) +{ + free(remote->fetch.src); + free(remote->fetch.dst); + free(remote->push.src); + free(remote->push.dst); + free(remote->url); + free(remote->name); + if (remote->transport != NULL) { + if (remote->transport->connected) + git_transport_close(remote->transport); + git_transport_free(remote->transport); + } + free(remote); +} diff --git a/src/remote.h b/src/remote.h new file mode 100644 index 000000000..fdd6cd569 --- /dev/null +++ b/src/remote.h @@ -0,0 +1,16 @@ +#ifndef INCLUDE_remote_h__ +#define INCLUDE_remote_h__ + +#include "remote.h" +#include "refspec.h" +#include "transport.h" + +struct git_remote { + char *name; + char *url; + struct git_refspec fetch; + struct git_refspec push; + git_transport *transport; +}; + +#endif diff --git a/src/transport.c b/src/transport.c new file mode 100644 index 000000000..204ef176f --- /dev/null +++ b/src/transport.c @@ -0,0 +1,91 @@ +#include "common.h" +#include "git2/types.h" +#include "git2/transport.h" +#include "git2/net.h" +#include "transport.h" + +struct { + char *prefix; + git_transport_cb fn; +} transports[] = { + {"git://", git_transport_git}, + {"http://", git_transport_dummy}, + {"https://", git_transport_dummy}, + {"file://", git_transport_local}, + {"git+ssh://", git_transport_dummy}, + {"ssh+git://", git_transport_dummy}, + {NULL, 0} +}; + +static git_transport_cb transport_new_fn(const char *url) +{ + int i = 0; + + while (1) { + if (transports[i].prefix == NULL) + break; + + if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix))) + return transports[i].fn; + + ++i; + } + + /* + * If we still haven't found the transport, we assume we mean a + * local file. + * TODO: Parse "example.com:project.git" as an SSH URL + */ + return git_transport_local; +} + +/************** + * Public API * + **************/ + +int git_transport_dummy(git_transport **GIT_UNUSED(transport)) +{ + GIT_UNUSED_ARG(transport); + return git__throw(GIT_ENOTIMPLEMENTED, "This protocol isn't implemented. Sorry"); +} + +int git_transport_new(git_transport **out, const char *url) +{ + git_transport_cb fn; + git_transport *transport; + int error; + + fn = transport_new_fn(url); + + error = fn(&transport); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to create new transport"); + + transport->url = git__strdup(url); + if (transport->url == NULL) + return GIT_ENOMEM; + + *out = transport; + + return GIT_SUCCESS; +} + +int git_transport_connect(git_transport *transport, int direction) +{ + return transport->connect(transport, direction); +} + +int git_transport_ls(git_transport *transport, git_headarray *array) +{ + return transport->ls(transport, array); +} + +int git_transport_close(git_transport *transport) +{ + return transport->close(transport); +} + +void git_transport_free(git_transport *transport) +{ + transport->free(transport); +} diff --git a/src/transport.h b/src/transport.h new file mode 100644 index 000000000..b17d9a929 --- /dev/null +++ b/src/transport.h @@ -0,0 +1,77 @@ +#ifndef INCLUDE_transport_h__ +#define INCLUDE_transport_h__ + +#include "git2/transport.h" +#include "git2/net.h" +#include "vector.h" + +/* + * A day in the life of a network operation + * ======================================== + * + * The library gets told to ls-remote/push/fetch on/to/from some + * remote. We look at the URL of the remote and fill the function + * table with whatever is appropriate (the remote may be git over git, + * ssh or http(s). It may even be an hg or svn repository, the library + * at this level doesn't care, it just calls the helpers. + * + * The first call is to ->connect() which connects to the remote, + * making use of the direction if necessary. This function must also + * store the remote heads and any other information it needs. + * + * If we just want to execute ls-remote, ->ls() gets + * called. Otherwise, the have/want/need list needs to be built via + * ->wanthaveneed(). We can then ->push() or ->pull(). When we're + * done, we call ->close() to close the connection. ->free() takes + * care of freeing all the resources. + */ + +struct git_transport { + /** + * Where the repo lives + */ + char *url; + /** + * Whether we want to push or fetch + */ + int direction : 1; /* 0 fetch, 1 push */ + int connected : 1; + /** + * Connect and store the remote heads + */ + int (*connect)(struct git_transport *transport, int dir); + /** + * Give a list of references, useful for ls-remote + */ + int (*ls)(struct git_transport *transport, git_headarray *headarray); + /** + * Calculate want/have/need. May not even be needed. + */ + int (*wanthaveneed)(struct git_transport *transport, void *something); + /** + * Build the pack + */ + int (*build_pack)(struct git_transport *transport); + /** + * Push the changes over + */ + int (*push)(struct git_transport *transport); + /** + * Fetch the changes + */ + int (*fetch)(struct git_transport *transport); + /** + * Close the connection + */ + int (*close)(struct git_transport *transport); + /** + * Free the associated resources + */ + void (*free)(struct git_transport *transport); +}; + +int git_transport_local(struct git_transport **transport); +int git_transport_git(struct git_transport **transport); +int git_transport_dummy(struct git_transport **transport); + +#endif diff --git a/src/transport_git.c b/src/transport_git.c new file mode 100644 index 000000000..d79ab5e34 --- /dev/null +++ b/src/transport_git.c @@ -0,0 +1,337 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _MSC_VER +# include +# include +# include +#else +# include +# include +# pragma comment(lib, "Ws2_32.lib") +#endif + +#include "git2/net.h" +#include "git2/pkt.h" +#include "git2/common.h" +#include "git2/types.h" +#include "git2/errors.h" + +#include "vector.h" +#include "transport.h" +#include "common.h" +#include "netops.h" + +typedef struct { + git_transport parent; + int socket; + git_vector refs; + git_remote_head **heads; +} transport_git; + +/* + * Create a git procol request. + * + * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 + */ +static int gen_proto(char **out, int *outlen, const char *cmd, const char *url) +{ + char *delim, *repo, *ptr; + char default_command[] = "git-upload-pack"; + char host[] = "host="; + int len; + + delim = strchr(url, '/'); + if (delim == NULL) + return git__throw(GIT_EOBJCORRUPTED, "Failed to create proto-request: malformed URL"); + + repo = delim; + + delim = strchr(url, ':'); + if (delim == NULL) + delim = strchr(url, '/'); + + if (cmd == NULL) + cmd = default_command; + + len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + STRLEN(host) + (delim - url) + 2; + + *out = git__malloc(len); + if (*out == NULL) + return GIT_ENOMEM; + + *outlen = len - 1; + ptr = *out; + memset(ptr, 0x0, len); + /* We expect the return value to be > len - 1 so don't bother checking it */ + snprintf(ptr, len -1, "%04x%s %s%c%s%s", len - 1, cmd, repo, 0, host, url); + + return GIT_SUCCESS; +} + +static int send_request(int s, const char *cmd, const char *url) +{ + int error, len; + char *msg = NULL; + + error = gen_proto(&msg, &len, cmd, url); + if (error < GIT_SUCCESS) + goto cleanup; + + error = gitno_send(s, msg, len, 0); + +cleanup: + free(msg); + return error; +} + +/* The URL should already have been stripped of the protocol */ +static int extract_host_and_port(char **host, char **port, const char *url) +{ + char *colon, *slash, *delim; + int error = GIT_SUCCESS; + + colon = strchr(url, ':'); + slash = strchr(url, '/'); + + if (slash == NULL) + return git__throw(GIT_EOBJCORRUPTED, "Malformed URL: missing /"); + + if (colon == NULL) { + *port = git__strdup(GIT_DEFAULT_PORT); + } else { + *port = git__strndup(colon + 1, slash - colon - 1); + } + if (*port == NULL) + return GIT_ENOMEM;; + + + delim = colon == NULL ? slash : colon; + *host = git__strndup(url, delim - url); + if (*host == NULL) { + free(*port); + error = GIT_ENOMEM; + } + + return error; +} + +/* + * Parse the URL and connect to a server, storing the socket in + * out. For convenience this also takes care of asking for the remote + * refs + */ +static int do_connect(transport_git *t, const char *url) +{ + int s = -1; + char *host, *port; + const char prefix[] = "git://"; + int error, connected = 0; + + if (!git__prefixcmp(url, prefix)) + url += STRLEN(prefix); + + error = extract_host_and_port(&host, &port, url); + s = gitno_connect(host, port); + connected = 1; + error = send_request(s, NULL, url); + t->socket = s; + + free(host); + free(port); + + if (error < GIT_SUCCESS && s > 0) + close(s); + if (!connected) + error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses"); + + return error; +} + +/* + * Read from the socket and store the references in the vector + */ +static int store_refs(transport_git *t) +{ + gitno_buffer buf; + int s = t->socket; + git_vector *refs = &t->refs; + int error = GIT_SUCCESS; + char buffer[1024]; + const char *line_end, *ptr; + git_pkt *pkt; + + gitno_buffer_setup(&buf, buffer, sizeof(buffer), s); + + while (1) { + error = gitno_recv(&buf); + if (error < GIT_SUCCESS) + return git__rethrow(GIT_EOSERR, "Failed to receive data"); + if (error == GIT_SUCCESS) /* Orderly shutdown, so exit */ + return GIT_SUCCESS; + + ptr = buf.data; + while (1) { + if (buf.offset == 0) + break; + error = git_pkt_parse_line(&pkt, ptr, &line_end, buf.offset); + /* + * If the error is GIT_ESHORTBUFFER, it means the buffer + * isn't long enough to satisfy the request. Break out and + * wait for more input. + * On any other error, fail. + */ + if (error == GIT_ESHORTBUFFER) { + break; + } + if (error < GIT_SUCCESS) { + return error; + } + + /* Get rid of the part we've used already */ + gitno_consume(&buf, line_end); + + error = git_vector_insert(refs, pkt); + if (error < GIT_SUCCESS) + return error; + + if (pkt->type == GIT_PKT_FLUSH) + return GIT_SUCCESS; + + } + } + + return error; +} + +/* + * Since this is a network connection, we need to parse and store the + * pkt-lines at this stage and keep them there. + */ +static int git_connect(git_transport *transport, int direction) +{ + transport_git *t = (transport_git *) transport; + int error = GIT_SUCCESS; + + if (direction == GIT_DIR_PUSH) + return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol"); + + t->parent.direction = direction; + error = git_vector_init(&t->refs, 16, NULL); + if (error < GIT_SUCCESS) + goto cleanup; + + /* Connect and ask for the refs */ + error = do_connect(t, transport->url); + if (error < GIT_SUCCESS) + return error; + + t->parent.connected = 1; + error = store_refs(t); + +cleanup: + if (error < GIT_SUCCESS) { + git_vector_free(&t->refs); + } + + return error; +} + +static int git_ls(git_transport *transport, git_headarray *array) +{ + transport_git *t = (transport_git *) transport; + git_vector *refs = &t->refs; + int len = 0; + unsigned int i; + + array->heads = git__calloc(refs->length, sizeof(git_remote_head *)); + if (array->heads == NULL) + return GIT_ENOMEM; + + for (i = 0; i < refs->length; ++i) { + git_pkt *p = git_vector_get(refs, i); + if (p->type != GIT_PKT_REF) + continue; + + ++len; + array->heads[i] = &(((git_pkt_ref *) p)->head); + } + array->len = len; + t->heads = array->heads; + + return GIT_SUCCESS; +} + +static int git_close(git_transport *transport) +{ + transport_git *t = (transport_git*) transport; + int s = t->socket; + int error; + + /* Can't do anything if there's an error, so don't bother checking */ + git_pkt_send_flush(s); + error = close(s); + if (error < 0) + error = git__throw(GIT_EOSERR, "Failed to close socket"); + + return error; +} + +static void git_free(git_transport *transport) +{ + transport_git *t = (transport_git *) transport; + git_vector *refs = &t->refs; + unsigned int i; + + for (i = 0; i < refs->length; ++i) { + git_pkt *p = git_vector_get(refs, i); + git_pkt_free(p); + } + + git_vector_free(refs); + free(t->heads); + free(t->parent.url); + free(t); +} + +int git_transport_git(git_transport **out) +{ + transport_git *t; + + t = git__malloc(sizeof(transport_git)); + if (t == NULL) + return GIT_ENOMEM; + + memset(t, 0x0, sizeof(transport_git)); + + t->parent.connect = git_connect; + t->parent.ls = git_ls; + t->parent.close = git_close; + t->parent.free = git_free; + + *out = (git_transport *) t; + + return GIT_SUCCESS; +} diff --git a/src/transport_local.c b/src/transport_local.c new file mode 100644 index 000000000..bdb75ad44 --- /dev/null +++ b/src/transport_local.c @@ -0,0 +1,223 @@ +#include "common.h" +#include "git2/types.h" +#include "git2/transport.h" +#include "git2/net.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/tag.h" +#include "refs.h" +#include "transport.h" + +typedef struct { + git_transport parent; + git_repository *repo; + git_vector *refs; +} transport_local; + +static int cmp_refs(const void *a, const void *b) +{ + const char *stra = *(const char **) a; + const char *strb = *(const char **) b; + + return strcmp(stra, strb); +} + +/* + * Try to open the url as a git directory. The direction doesn't + * matter in this case because we're calulating the heads ourselves. + */ +static int local_connect(git_transport *transport, int GIT_UNUSED(direction)) +{ + git_repository *repo; + int error; + transport_local *t = (transport_local *) transport; + const char *path; + const char file_prefix[] = "file://"; + GIT_UNUSED_ARG(dir); + + /* The repo layer doesn't want the prefix */ + if (!git__prefixcmp(transport->url, file_prefix)) + path = transport->url + STRLEN(file_prefix); + else + path = transport->url; + + error = git_repository_open(&repo, path); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to open remote"); + + t->repo = repo; + t->parent.connected = 1; + + return GIT_SUCCESS; +} + +static int add_ref(const char *name, git_repository *repo, git_vector *vec) +{ + const char peeled[] = "^{}"; + git_remote_head *head; + git_reference *ref; + git_object *obj = NULL; + int error = GIT_SUCCESS, peel_len, ret; + + head = git__malloc(sizeof(git_remote_head)); + if (head == NULL) + return GIT_ENOMEM; + + head->name = git__strdup(name); + if (head->name == NULL) { + error = GIT_ENOMEM; + goto out; + } + + error = git_reference_lookup(&ref, repo, name); + if (error < GIT_SUCCESS) + goto out; + + error = git_reference_resolve(&ref, ref); + if (error < GIT_SUCCESS) + goto out; + + git_oid_cpy(&head->oid, git_reference_oid(ref)); + + error = git_vector_insert(vec, head); + if (error < GIT_SUCCESS) + goto out; + + /* If it's not a tag, we don't need to try to peel it */ + if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) + goto out; + + error = git_object_lookup(&obj, repo, &head->oid, GIT_OBJ_ANY); + if (error < GIT_SUCCESS) { + git__rethrow(error, "Failed to lookup object"); + } + + /* If it's not an annotated tag, just get out */ + if (git_object_type(obj) != GIT_OBJ_TAG) + goto out; + + /* And if it's a tag, peel it, and add it to the list */ + head = git__malloc(sizeof(git_remote_head)); + peel_len = strlen(name) + STRLEN(peeled); + head->name = git__malloc(peel_len + 1); + ret = snprintf(head->name, peel_len + 1, "%s%s", name, peeled); + if (ret >= peel_len + 1) { + error = git__throw(GIT_ERROR, "The string is magically to long"); + } + + git_oid_cpy(&head->oid, git_tag_target_oid((git_tag *) obj)); + + error = git_vector_insert(vec, head); + if (error < GIT_SUCCESS) + goto out; + + out: + git_object_close(obj); + if (error < GIT_SUCCESS) { + free(head->name); + free(head); + } + return error; +} + +static int local_ls(git_transport *transport, git_headarray *array) +{ + int error; + unsigned int i; + git_repository *repo; + git_vector *vec; + git_strarray refs; + transport_local *t = (transport_local *) transport; + + assert(transport && transport->connected); + + repo = t->repo; + + error = git_reference_listall(&refs, repo, GIT_REF_LISTALL); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to list remote heads"); + + vec = git__malloc(sizeof(git_vector)); + if (vec == NULL) { + error = GIT_ENOMEM; + goto out; + } + + error = git_vector_init(vec, refs.count, NULL); + if (error < GIT_SUCCESS) + return error; + + /* Sort the references first */ + qsort(refs.strings, refs.count, sizeof(char *), cmp_refs); + + /* Add HEAD */ + error = add_ref(GIT_HEAD_FILE, repo, vec); + if (error < GIT_SUCCESS) + goto out; + + for (i = 0; i < refs.count; ++i) { + error = add_ref(refs.strings[i], repo, vec); + if (error < GIT_SUCCESS) + goto out; + } + + array->len = vec->length; + array->heads = (git_remote_head **)vec->contents; + + t->refs = vec; + + out: + + git_strarray_free(&refs); + + return error; +} + +static int local_close(git_transport *GIT_UNUSED(transport)) +{ + /* Nothing to do */ + GIT_UNUSED_ARG(transport); + return GIT_SUCCESS; +} + +static void local_free(git_transport *transport) +{ + unsigned int i; + transport_local *t = (transport_local *) transport; + git_vector *vec = t->refs; + + assert(transport); + + for (i = 0; i < vec->length; ++i) { + git_remote_head *h = git_vector_get(vec, i); + free(h->name); + free(h); + } + git_vector_free(vec); + free(vec); + git_repository_free(t->repo); + free(t->parent.url); + free(t); +} + +/************** + * Public API * + **************/ + +int git_transport_local(git_transport **out) +{ + transport_local *t; + + t = git__malloc(sizeof(transport_local)); + if (t == NULL) + return GIT_ENOMEM; + + t->parent.connect = local_connect; + t->parent.ls = local_ls; + t->parent.close = local_close; + t->parent.free = local_free; + + *out = (git_transport *) t; + + return GIT_SUCCESS; +} diff --git a/src/util.c b/src/util.c index 7dc8d3b4f..5b8a1367c 100644 --- a/src/util.c +++ b/src/util.c @@ -1,9 +1,16 @@ #include #include "common.h" +#include "fnmatch.h" #include #include #include +#ifdef _MSV_VER +# include +#else +# include +#endif + void git_libgit2_version(int *major, int *minor, int *rev) { *major = LIBGIT2_VER_MAJOR; @@ -20,6 +27,21 @@ void git_strarray_free(git_strarray *array) free(array->strings); } +int git__fnmatch(const char *pattern, const char *name, int flags) +{ + int ret; + + ret = fnmatch(pattern, name, flags); + switch (ret) { + case 0: + return GIT_SUCCESS; + case FNM_NOMATCH: + return GIT_ENOMATCH; + default: + return git__throw(GIT_EOSERR, "Error trying to match path"); + } +} + int git__strtol32(long *result, const char *nptr, const char **endptr, int base) { const char *p; diff --git a/src/util.h b/src/util.h index eac615141..0faf7f69c 100644 --- a/src/util.h +++ b/src/util.h @@ -4,6 +4,9 @@ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define bitsizeof(x) (CHAR_BIT * sizeof(x)) #define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits)))) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif /* * Custom memory allocation wrappers @@ -94,6 +97,8 @@ extern void git__strtolower(char *str); #define GIT_OID_LINE_LENGTH(header) (STRLEN(header) + 1 + GIT_OID_HEXSZ + 1) +extern int git__fnmatch(const char *pattern, const char *name, int flags); + /* * Realloc the buffer pointed at by variable 'x' so that it can hold * at least 'nr' entries; the number of entries currently allocated diff --git a/tests/resources/testrepo.git/config b/tests/resources/testrepo.git/config index 2f8958058..1a5aacdfa 100644 Binary files a/tests/resources/testrepo.git/config and b/tests/resources/testrepo.git/config differ diff --git a/tests/t16-remotes.c b/tests/t16-remotes.c new file mode 100644 index 000000000..4bc2f55d7 --- /dev/null +++ b/tests/t16-remotes.c @@ -0,0 +1,106 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "test_lib.h" +#include "test_helpers.h" + +#include + +BEGIN_TEST(remotes0, "remote parsing works") + git_remote *remote; + git_repository *repo; + git_config *cfg; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_config(&cfg, repo, NULL, NULL)); + must_pass(git_remote_get(&remote, cfg, "test")); + must_be_true(!strcmp(git_remote_name(remote), "test")); + must_be_true(!strcmp(git_remote_url(remote), "git://github.com/libgit2/libgit2")); + + git_remote_free(remote); + git_config_free(cfg); + git_repository_free(repo); +END_TEST + +BEGIN_TEST(refspec0, "remote with refspec works") + git_remote *remote; + git_repository *repo; + git_config *cfg; + const git_refspec *refspec = NULL; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_config(&cfg, repo, NULL, NULL)); + must_pass(git_remote_get(&remote, cfg, "test")); + refspec = git_remote_fetchspec(remote); + must_be_true(refspec != NULL); + must_be_true(!strcmp(git_refspec_src(refspec), "refs/heads/*")); + must_be_true(!strcmp(git_refspec_dst(refspec), "refs/remotes/test/*")); + git_remote_free(remote); + git_config_free(cfg); + git_repository_free(repo); +END_TEST + +BEGIN_TEST(refspec1, "remote fnmatch works as expected") + git_remote *remote; + git_repository *repo; + git_config *cfg; + const git_refspec *refspec = NULL; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_config(&cfg, repo, NULL, NULL)); + must_pass(git_remote_get(&remote, cfg, "test")); + refspec = git_remote_fetchspec(remote); + must_be_true(refspec != NULL); + must_pass(git_refspec_src_match(refspec, "refs/heads/master")); + must_pass(git_refspec_src_match(refspec, "refs/heads/multi/level/branch")); + git_remote_free(remote); + git_config_free(cfg); + git_repository_free(repo); +END_TEST + +BEGIN_TEST(refspec2, "refspec transform") + git_remote *remote; + git_repository *repo; + git_config *cfg; + const git_refspec *refspec = NULL; + char ref[1024] = {0}; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_config(&cfg, repo, NULL, NULL)); + must_pass(git_remote_get(&remote, cfg, "test")); + refspec = git_remote_fetchspec(remote); + must_be_true(refspec != NULL); + must_pass(git_refspec_transform(ref, sizeof(ref), refspec, "refs/heads/master")); + must_be_true(!strcmp(ref, "refs/remotes/test/master")); + git_remote_free(remote); + git_config_free(cfg); + git_repository_free(repo); +END_TEST + +BEGIN_SUITE(remotes) + ADD_TEST(remotes0) + ADD_TEST(refspec0) + ADD_TEST(refspec1) + ADD_TEST(refspec2) +END_SUITE diff --git a/tests/test_main.c b/tests/test_main.c index 3fd117d0b..aab6c068b 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -43,6 +43,7 @@ DECLARE_SUITE(refs); DECLARE_SUITE(repository); DECLARE_SUITE(threads); DECLARE_SUITE(config); +DECLARE_SUITE(remotes); static libgit2_suite suite_methods[]= { SUITE_NAME(core), @@ -59,6 +60,7 @@ static libgit2_suite suite_methods[]= { SUITE_NAME(repository), SUITE_NAME(threads), SUITE_NAME(config), + SUITE_NAME(remotes), }; #define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods))