summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVicent Martí <tanoku@gmail.com>2011-07-05 04:31:37 -0700
committerVicent Martí <tanoku@gmail.com>2011-07-05 04:31:37 -0700
commitf12aa9dc5ed04cfe92f1d536314eb03185f67f7d (patch)
treeb720325beef678f7d132280d8f2644277a4e7d86
parent7d69f78897fc079a58059d9a84ab5928161d78cb (diff)
parent0ac2726fdf945792028e59105d8630a91c5d3663 (diff)
downloadlibgit2-f12aa9dc5ed04cfe92f1d536314eb03185f67f7d.tar.gz
Merge pull request #300 from carlosmn/gsoc2011/master
A bit of networking
-rw-r--r--include/git2.h8
-rw-r--r--include/git2/branch.h9
-rw-r--r--include/git2/errors.h6
-rw-r--r--include/git2/net.h33
-rw-r--r--include/git2/pkt.h56
-rw-r--r--include/git2/refspec.h42
-rw-r--r--include/git2/remote.h87
-rw-r--r--include/git2/transport.h58
-rw-r--r--include/git2/types.h21
-rw-r--r--src/fnmatch.c489
-rw-r--r--src/fnmatch.h84
-rw-r--r--src/netops.c144
-rw-r--r--src/netops.h22
-rw-r--r--src/pkt.c210
-rw-r--r--src/refspec.c108
-rw-r--r--src/refspec.h14
-rw-r--r--src/remote.c218
-rw-r--r--src/remote.h16
-rw-r--r--src/transport.c91
-rw-r--r--src/transport.h77
-rw-r--r--src/transport_git.c337
-rw-r--r--src/transport_local.c223
-rw-r--r--src/util.c22
-rw-r--r--src/util.h5
-rw-r--r--tests/resources/testrepo.git/config3
-rw-r--r--tests/t16-remotes.c106
-rw-r--r--tests/test_main.c2
27 files changed, 2491 insertions, 0 deletions
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 <config.h>
+#endif
+
+/* Enable GNU extensions in fnmatch.h. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+
+#include <errno.h>
+#include <fnmatch.h>
+#include <ctype.h>
+
+#if defined _MSC_VER
+# define HAVE_STRING_H 1
+#endif
+
+#if HAVE_STRING_H || defined _LIBC
+# include <string.h>
+#else
+# include <strings.h>
+#endif
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#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: <wchar.h> must be included before <wctype.h>. */
+# include <wchar.h>
+# include <wctype.h>
+#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 <unistd.h>. */
+#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 <sys/types.h>
+# include <sys/socket.h>
+# include <netdb.h>
+#else
+# include <winsock2.h>
+# include <Ws2tcpip.h>
+# 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 <ctype.h>
+
+#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 <sys/types.h>
+# include <sys/socket.h>
+# include <netdb.h>
+#else
+# include <winsock2.h>
+# include <Ws2tcpip.h>
+# 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 <git2.h>
#include "common.h"
+#include "fnmatch.h"
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
+#ifdef _MSV_VER
+# include <Shlwapi.h>
+#else
+# include <fnmatch.h>
+#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
--- a/tests/resources/testrepo.git/config
+++ b/tests/resources/testrepo.git/config
@@ -3,3 +3,6 @@
filemode = true
bare = true
logallrefupdates = true
+[remote "test"]
+ url = git://github.com/libgit2/libgit2
+ fetch = +refs/heads/*:refs/remotes/test/*
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 <git2.h>
+
+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))