git-remote-ext
[gitweb.git] / transport.c
index 298dc46ec5ede0b972ec9be46556ffbec17df253..4dba6f8815a80093a8ac9edc226d99bba1bb6394 100644 (file)
@@ -8,6 +8,8 @@
 #include "bundle.h"
 #include "dir.h"
 #include "refs.h"
+#include "branch.h"
+#include "url.h"
 
 /* rsync support */
 
@@ -135,6 +137,53 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list)
        }
 }
 
+static void set_upstreams(struct transport *transport, struct ref *refs,
+       int pretend)
+{
+       struct ref *ref;
+       for (ref = refs; ref; ref = ref->next) {
+               const char *localname;
+               const char *tmp;
+               const char *remotename;
+               unsigned char sha[20];
+               int flag = 0;
+               /*
+                * Check suitability for tracking. Must be successful /
+                * already up-to-date ref create/modify (not delete).
+                */
+               if (ref->status != REF_STATUS_OK &&
+                       ref->status != REF_STATUS_UPTODATE)
+                       continue;
+               if (!ref->peer_ref)
+                       continue;
+               if (!ref->new_sha1 || is_null_sha1(ref->new_sha1))
+                       continue;
+
+               /* Follow symbolic refs (mainly for HEAD). */
+               localname = ref->peer_ref->name;
+               remotename = ref->name;
+               tmp = resolve_ref(localname, sha, 1, &flag);
+               if (tmp && flag & REF_ISSYMREF &&
+                       !prefixcmp(tmp, "refs/heads/"))
+                       localname = tmp;
+
+               /* Both source and destination must be local branches. */
+               if (!localname || prefixcmp(localname, "refs/heads/"))
+                       continue;
+               if (!remotename || prefixcmp(remotename, "refs/heads/"))
+                       continue;
+
+               if (!pretend)
+                       install_branch_config(BRANCH_CONFIG_VERBOSE,
+                               localname + 11, transport->remote->name,
+                               remotename);
+               else
+                       printf("Would set upstream of '%s' to '%s' of '%s'\n",
+                               localname + 11, remotename + 11,
+                               transport->remote->name);
+       }
+}
+
 static const char *rsync_url(const char *url)
 {
        return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
@@ -143,7 +192,7 @@ static const char *rsync_url(const char *url)
 static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 {
        struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
-       struct ref dummy, *tail = &dummy;
+       struct ref dummy = {0}, *tail = &dummy;
        struct child_process rsync;
        const char *args[5];
        int temp_dir_len;
@@ -204,7 +253,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 }
 
 static int fetch_objs_via_rsync(struct transport *transport,
-                               int nr_objs, const struct ref **to_fetch)
+                               int nr_objs, struct ref **to_fetch)
 {
        struct strbuf buf = STRBUF_INIT;
        struct child_process rsync;
@@ -349,35 +398,6 @@ static int rsync_transport_push(struct transport *transport,
        return result;
 }
 
-#ifndef NO_CURL
-static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
-{
-       const char **argv;
-       int argc;
-
-       if (flags & TRANSPORT_PUSH_MIRROR)
-               return error("http transport does not support mirror mode");
-
-       argv = xmalloc((refspec_nr + 12) * sizeof(char *));
-       argv[0] = "http-push";
-       argc = 1;
-       if (flags & TRANSPORT_PUSH_ALL)
-               argv[argc++] = "--all";
-       if (flags & TRANSPORT_PUSH_FORCE)
-               argv[argc++] = "--force";
-       if (flags & TRANSPORT_PUSH_DRY_RUN)
-               argv[argc++] = "--dry-run";
-       if (flags & TRANSPORT_PUSH_VERBOSE)
-               argv[argc++] = "--verbose";
-       argv[argc++] = transport->url;
-       while (refspec_nr--)
-               argv[argc++] = *refspec++;
-       argv[argc] = NULL;
-       return !!run_command_v_opt(argv, RUN_GIT_CMD);
-}
-
-#endif
-
 struct bundle_transport_data {
        int fd;
        struct bundle_header header;
@@ -408,7 +428,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus
 }
 
 static int fetch_refs_from_bundle(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd);
@@ -424,41 +444,36 @@ static int close_bundle(struct transport *transport)
 }
 
 struct git_transport_data {
-       unsigned thin : 1;
-       unsigned keep : 1;
-       unsigned followtags : 1;
-       int depth;
+       struct git_transport_options options;
        struct child_process *conn;
        int fd[2];
-       const char *uploadpack;
-       const char *receivepack;
+       unsigned got_remote_heads : 1;
        struct extra_have_objects extra_have;
 };
 
