Push code for transport library
authorDaniel Barkalow <barkalow@iabervon.org>
Tue, 11 Sep 2007 03:03:04 +0000 (23:03 -0400)
committerJunio C Hamano <gitster@pobox.com>
Wed, 19 Sep 2007 10:22:30 +0000 (03:22 -0700)
This moves the code to call push backends into a library that can be
extended to make matching fetch and push decisions based on the URL it
gets, and which could be changed to have built-in implementations
instead of calling external programs.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Makefile
builtin-push.c
transport.c [new file with mode: 0644]
transport.h [new file with mode: 0644]

index 9ce6a9ba310d1c6521f48039c360c3c683e06e1a..da6abdfd81579fe2265b4aa6db2a5b8a5d3f42a2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -310,7 +310,8 @@ LIB_OBJS = \
        write_or_die.o trace.o list-objects.o grep.o match-trees.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
-       convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o
+       convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
+       transport.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
index 88c5024da7c9831e69ee20ca20ed9bdb5ddee63f..f496b460073827cc14ce2e224bc9756735b0141f 100644 (file)
@@ -6,10 +6,11 @@
 #include "run-command.h"
 #include "builtin.h"
 #include "remote.h"
+#include "transport.h"
 
 static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
 
-static int all, force, thin, verbose;
+static int all, thin, verbose;
 static const char *receivepack;
 
 static const char **refspec;
@@ -43,80 +44,38 @@ static void set_refspecs(const char **refs, int nr)
        }
 }
 
-static int do_push(const char *repo)
+static int do_push(const char *repo, int flags)
 {
        int i, errs;
-       int common_argc;
-       const char **argv;
-       int argc;
        struct remote *remote = remote_get(repo);
 
        if (!remote)
                die("bad repository '%s'", repo);
 
-       if (remote->receivepack) {
-               char *rp = xmalloc(strlen(remote->receivepack) + 16);
-               sprintf(rp, "--receive-pack=%s", remote->receivepack);
-               receivepack = rp;
-       }
        if (!refspec && !all && remote->push_refspec_nr) {
                refspec = remote->push_refspec;
                refspec_nr = remote->push_refspec_nr;
        }
-
-       argv = xmalloc((refspec_nr + 10) * sizeof(char *));
-       argv[0] = "dummy-send-pack";
-       argc = 1;
-       if (all)
-               argv[argc++] = "--all";
-       if (force)
-               argv[argc++] = "--force";
-       if (receivepack)
-               argv[argc++] = receivepack;
-       common_argc = argc;
-
        errs = 0;
        for (i = 0; i < remote->uri_nr; i++) {
+               struct transport *transport =
+                       transport_get(remote, remote->uri[i], 0);
                int err;
-               int dest_argc = common_argc;
-               int dest_refspec_nr = refspec_nr;
-               const char **dest_refspec = refspec;
-               const char *dest = remote->uri[i];
-               const char *sender = "send-pack";
-               if (!prefixcmp(dest, "http://") ||
-                   !prefixcmp(dest, "https://"))
-                       sender = "http-push";
-               else {
-                       char *rem = xmalloc(strlen(remote->name) + 10);
-                       sprintf(rem, "--remote=%s", remote->name);
-                       argv[dest_argc++] = rem;
-                       if (thin)
-                               argv[dest_argc++] = "--thin";
-               }
-               argv[0] = sender;
-               argv[dest_argc++] = dest;
-               while (dest_refspec_nr--)
-                       argv[dest_argc++] = *dest_refspec++;
-               argv[dest_argc] = NULL;
+               if (receivepack)
+                       transport_set_option(transport,
+                                            TRANS_OPT_RECEIVEPACK, receivepack);
+               if (thin)
+                       transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
                if (verbose)
-                       fprintf(stderr, "Pushing to %s\n", dest);
-               err = run_command_v_opt(argv, RUN_GIT_CMD);
+                       fprintf(stderr, "Pushing to %s\n", remote->uri[i]);
+               err = transport_push(transport, refspec_nr, refspec, flags);
+               err |= transport_disconnect(transport);
+
                if (!err)
                        continue;
 
                error("failed to push to '%s'", remote->uri[i]);
-               switch (err) {
-               case -ERR_RUN_COMMAND_FORK:
-                       error("unable to fork for %s", sender);
-               case -ERR_RUN_COMMAND_EXEC:
-                       error("unable to exec %s", sender);
-                       break;
-               case -ERR_RUN_COMMAND_WAITPID:
-               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
-               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-                       error("%s died with strange error", sender);
-               }
                errs++;
        }
        return !!errs;
@@ -125,6 +84,7 @@ static int do_push(const char *repo)
 int cmd_push(int argc, const char **argv, const char *prefix)
 {
        int i;
+       int flags = 0;
        const char *repo = NULL;        /* default repository */
 
        for (i = 1; i < argc; i++) {
@@ -144,7 +104,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp(arg, "--all")) {
-                       all = 1;
+                       flags |= TRANSPORT_PUSH_ALL;
                        continue;
                }
                if (!strcmp(arg, "--tags")) {
@@ -152,7 +112,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) {
-                       force = 1;
+                       flags |= TRANSPORT_PUSH_FORCE;
                        continue;
                }
                if (!strcmp(arg, "--thin")) {
@@ -164,11 +124,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!prefixcmp(arg, "--receive-pack=")) {
-                       receivepack = arg;
+                       receivepack = arg + 15;
                        continue;
                }
                if (!prefixcmp(arg, "--exec=")) {
-                       receivepack = arg;
+                       receivepack = arg + 7;
                        continue;
                }
                usage(push_usage);
@@ -177,5 +137,5 @@ int cmd_push(int argc, const char **argv, const char *prefix)
        if (all && refspec)
                usage(push_usage);
 
-       return do_push(repo);
+       return do_push(repo, flags);
 }
diff --git a/transport.c b/transport.c
new file mode 100644 (file)
index 0000000..edbdc3c
--- /dev/null
@@ -0,0 +1,196 @@
+#include "cache.h"
+#include "transport.h"
+#include "run-command.h"
+
+static const struct transport_ops rsync_transport;
+
+static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) {
+       const char **argv;
+       int argc;
+       int err;
+
+       argv = xmalloc((refspec_nr + 11) * sizeof(char *));
+       argv[0] = "http-push";
+       argc = 1;
+       if (flags & TRANSPORT_PUSH_ALL)
+               argv[argc++] = "--all";
+       if (flags & TRANSPORT_PUSH_FORCE)
+               argv[argc++] = "--force";
+       argv[argc++] = transport->url;
+       while (refspec_nr--)
+               argv[argc++] = *refspec++;
+       argv[argc] = NULL;
+       err = run_command_v_opt(argv, RUN_GIT_CMD);
+       switch (err) {
+       case -ERR_RUN_COMMAND_FORK:
+               error("unable to fork for %s", argv[0]);
+       case -ERR_RUN_COMMAND_EXEC:
+               error("unable to exec %s", argv[0]);
+               break;
+       case -ERR_RUN_COMMAND_WAITPID:
+       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+               error("%s died with strange error", argv[0]);
+       }
+       return !!err;
+}
+
+static const struct transport_ops curl_transport = {
+       /* set_option */        NULL,
+       /* push */              curl_transport_push
+};
+
+static const struct transport_ops bundle_transport = {
+};
+
+struct git_transport_data {
+       unsigned thin : 1;
+
+       const char *receivepack;
+};
+
+static int set_git_option(struct transport *connection,
+                         const char *name, const char *value)
+{
+       struct git_transport_data *data = connection->data;
+       if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
+               data->receivepack = value;
+               return 0;
+       } else if (!strcmp(name, TRANS_OPT_THIN)) {
+               data->thin = !!value;
+               return 0;
+       }
+       return 1;
+}
+
+static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) {
+       struct git_transport_data *data = transport->data;
+       const char **argv;
+       char *rem;
+       int argc;
+       int err;
+
+       argv = xmalloc((refspec_nr + 11) * sizeof(char *));
+       argv[0] = "send-pack";
+       argc = 1;
+       if (flags & TRANSPORT_PUSH_ALL)
+               argv[argc++] = "--all";
+       if (flags & TRANSPORT_PUSH_FORCE)
+               argv[argc++] = "--force";
+       if (data->receivepack) {
+               char *rp = xmalloc(strlen(data->receivepack) + 16);
+               sprintf(rp, "--receive-pack=%s", data->receivepack);
+               argv[argc++] = rp;
+       }
+       if (data->thin)
+               argv[argc++] = "--thin";
+       rem = xmalloc(strlen(transport->remote->name) + 10);
+       sprintf(rem, "--remote=%s", transport->remote->name);
+       argv[argc++] = rem;
+       argv[argc++] = transport->url;
+       while (refspec_nr--)
+               argv[argc++] = *refspec++;
+       argv[argc] = NULL;
+       err = run_command_v_opt(argv, RUN_GIT_CMD);
+       switch (err) {
+       case -ERR_RUN_COMMAND_FORK:
+               error("unable to fork for %s", argv[0]);
+       case -ERR_RUN_COMMAND_EXEC:
+               error("unable to exec %s", argv[0]);
+               break;
+       case -ERR_RUN_COMMAND_WAITPID:
+       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+               error("%s died with strange error", argv[0]);
+       }
+       return !!err;
+}
+
+static const struct transport_ops git_transport = {
+       /* set_option */        set_git_option,
+       /* push */              git_transport_push
+};
+
+static int is_local(const char *url)
+{
+       const char *colon = strchr(url, ':');
+       const char *slash = strchr(url, '/');
+       return !colon || (slash && slash < colon);
+}
+
+static int is_file(const char *url)
+{
+       struct stat buf;
+       if (stat(url, &buf))
+               return 0;
+       return S_ISREG(buf.st_mode);
+}
+
+struct transport *transport_get(struct remote *remote, const char *url,
+                               int fetch)
+{
+       struct transport *ret = NULL;
+       if (!prefixcmp(url, "rsync://")) {
+               ret = xmalloc(sizeof(*ret));
+               ret->data = NULL;
+               ret->ops = &rsync_transport;
+       } else if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://") ||
+                  !prefixcmp(url, "ftp://")) {
+               ret = xmalloc(sizeof(*ret));
+               ret->ops = &curl_transport;
+               ret->data = NULL;
+       } else if (is_local(url) && is_file(url)) {
+               ret = xmalloc(sizeof(*ret));
+               ret->data = NULL;
+               ret->ops = &bundle_transport;
+       } else {
+               struct git_transport_data *data = xcalloc(1, sizeof(*data));
+               ret = xcalloc(1, sizeof(*ret));
+               ret->data = data;
+               data->thin = 1;
+               data->receivepack = "git-receive-pack";
+               if (remote && remote->receivepack)
+                       data->receivepack = remote->receivepack;
+               ret->ops = &git_transport;
+       }
+       if (ret) {
+               ret->remote = remote;
+               ret->url = url;
+               ret->fetch = !!fetch;
+       }
+       return ret;
+}
+
+int transport_set_option(struct transport *transport,
+                        const char *name, const char *value)
+{
+       int ret = 1;
+       if (transport->ops->set_option)
+               ret = transport->ops->set_option(transport, name, value);
+       if (ret < 0)
+               fprintf(stderr, "For '%s' option %s cannot be set to '%s'\n",
+                       transport->url, name, value);
+       if (ret > 0)
+               fprintf(stderr, "For '%s' option %s is ignored\n",
+                       transport->url, name);
+       return ret;
+}
+
+int transport_push(struct transport *transport,
+                  int refspec_nr, const char **refspec, int flags)
+{
+       if (!transport->ops->push)
+               return 1;
+       return transport->ops->push(transport, refspec_nr, refspec, flags);
+}
+
+int transport_disconnect(struct transport *transport)
+{
+       int ret = 0;
+       if (transport->ops->disconnect)
+               ret = transport->ops->disconnect(transport);
+       free(transport);
+       return ret;
+}
diff --git a/transport.h b/transport.h
new file mode 100644 (file)
index 0000000..5c2eb95
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef TRANSPORT_H
+#define TRANSPORT_H
+
+#include "cache.h"
+#include "remote.h"
+
+struct transport {
+       unsigned verbose : 1;
+       unsigned fetch : 1;
+       struct remote *remote;
+       const char *url;
+
+       void *data;
+
+       struct ref *remote_refs;
+
+       const struct transport_ops *ops;
+};
+
+#define TRANSPORT_PUSH_ALL 1
+#define TRANSPORT_PUSH_FORCE 2
+
+struct transport_ops {
+       /**
+        * Returns 0 if successful, positive if the option is not
+        * recognized or is inapplicable, and negative if the option
+        * is applicable but the value is invalid.
+        **/
+       int (*set_option)(struct transport *connection, const char *name,
+                         const char *value);
+
+       int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
+
+       int (*disconnect)(struct transport *connection);
+};
+
+/* Returns a transport suitable for the url */
+struct transport *transport_get(struct remote *remote, const char *url,
+                               int fetch);
+
+/* Transport options which apply to git:// and scp-style URLs */
+
+/* The program to use on the remote side to receive a pack */
+#define TRANS_OPT_RECEIVEPACK "receivepack"
+
+/* Transfer the data as a thin pack if not null */
+#define TRANS_OPT_THIN "thin"
+
+/**
+ * Returns 0 if the option was used, non-zero otherwise. Prints a
+ * message to stderr if the option is not used.
+ **/
+int transport_set_option(struct transport *transport, const char *name,
+                        const char *value);
+
+int transport_push(struct transport *connection,
+                  int refspec_nr, const char **refspec, int flags);
+
+int transport_disconnect(struct transport *transport);
+
+#endif