-static int set_git_option(struct transport *connection,
+static int set_git_option(struct git_transport_options *opts,
                          const char *name, const char *value)
 {
-       struct git_transport_data *data = connection->data;
        if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
-               data->uploadpack = value;
+               opts->uploadpack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
-               data->receivepack = value;
+               opts->receivepack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_THIN)) {
-               data->thin = !!value;
+               opts->thin = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
-               data->followtags = !!value;
+               opts->followtags = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_KEEP)) {
-               data->keep = !!value;
+               opts->keep = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_DEPTH)) {
                if (!value)
-                       data->depth = 0;
+                       opts->depth = 0;
                else
-                       data->depth = atoi(value);
+                       opts->depth = atoi(value);
                return 0;
        }
        return 1;
@@ -467,9 +482,15 @@ static int set_git_option(struct transport *connection,
 static int connect_setup(struct transport *transport, int for_push, int verbose)
 {
        struct git_transport_data *data = transport->data;
+
+       if (data->conn)
+               return 0;
+
        data->conn = git_connect(data->fd, transport->url,
-                                for_push ? data->receivepack : data->uploadpack,
+                                for_push ? data->options.receivepack :
+                                data->options.uploadpack,
                                 verbose ? CONNECT_VERBOSE : 0);
+
        return 0;
 }
 
@@ -481,12 +502,13 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
        connect_setup(transport, for_push, 0);
        get_remote_heads(data->fd[0], &refs, 0, NULL,
                         for_push ? REF_NORMAL : 0, &data->extra_have);
+       data->got_remote_heads = 1;
 
        return refs;
 }
 
 static int fetch_refs_via_pack(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
@@ -498,22 +520,23 @@ static int fetch_refs_via_pack(struct transport *transport,
        struct ref *refs_tmp = NULL;
 
        memset(&args, 0, sizeof(args));
-       args.uploadpack = data->uploadpack;
-       args.keep_pack = data->keep;
+       args.uploadpack = data->options.uploadpack;
+       args.keep_pack = data->options.keep;
        args.lock_pack = 1;
-       args.use_thin_pack = data->thin;
-       args.include_tag = data->followtags;
+       args.use_thin_pack = data->options.thin;
+       args.include_tag = data->options.followtags;
        args.verbose = (transport->verbose > 0);
        args.quiet = (transport->verbose < 0);
-       args.no_progress = args.quiet || (!transport->progress && !isatty(1));
-       args.depth = data->depth;
+       args.no_progress = !transport->progress;
+       args.depth = data->options.depth;
 
        for (i = 0; i < nr_heads; i++)
                origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
 
-       if (!data->conn) {
+       if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
                get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
+               data->got_remote_heads = 1;
        }
 
        refs = fetch_pack(&args, data->fd, data->conn,
@@ -524,6 +547,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        if (finish_connect(data->conn))
                refs = NULL;
        data->conn = NULL;
+       data->got_remote_heads = 0;
 
        free_refs(refs_tmp);
 
@@ -550,7 +574,7 @@ static int push_had_errors(struct ref *ref)
        return 0;
 }
 
-static int refs_pushed(struct ref *ref)
+int transport_refs_pushed(struct ref *ref)
 {
        for (; ref; ref = ref->next) {
                switch(ref->status) {
@@ -564,7 +588,7 @@ static int refs_pushed(struct ref *ref)
        return 0;
 }
 
-static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
 {
        struct refspec rs;
 
@@ -586,8 +610,6 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref, int verb
        }
 }
 
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-
 static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain)
 {
        if (porcelain) {
@@ -600,7 +622,7 @@ static void print_ref_status(char flag, const char *summary, struct ref *to, str
                else
                        fprintf(stdout, "%s\n", summary);
        } else {
-               fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
+               fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary);
                if (from)
                        fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
                else
@@ -652,7 +674,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)
 static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain)
 {
        if (!count)
-               fprintf(stderr, "To %s\n", dest);
+               fprintf(porcelain ? stdout : stderr, "To %s\n", dest);
 
        switch(ref->status) {
        case REF_STATUS_NONE:
@@ -668,7 +690,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
                break;
        case REF_STATUS_REJECT_NONFASTFORWARD:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
-                                                "non-fast forward", porcelain);
+                                                "non-fast-forward", porcelain);
                break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
@@ -688,8 +710,8 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
        return 1;
 }
 
-static void print_push_status(const char *dest, struct ref *refs,
-                             int verbose, int porcelain, int * nonfastforward)
+void transport_print_push_status(const char *dest, struct ref *refs,
+                                 int verbose, int porcelain, int *nonfastforward)
 {
        struct ref *ref;
        int n = 0;
@@ -715,7 +737,7 @@ static void print_push_status(const char *dest, struct ref *refs,
        }
 }
 
-static void verify_remote_names(int nr_heads, const char **heads)
+void transport_verify_remote_names(int nr_heads, const char **heads)
 {
        int i;
 
@@ -752,20 +774,23 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
        struct send_pack_args args;
        int ret;
 
-       if (!data->conn) {
+       if (!data->got_remote_heads) {
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
 
                get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
                                 NULL);
+               data->got_remote_heads = 1;
        }
 
+       memset(&args, 0, sizeof(args));
        args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
-       args.use_thin_pack = data->thin;
-       args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
-       args.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
+       args.use_thin_pack = data->options.thin;
+       args.verbose = (transport->verbose > 0);
+       args.quiet = (transport->verbose < 0);
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
+       args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
 
        ret = send_pack(&args, data->fd, data->conn, remote_refs,
                        &data->extra_have);
@@ -774,15 +799,28 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
        close(data->fd[0]);
        ret |= finish_connect(data->conn);
        data->conn = NULL;
+       data->got_remote_heads = 0;
 
        return ret;
 }
 
+static int connect_git(struct transport *transport, const char *name,
+                      const char *executable, int fd[2])
+{
+       struct git_transport_data *data = transport->data;
+       data->conn = git_connect(data->fd, transport->url,
+                                executable, 0);
+       fd[0] = data->fd[0];
+       fd[1] = data->fd[1];
+       return 0;
+}
+
 static int disconnect_git(struct transport *transport)
 {
        struct git_transport_data *data = transport->data;
        if (data->conn) {
-               packet_flush(data->fd[1]);
+               if (data->got_remote_heads)
+                       packet_flush(data->fd[1]);
                close(data->fd[0]);
                close(data->fd[1]);
                finish_connect(data->conn);
@@ -792,6 +830,32 @@ static int disconnect_git(struct transport *transport)
        return 0;
 }
 
+void transport_take_over(struct transport *transport,
+                        struct child_process *child)
+{
+       struct git_transport_data *data;
+
+       if (!transport->smart_options)
+               die("Bug detected: Taking over transport requires non-NULL "
+                   "smart_options field.");
+
+       data = xcalloc(1, sizeof(*data));
+       data->options = *transport->smart_options;
+       data->conn = child;
+       data->fd[0] = data->conn->out;
+       data->fd[1] = data->conn->in;
+       data->got_remote_heads = 0;
+       transport->data = data;
+
+       transport->set_option = NULL;
+       transport->get_refs_list = get_refs_via_connect;
+       transport->fetch = fetch_refs_via_pack;
+       transport->push = NULL;
+       transport->push_refs = git_transport_push;
+       transport->disconnect = disconnect_git;
+       transport->smart_options = &(data->options);
+}
+
 static int is_local(const char *url)
 {
        const char *colon = strchr(url, ':');
@@ -808,55 +872,89 @@ static int is_file(const char *url)
        return S_ISREG(buf.st_mode);
 }
 
+static int external_specification_len(const char *url)
+{
+       return strchr(url, ':') - url;
+}
+
 struct transport *transport_get(struct remote *remote, const char *url)
 {
+       const char *helper;
        struct transport *ret = xcalloc(1, sizeof(*ret));
 
+       ret->progress = isatty(2);
+
        if (!remote)
                die("No remote provided to transport_get()");
 
+       ret->got_remote_refs = 0;
        ret->remote = remote;
+       helper = remote->foreign_vcs;
+
+       if (!url && remote->url)
+               url = remote->url[0];
        ret->url = url;
 
-       if (!prefixcmp(url, "rsync:")) {
+       /* maybe it is a foreign URL? */
+       if (url) {
+               const char *p = url;
+
+               while (is_urlschemechar(p == url, *p))
+                       p++;
+               if (!prefixcmp(p, "::"))
+                       helper = xstrndup(url, p - url);
+       }
+
+       if (helper) {
+               transport_helper_init(ret, helper);
+       } else if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
-
-       } else if (!prefixcmp(url, "http://")
-               || !prefixcmp(url, "https://")
-               || !prefixcmp(url, "ftp://")) {
-               transport_helper_init(ret, "curl");
-#ifdef NO_CURL
-               error("git was compiled without libcurl support.");
-#else
-               ret->push = curl_transport_push;
-#endif
-
+               ret->smart_options = NULL;
        } else if (is_local(url) && is_file(url)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->fetch = fetch_refs_from_bundle;
                ret->disconnect = close_bundle;
-
-       } else {
+               ret->smart_options = NULL;
+       } else if (!is_url(url)
+               || !prefixcmp(url, "file://")
+               || !prefixcmp(url, "git://")
+               || !prefixcmp(url, "ssh://")
+               || !prefixcmp(url, "git+ssh://")
+               || !prefixcmp(url, "ssh+git://")) {
+               /* These are builtin smart transports. */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
-               ret->set_option = set_git_option;
+               ret->set_option = NULL;
                ret->get_refs_list = get_refs_via_connect;
                ret->fetch = fetch_refs_via_pack;
                ret->push_refs = git_transport_push;
+               ret->connect = connect_git;
                ret->disconnect = disconnect_git;
+               ret->smart_options = &(data->options);
 
-               data->thin = 1;
                data->conn = NULL;
-               data->uploadpack = "git-upload-pack";
+               data->got_remote_heads = 0;
+       } else {
+               /* Unknown protocol in URL. Pass to external handler. */
+               int len = external_specification_len(url);
+               char *handler = xmalloc(len + 1);
+               handler[len] = 0;
+               strncpy(handler, url, len);
+               transport_helper_init(ret, handler);
+       }
+
+       if (ret->smart_options) {
+               ret->smart_options->thin = 1;
+               ret->smart_options->uploadpack = "git-upload-pack";
                if (remote->uploadpack)
-                       data->uploadpack = remote->uploadpack;
-               data->receivepack = "git-receive-pack";
+                       ret->smart_options->uploadpack = remote->uploadpack;
+               ret->smart_options->receivepack = "git-receive-pack";
                if (remote->receivepack)
-                       data->receivepack = remote->receivepack;
+                       ret->smart_options->receivepack = remote->receivepack;
        }
 
        return ret;
@@ -865,29 +963,68 @@ struct transport *transport_get(struct remote *remote, const char *url)
 int transport_set_option(struct transport *transport,
                         const char *name, const char *value)
 {
+       int git_reports = 1, protocol_reports = 1;
+
+       if (transport->smart_options)
+               git_reports = set_git_option(transport->smart_options,
+                                            name, value);
+
        if (transport->set_option)
-               return transport->set_option(transport, name, value);
+               protocol_reports = transport->set_option(transport, name,
+                                                       value);
+
+       /* If either report is 0, report 0 (success). */
+       if (!git_reports || !protocol_reports)
+               return 0;
+       /* If either reports -1 (invalid value), report -1. */
+       if ((git_reports == -1) || (protocol_reports == -1))
+               return -1;
+       /* Otherwise if both report unknown, report unknown. */
        return 1;
 }
 
+void transport_set_verbosity(struct transport *transport, int verbosity,
+       int force_progress)
+{
+       if (verbosity >= 2)
+               transport->verbose = verbosity <= 3 ? verbosity : 3;
+       if (verbosity < 0)
+               transport->verbose = -1;
+
+       /**
+        * Rules used to determine whether to report progress (processing aborts
+        * when a rule is satisfied):
+        *
+        *   1. Report progress, if force_progress is 1 (ie. --progress).
+        *   2. Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
+        *   3. Report progress if isatty(2) is 1.
+        **/
+       transport->progress = force_progress || (verbosity >= 0 && isatty(2));
+}
+
 int transport_push(struct transport *transport,
                   int refspec_nr, const char **refspec, int flags,
                   int *nonfastforward)
 {
        *nonfastforward = 0;
-       verify_remote_names(refspec_nr, refspec);
+       transport_verify_remote_names(refspec_nr, refspec);
+
+       if (transport->push) {
+               /* Maybe FIXME. But no important transport uses this case. */
+               if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+                       die("This transport does not support using --set-upstream");
 
-       if (transport->push)
                return transport->push(transport, refspec_nr, refspec, flags);
-       if (transport->push_refs) {
+       } else if (transport->push_refs) {
                struct ref *remote_refs =
                        transport->get_refs_list(transport, 1);
                struct ref *local_refs = get_local_heads();
                int match_flags = MATCH_REFS_NONE;
-               int verbose = flags & TRANSPORT_PUSH_VERBOSE;
-               int quiet = flags & TRANSPORT_PUSH_QUIET;
+               int verbose = (transport->verbose > 0);
+               int quiet = (transport->verbose < 0);
                int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
-               int ret;
+               int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
+               int push_ret, ret, err;
 
                if (flags & TRANSPORT_PUSH_ALL)
                        match_flags |= MATCH_REFS_ALL;
@@ -899,21 +1036,33 @@ int transport_push(struct transport *transport,
                        return -1;
                }
 
-               ret = transport->push_refs(transport, remote_refs, flags);
+               set_ref_status_for_push(remote_refs,
+                       flags & TRANSPORT_PUSH_MIRROR,
+                       flags & TRANSPORT_PUSH_FORCE);
 
-               if (!quiet || push_had_errors(remote_refs))
-                       print_push_status(transport->url, remote_refs,
+               push_ret = transport->push_refs(transport, remote_refs, flags);
+               err = push_had_errors(remote_refs);
+               ret = push_ret | err;
+
+               if (!quiet || err)
+                       transport_print_push_status(transport->url, remote_refs,
                                        verbose | porcelain, porcelain,
                                        nonfastforward);
 
+               if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+                       set_upstreams(transport, remote_refs, pretend);
+
                if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
                        struct ref *ref;
                        for (ref = remote_refs; ref; ref = ref->next)
-                               update_tracking_ref(transport->remote, ref, verbose);
+                               transport_update_tracking_ref(transport->remote, ref, verbose);
                }
 
-               if (!quiet && !ret && !refs_pushed(remote_refs))
+               if (porcelain && !push_ret)
+                       puts("Done");
+               else if (!quiet && !ret && !transport_refs_pushed(remote_refs))
                        fprintf(stderr, "Everything up-to-date\n");
+
                return ret;
        }
        return 1;
@@ -921,21 +1070,25 @@ int transport_push(struct transport *transport,
 
 const struct ref *transport_get_remote_refs(struct transport *transport)
 {
-       if (!transport->remote_refs)
+       if (!transport->got_remote_refs) {
                transport->remote_refs = transport->get_refs_list(transport, 0);
+               transport->got_remote_refs = 1;
+       }
+
        return transport->remote_refs;
 }
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs)
+int transport_fetch_refs(struct transport *transport, struct ref *refs)
 {
        int rc;
        int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
-       const struct ref **heads = NULL;
-       const struct ref *rm;
+       struct ref **heads = NULL;
+       struct ref *rm;
 
        for (rm = refs; rm; rm = rm->next) {
                nr_refs++;
                if (rm->peer_ref &&
+                   !is_null_sha1(rm->old_sha1) &&
                    !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
                        continue;
                ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
@@ -956,6 +1109,7 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs)
        }
 
        rc = transport->fetch(transport, nr_heads, heads);
+
        free(heads);
        return rc;
 }
@@ -969,6 +1123,15 @@ void transport_unlock_pack(struct transport *transport)
        }
 }
 
+int transport_connect(struct transport *transport, const char *name,
+                     const char *exec, int fd[2])
+{
+       if (transport->connect)
+               return transport->connect(transport, name, exec, fd);
+       else
+               die("Operation not supported by protocol");
+}
+
 int transport_disconnect(struct transport *transport)
 {
        int ret = 0;