diff --git a/include/git2.h b/include/git2.h index 501128c43..fe8b02103 100644 --- a/include/git2.h +++ b/include/git2.h @@ -40,6 +40,7 @@ #include "git2/remote.h" #include "git2/clone.h" #include "git2/checkout.h" +#include "git2/push.h" #include "git2/attr.h" #include "git2/ignore.h" diff --git a/include/git2/common.h b/include/git2/common.h index dd6909f90..ad23d2d34 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -85,6 +85,11 @@ GIT_BEGIN_DECL */ #define GIT_PATH_MAX 4096 +/** + * The string representation of the null object ID. + */ +#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" + /** * Return the version of the libgit2 library * being currently used. diff --git a/include/git2/errors.h b/include/git2/errors.h index 45e04578d..9dd42f0c4 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -29,6 +29,7 @@ enum { GIT_EBAREREPO = -8, GIT_EORPHANEDHEAD = -9, GIT_EUNMERGED = -10, + GIT_ENONFASTFORWARD = -11, GIT_PASSTHROUGH = -30, GIT_ITEROVER = -31, diff --git a/include/git2/push.h b/include/git2/push.h new file mode 100644 index 000000000..900a1833e --- /dev/null +++ b/include/git2/push.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_push_h__ +#define INCLUDE_git_push_h__ + +#include "common.h" + +/** + * @file git2/push.h + * @brief Git push management functions + * @defgroup git_push push management functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Create a new push object + * + * @param out New push object + * @param remote Remote instance + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_push_new(git_push **out, git_remote *remote); + +/** + * Add a refspec to be pushed + * + * @param push The push object + * @param refspec Refspec string + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_push_add_refspec(git_push *push, const char *refspec); + +/** + * Actually push all given refspecs + * + * @param push The push object + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_push_finish(git_push *push); + +/** + * Check if remote side successfully unpacked + * + * @param push The push object + * + * @return true if equal, false otherwise + */ +GIT_EXTERN(int) git_push_unpack_ok(git_push *push); + +/** + * Call callback `cb' on each status + * + * @param push The push object + * @param cb The callback to call on each object + * + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_push_status_foreach(git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data); + +/** + * Free the given push object + * + * @param push The push object + */ +GIT_EXTERN(void) git_push_free(git_push *push); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/transport.h b/include/git2/transport.h index 5a27d7f97..61726922f 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -9,6 +9,7 @@ #include "indexer.h" #include "net.h" +#include "types.h" /** * @file git2/transport.h @@ -102,8 +103,8 @@ typedef struct git_transport { git_headlist_cb list_cb, void *payload); - /* Reserved until push is implemented. */ - int (*push)(struct git_transport *transport); + /* Executes the push whose context is in the git_push object. */ + int (*push)(struct git_transport *transport, git_push *push); /* This function may be called after a successful call to connect(), when * the direction is FETCH. The function performs a negotiation to calculate @@ -123,7 +124,7 @@ typedef struct git_transport { void *progress_payload); /* Checks to see if the transport is connected */ - int (*is_connected)(struct git_transport *transport, int *connected); + int (*is_connected)(struct git_transport *transport); /* Reads the flags value previously passed into connect() */ int (*read_flags)(struct git_transport *transport, int *flags); @@ -145,10 +146,11 @@ typedef struct git_transport { * git:// or http://) and a transport object is returned to the caller. * * @param out The newly created transport (out) + * @param owner The git_remote which will own this transport * @param url The URL to connect to * @return 0 or an error code */ -GIT_EXTERN(int) git_transport_new(git_transport **out, const char *url); +GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const char *url); /** * Function which checks to see if a transport could be created for the @@ -161,7 +163,7 @@ GIT_EXTERN(int) git_transport_new(git_transport **out, const char *url); GIT_EXTERN(int) git_transport_valid_url(const char *url); /* Signature of a function which creates a transport */ -typedef int (*git_transport_cb)(git_transport **out, void *payload); +typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param); /* Transports which come with libgit2 (match git_transport_cb). The expected * value for "param" is listed in-line below. */ @@ -169,34 +171,40 @@ typedef int (*git_transport_cb)(git_transport **out, void *payload); /** * Create an instance of the dummy transport. * - * @param transport The newly created transport (out) + * @param out The newly created transport (out) + * @param owner The git_remote which will own this transport * @param payload You must pass NULL for this parameter. * @return 0 or an error code */ GIT_EXTERN(int) git_transport_dummy( - git_transport **transport, + git_transport **out, + git_remote *owner, /* NULL */ void *payload); /** * Create an instance of the local transport. * - * @param transport The newly created transport (out) + * @param out The newly created transport (out) + * @param owner The git_remote which will own this transport * @param payload You must pass NULL for this parameter. * @return 0 or an error code */ GIT_EXTERN(int) git_transport_local( - git_transport **transport, + git_transport **out, + git_remote *owner, /* NULL */ void *payload); /** * Create an instance of the smart transport. * - * @param transport The newly created transport (out) + * @param out The newly created transport (out) + * @param owner The git_remote which will own this transport * @param payload A pointer to a git_smart_subtransport_definition * @return 0 or an error code */ GIT_EXTERN(int) git_transport_smart( - git_transport **transport, + git_transport **out, + git_remote *owner, /* (git_smart_subtransport_definition *) */ void *payload); /* @@ -221,6 +229,8 @@ GIT_EXTERN(int) git_transport_smart( typedef enum { GIT_SERVICE_UPLOADPACK_LS = 1, GIT_SERVICE_UPLOADPACK = 2, + GIT_SERVICE_RECEIVEPACK_LS = 3, + GIT_SERVICE_RECEIVEPACK = 4, } git_smart_service_t; struct git_smart_subtransport; @@ -255,6 +265,14 @@ typedef struct git_smart_subtransport { const char *url, git_smart_service_t action); + /* Subtransports are guaranteed a call to close() between + * calls to action(), except for the following two "natural" progressions + * of actions against a constant URL. + * + * 1. UPLOADPACK_LS -> UPLOADPACK + * 2. RECEIVEPACK_LS -> RECEIVEPACK */ + int (* close)(struct git_smart_subtransport *transport); + void (* free)(struct git_smart_subtransport *transport); } git_smart_subtransport; diff --git a/include/git2/types.h b/include/git2/types.h index 11bcf270f..06fcf3613 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -187,6 +187,7 @@ typedef enum { typedef struct git_refspec git_refspec; typedef struct git_remote git_remote; +typedef struct git_push git_push; typedef struct git_remote_head git_remote_head; typedef struct git_remote_callbacks git_remote_callbacks; diff --git a/src/object.c b/src/object.c index 392fd80a8..f88c2ba50 100644 --- a/src/object.c +++ b/src/object.c @@ -373,3 +373,4 @@ int git_object_peel( git_object_free(deref); return -1; } + diff --git a/src/pack-objects.c b/src/pack-objects.c index 008d8f288..44ad3fd98 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -604,12 +604,6 @@ on_error: return -1; } -static int send_pack_file(void *buf, size_t size, void *data) -{ - gitno_socket *s = (gitno_socket *)data; - return gitno_send(s, buf, size, 0); -} - static int write_pack_buf(void *buf, size_t size, void *data) { git_buf *b = (git_buf *)data; @@ -1233,12 +1227,6 @@ static int prepare_pack(git_packbuilder *pb) #define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; } -int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s) -{ - PREPARE_PACK; - return write_pack(pb, &send_pack_file, s); -} - int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) { PREPARE_PACK; @@ -1264,6 +1252,10 @@ static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *pay git_packbuilder *pb = payload; git_buf buf = GIT_BUF_INIT; + /* A commit inside a tree represents a submodule commit and should be skipped. */ + if(git_tree_entry_type(entry) == GIT_OBJ_COMMIT) + return 0; + git_buf_puts(&buf, root); git_buf_puts(&buf, git_tree_entry_name(entry)); diff --git a/src/pack-objects.h b/src/pack-objects.h index e34cc2754..70ee72ce9 100644 --- a/src/pack-objects.h +++ b/src/pack-objects.h @@ -82,7 +82,6 @@ struct git_packbuilder { bool done; }; -int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s); int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb); #endif /* INCLUDE_pack_objects_h__ */ diff --git a/src/push.c b/src/push.c new file mode 100644 index 000000000..1d63d574e --- /dev/null +++ b/src/push.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2.h" + +#include "common.h" +#include "pack.h" +#include "pack-objects.h" +#include "remote.h" +#include "vector.h" +#include "push.h" + +int git_push_new(git_push **out, git_remote *remote) +{ + git_push *p; + + *out = NULL; + + p = git__calloc(1, sizeof(*p)); + GITERR_CHECK_ALLOC(p); + + p->repo = remote->repo; + p->remote = remote; + p->report_status = 1; + + if (git_vector_init(&p->specs, 0, NULL) < 0) { + git__free(p); + return -1; + } + + if (git_vector_init(&p->status, 0, NULL) < 0) { + git_vector_free(&p->specs); + git__free(p); + return -1; + } + + *out = p; + return 0; +} + +static void free_refspec(push_spec *spec) +{ + if (spec == NULL) + return; + + if (spec->lref) + git__free(spec->lref); + + if (spec->rref) + git__free(spec->rref); + + git__free(spec); +} + +static void free_status(push_status *status) +{ + if (status == NULL) + return; + + if (status->msg) + git__free(status->msg); + + git__free(status->ref); + git__free(status); +} + +static int check_ref(char *ref) +{ + if (strcmp(ref, "HEAD") && + git__prefixcmp(ref, "refs/heads/") && + git__prefixcmp(ref, "refs/tags/")) { + giterr_set(GITERR_INVALID, "No valid reference '%s'", ref); + return -1; + } + return 0; +} + +static int parse_refspec(push_spec **spec, const char *str) +{ + push_spec *s; + char *delim; + + *spec = NULL; + + s = git__calloc(1, sizeof(*s)); + GITERR_CHECK_ALLOC(s); + + if (str[0] == '+') { + s->force = true; + str++; + } + +#define check(ref) \ + if (!ref || check_ref(ref) < 0) goto on_error + + delim = strchr(str, ':'); + if (delim == NULL) { + s->lref = git__strdup(str); + check(s->lref); + s->rref = NULL; + } else { + if (delim - str) { + s->lref = git__strndup(str, delim - str); + check(s->lref); + } else + s->lref = NULL; + + if (strlen(delim + 1)) { + s->rref = git__strdup(delim + 1); + check(s->rref); + } else + s->rref = NULL; + } + + if (!s->lref && !s->rref) + goto on_error; + +#undef check + + *spec = s; + return 0; + +on_error: + free_refspec(s); + return -1; +} + +int git_push_add_refspec(git_push *push, const char *refspec) +{ + push_spec *spec; + + if (parse_refspec(&spec, refspec) < 0 || + git_vector_insert(&push->specs, spec) < 0) + return -1; + + return 0; +} + +static int revwalk(git_vector *commits, git_push *push) +{ + git_remote_head *head; + push_spec *spec; + git_revwalk *rw; + git_oid oid; + unsigned int i; + int error = -1; + + if (git_revwalk_new(&rw, push->repo) < 0) + return -1; + + git_revwalk_sorting(rw, GIT_SORT_TIME); + + git_vector_foreach(&push->specs, i, spec) { + if (git_oid_iszero(&spec->loid)) + /* + * Delete reference on remote side; + * nothing to do here. + */ + continue; + + if (git_oid_equal(&spec->loid, &spec->roid)) + continue; /* up-to-date */ + + if (git_revwalk_push(rw, &spec->loid) < 0) + goto on_error; + + if (!spec->force) { + git_oid base; + + if (git_oid_iszero(&spec->roid)) + continue; + + if (!git_odb_exists(push->repo->_odb, &spec->roid)) { + giterr_clear(); + error = GIT_ENONFASTFORWARD; + goto on_error; + } + + error = git_merge_base(&base, push->repo, + &spec->loid, &spec->roid); + + if (error == GIT_ENOTFOUND || + (!error && !git_oid_equal(&base, &spec->roid))) { + giterr_clear(); + error = GIT_ENONFASTFORWARD; + goto on_error; + } + + if (error < 0) + goto on_error; + } + } + + git_vector_foreach(&push->remote->refs, i, head) { + if (git_oid_iszero(&head->oid)) + continue; + + /* TODO */ + git_revwalk_hide(rw, &head->oid); + } + + while ((error = git_revwalk_next(&oid, rw)) == 0) { + git_oid *o = git__malloc(GIT_OID_RAWSZ); + GITERR_CHECK_ALLOC(o); + git_oid_cpy(o, &oid); + if (git_vector_insert(commits, o) < 0) { + error = -1; + goto on_error; + } + } + +on_error: + git_revwalk_free(rw); + return error == GIT_ITEROVER ? 0 : error; +} + +static int queue_objects(git_push *push) +{ + git_vector commits; + git_oid *o; + unsigned int i; + int error; + + if (git_vector_init(&commits, 0, NULL) < 0) + return -1; + + if ((error = revwalk(&commits, push)) < 0) + goto on_error; + + if (!commits.length) { + git_vector_free(&commits); + return 0; /* nothing to do */ + } + + git_vector_foreach(&commits, i, o) { + if ((error = git_packbuilder_insert(push->pb, o, NULL)) < 0) + goto on_error; + } + + git_vector_foreach(&commits, i, o) { + git_object *obj; + + if ((error = git_object_lookup(&obj, push->repo, o, GIT_OBJ_ANY)) < 0) + goto on_error; + + switch (git_object_type(obj)) { + case GIT_OBJ_TAG: /* TODO: expect tags */ + case GIT_OBJ_COMMIT: + if ((error = git_packbuilder_insert_tree(push->pb, + git_commit_tree_id((git_commit *)obj))) < 0) { + git_object_free(obj); + goto on_error; + } + break; + case GIT_OBJ_TREE: + case GIT_OBJ_BLOB: + default: + git_object_free(obj); + giterr_set(GITERR_INVALID, "Given object type invalid"); + error = -1; + goto on_error; + } + git_object_free(obj); + } + error = 0; + +on_error: + git_vector_foreach(&commits, i, o) { + git__free(o); + } + git_vector_free(&commits); + return error; +} + +static int calculate_work(git_push *push) +{ + git_remote_head *head; + push_spec *spec; + unsigned int i, j; + + git_vector_foreach(&push->specs, i, spec) { + if (spec->lref) { + if (git_reference_name_to_id( + &spec->loid, push->repo, spec->lref) < 0) { + giterr_set(GIT_ENOTFOUND, "No such reference '%s'", spec->lref); + return -1; + } + + if (!spec->rref) { + /* + * No remote reference given; if we find a remote + * reference with the same name we will update it, + * otherwise a new reference will be created. + */ + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->lref, head->name)) { + /* + * Update remote reference + */ + git_oid_cpy(&spec->roid, &head->oid); + + break; + } + } + } else { + /* + * Remote reference given; update the given + * reference or create it. + */ + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->rref, head->name)) { + /* + * Update remote reference + */ + git_oid_cpy(&spec->roid, &head->oid); + + break; + } + } + } + } + } + + return 0; +} + +static int do_push(git_push *push) +{ + int error; + git_transport *transport = push->remote->transport; + + /* + * A pack-file MUST be sent if either create or update command + * is used, even if the server already has all the necessary + * objects. In this case the client MUST send an empty pack-file. + */ + + if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0 || + (error = calculate_work(push)) < 0 || + (error = queue_objects(push)) < 0 || + (error = transport->push(transport, push)) < 0) + goto on_error; + + error = 0; + +on_error: + git_packbuilder_free(push->pb); + return error; +} + +static int cb_filter_refs(git_remote_head *ref, void *data) +{ + git_remote *remote = (git_remote *) data; + return git_vector_insert(&remote->refs, ref); +} + +static int filter_refs(git_remote *remote) +{ + git_vector_clear(&remote->refs); + return git_remote_ls(remote, cb_filter_refs, remote); +} + +int git_push_finish(git_push *push) +{ + int error; + + if (!git_remote_connected(push->remote) && + (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH)) < 0) + return error; + + if ((error = filter_refs(push->remote)) < 0 || + (error = do_push(push)) < 0) + return error; + + return 0; +} + +int git_push_unpack_ok(git_push *push) +{ + return push->unpack_ok; +} + +int git_push_status_foreach(git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data) +{ + push_status *status; + unsigned int i; + + git_vector_foreach(&push->status, i, status) { + if (cb(status->ref, status->msg, data) < 0) + return GIT_EUSER; + } + + return 0; +} + +void git_push_free(git_push *push) +{ + push_spec *spec; + push_status *status; + unsigned int i; + + if (push == NULL) + return; + + git_vector_foreach(&push->specs, i, spec) { + free_refspec(spec); + } + git_vector_free(&push->specs); + + git_vector_foreach(&push->status, i, status) { + free_status(status); + } + git_vector_free(&push->status); + + git__free(push); +} diff --git a/src/push.h b/src/push.h new file mode 100644 index 000000000..1a2fb0260 --- /dev/null +++ b/src/push.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_push_h__ +#define INCLUDE_push_h__ + +#include "git2.h" + +typedef struct push_spec { + char *lref; + char *rref; + + git_oid loid; + git_oid roid; + + bool force; +} push_spec; + +typedef struct push_status { + bool ok; + + char *ref; + char *msg; +} push_status; + +struct git_push { + git_repository *repo; + git_packbuilder *pb; + git_remote *remote; + git_vector specs; + bool report_status; + + /* report-status */ + bool unpack_ok; + git_vector status; +}; + +#endif diff --git a/src/reflog.h b/src/reflog.h index 3bbdf6e10..749cbc688 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -17,8 +17,6 @@ #define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) -#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" - struct git_reflog_entry { git_oid oid_old; git_oid oid_cur; diff --git a/src/remote.c b/src/remote.c index bdec3c1f4..c84911aa1 100644 --- a/src/remote.c +++ b/src/remote.c @@ -488,13 +488,13 @@ int git_remote_connect(git_remote *remote, git_direction direction) /* A transport could have been supplied in advance with * git_remote_set_transport */ - if (!t && git_transport_new(&t, url) < 0) + if (!t && git_transport_new(&t, remote, url) < 0) return -1; if (t->set_callbacks && t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.payload) < 0) goto on_error; - + if (!remote->check_cert) flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT; @@ -507,6 +507,10 @@ int git_remote_connect(git_remote *remote, git_direction direction) on_error: t->free(t); + + if (t == remote->transport) + remote->transport = NULL; + return -1; } @@ -514,7 +518,7 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) { assert(remote); - if (!remote->transport) { + if (!git_remote_connected(remote)) { giterr_set(GITERR_NET, "The remote is not connected"); return -1; } @@ -522,6 +526,63 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) return remote->transport->ls(remote->transport, list_cb, payload); } +int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url) +{ + git_config *cfg; + const char *val; + + assert(remote); + + if (!proxy_url) + return -1; + + *proxy_url = NULL; + + if (git_repository_config__weakptr(&cfg, remote->repo) < 0) + return -1; + + /* Go through the possible sources for proxy configuration, from most specific + * to least specific. */ + + /* remote..proxy config setting */ + if (remote->name && 0 != *(remote->name)) { + git_buf buf = GIT_BUF_INIT; + + if (git_buf_printf(&buf, "remote.%s.proxy", remote->name) < 0) + return -1; + + if (!git_config_get_string(&val, cfg, git_buf_cstr(&buf)) && + val && ('\0' != *val)) { + git_buf_free(&buf); + + *proxy_url = git__strdup(val); + GITERR_CHECK_ALLOC(*proxy_url); + return 0; + } + + git_buf_free(&buf); + } + + /* http.proxy config setting */ + if (!git_config_get_string(&val, cfg, "http.proxy") && + val && ('\0' != *val)) { + *proxy_url = git__strdup(val); + GITERR_CHECK_ALLOC(*proxy_url); + return 0; + } + + /* HTTP_PROXY / HTTPS_PROXY environment variables */ + val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY"); + + if (val && ('\0' != *val)) { + *proxy_url = git__strdup(val); + GITERR_CHECK_ALLOC(*proxy_url); + return 0; + } + + return 0; +} + int git_remote_download( git_remote *remote, git_transfer_progress_callback progress_cb, @@ -687,7 +748,7 @@ int git_remote_update_tips(git_remote *remote) git_vector_init(&update_heads, 16, NULL) < 0) return -1; - if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0) + if (git_remote_ls(remote, update_tips_callback, &refs) < 0) goto on_error; /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */ @@ -779,22 +840,20 @@ on_error: int git_remote_connected(git_remote *remote) { - int connected; - assert(remote); if (!remote->transport || !remote->transport->is_connected) return 0; /* Ask the transport if it's connected. */ - remote->transport->is_connected(remote->transport, &connected); - - return connected; + return remote->transport->is_connected(remote->transport); } void git_remote_stop(git_remote *remote) { - if (remote->transport->cancel) + assert(remote); + + if (remote->transport && remote->transport->cancel) remote->transport->cancel(remote->transport); } diff --git a/src/remote.h b/src/remote.h index 448a9e9a9..06f712fbc 100644 --- a/src/remote.h +++ b/src/remote.h @@ -34,5 +34,6 @@ struct git_remote { }; const char* git_remote__urlfordirection(struct git_remote *remote, int direction); +int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url); #endif diff --git a/src/transport.c b/src/transport.c index 8c242af6d..9c88d983e 100644 --- a/src/transport.c +++ b/src/transport.c @@ -76,15 +76,16 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * * Public API * **************/ -int git_transport_dummy(git_transport **transport, void *param) +int git_transport_dummy(git_transport **transport, git_remote *owner, void *param) { GIT_UNUSED(transport); + GIT_UNUSED(owner); GIT_UNUSED(param); giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); return -1; } -int git_transport_new(git_transport **out, const char *url) +int git_transport_new(git_transport **out, git_remote *owner, const char *url) { git_transport_cb fn; git_transport *transport; @@ -96,7 +97,7 @@ int git_transport_new(git_transport **out, const char *url) return -1; } - error = fn(&transport, param); + error = fn(&transport, owner, param); if (error < 0) return error; diff --git a/src/transports/cred.c b/src/transports/cred.c index e137ca9ac..8c97924e4 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -34,7 +34,7 @@ int git_cred_userpass_plaintext_new( if (!cred) return -1; - c = (git_cred_userpass_plaintext *)git__malloc(sizeof(git_cred_userpass_plaintext)); + c = git__malloc(sizeof(git_cred_userpass_plaintext)); GITERR_CHECK_ALLOC(c); c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT; diff --git a/src/transports/git.c b/src/transports/git.c index a895c1389..e8a7bde36 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -13,6 +13,7 @@ static const char prefix_git[] = "git://"; static const char cmd_uploadpack[] = "git-upload-pack"; +static const char cmd_receivepack[] = "git-receive-pack"; typedef struct { git_smart_subtransport_stream parent; @@ -153,7 +154,7 @@ static int git_stream_alloc( if (!stream) return -1; - s = (git_stream *)git__calloc(sizeof(git_stream), 1); + s = git__calloc(sizeof(git_stream), 1); GITERR_CHECK_ALLOC(s); s->parent.subtransport = &t->parent; @@ -173,7 +174,7 @@ static int git_stream_alloc( return 0; } -static int git_git_uploadpack_ls( +static int _git_uploadpack_ls( git_subtransport *t, const char *url, git_smart_subtransport_stream **stream) @@ -211,7 +212,7 @@ on_error: return -1; } -static int git_git_uploadpack( +static int _git_uploadpack( git_subtransport *t, const char *url, git_smart_subtransport_stream **stream) @@ -227,29 +228,100 @@ static int git_git_uploadpack( return -1; } +static int _git_receivepack_ls( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + char *host, *port; + git_stream *s; + + *stream = NULL; + + if (!git__prefixcmp(url, prefix_git)) + url += strlen(prefix_git); + + if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0) + return -1; + + s = (git_stream *)*stream; + + if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) + goto on_error; + + if (gitno_connect(&s->socket, host, port, 0) < 0) + goto on_error; + + t->current_stream = s; + git__free(host); + git__free(port); + return 0; + +on_error: + if (*stream) + git_stream_free(*stream); + + git__free(host); + git__free(port); + return -1; +} + +static int _git_receivepack( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK"); + return -1; +} + static int _git_action( git_smart_subtransport_stream **stream, - git_smart_subtransport *smart_transport, + git_smart_subtransport *subtransport, const char *url, git_smart_service_t action) { - git_subtransport *t = (git_subtransport *) smart_transport; + git_subtransport *t = (git_subtransport *) subtransport; switch (action) { case GIT_SERVICE_UPLOADPACK_LS: - return git_git_uploadpack_ls(t, url, stream); + return _git_uploadpack_ls(t, url, stream); case GIT_SERVICE_UPLOADPACK: - return git_git_uploadpack(t, url, stream); + return _git_uploadpack(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK_LS: + return _git_receivepack_ls(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK: + return _git_receivepack(t, url, stream); } *stream = NULL; return -1; } -static void _git_free(git_smart_subtransport *smart_transport) +static int _git_close(git_smart_subtransport *subtransport) { - git_subtransport *t = (git_subtransport *) smart_transport; + git_subtransport *t = (git_subtransport *) subtransport; + + assert(!t->current_stream); + + GIT_UNUSED(t); + + return 0; +} + +static void _git_free(git_smart_subtransport *subtransport) +{ + git_subtransport *t = (git_subtransport *) subtransport; assert(!t->current_stream); @@ -263,11 +335,12 @@ int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owne if (!out) return -1; - t = (git_subtransport *)git__calloc(sizeof(git_subtransport), 1); + t = git__calloc(sizeof(git_subtransport), 1); GITERR_CHECK_ALLOC(t); t->owner = owner; t->parent.action = _git_action; + t->parent.close = _git_close; t->parent.free = _git_free; *out = (git_smart_subtransport *) t; diff --git a/src/transports/http.c b/src/transports/http.c index ba4d8746f..02f749262 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -17,6 +17,9 @@ static const char *prefix_https = "https://"; static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; static const char *upload_pack_service_url = "/git-upload-pack"; +static const char *receive_pack_service = "receive-pack"; +static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack"; +static const char *receive_pack_service_url = "/git-receive-pack"; static const char *get_verb = "GET"; static const char *post_verb = "POST"; static const char *basic_authtype = "Basic"; @@ -26,6 +29,8 @@ static const char *basic_authtype = "Basic"; #define PARSE_ERROR_GENERIC -1 #define PARSE_ERROR_REPLAY -2 +#define CHUNK_SIZE 4096 + enum last_cb { NONE, FIELD, @@ -41,7 +46,11 @@ typedef struct { const char *service; const char *service_url; const char *verb; - unsigned sent_request : 1; + char *chunk_buffer; + unsigned chunk_buffer_len; + unsigned sent_request : 1, + received_response : 1, + chunked : 1; } http_stream; typedef struct { @@ -106,33 +115,33 @@ on_error: static int gen_request( git_buf *buf, - const char *path, - const char *host, - git_cred *cred, - http_authmechanism_t auth_mechanism, - const char *op, - const char *service, - const char *service_url, - ssize_t content_length) + http_stream *s, + size_t content_length) { - if (!path) - path = "/"; + http_subtransport *t = OWNING_SUBTRANSPORT(s); - git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url); + if (!t->path) + t->path = "/"; + + git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url); git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); - git_buf_printf(buf, "Host: %s\r\n", host); - if (content_length > 0) { - git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service); - git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service); - git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length); - } else { + git_buf_printf(buf, "Host: %s\r\n", t->host); + + if (s->chunked || content_length > 0) { + git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service); + git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service); + + if (s->chunked) + git_buf_puts(buf, "Transfer-Encoding: chunked\r\n"); + else + git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length); + } else git_buf_puts(buf, "Accept: */*\r\n"); - } /* Apply credentials to the request */ - if (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && - auth_mechanism == GIT_HTTP_AUTH_BASIC && - apply_basic_credential(buf, cred) < 0) + if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && + t->auth_mechanism == GIT_HTTP_AUTH_BASIC && + apply_basic_credential(buf, t->cred) < 0) return -1; git_buf_puts(buf, "\r\n"); @@ -168,7 +177,7 @@ static int on_header_ready(http_subtransport *t) git_buf *value = &t->parse_header_value; char *dup; - if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) { + if (!t->content_type && !strcasecmp("Content-Type", git_buf_cstr(name))) { t->content_type = git__strdup(git_buf_cstr(value)); GITERR_CHECK_ALLOC(t->content_type); } @@ -355,6 +364,34 @@ static void clear_parser_state(http_subtransport *t) git_vector_free(&t->www_authenticate); } +static int write_chunk(gitno_socket *socket, const char *buffer, size_t len) +{ + git_buf buf = GIT_BUF_INIT; + + /* Chunk header */ + git_buf_printf(&buf, "%X\r\n", (unsigned)len); + + if (git_buf_oom(&buf)) + return -1; + + if (gitno_send(socket, buf.ptr, buf.size, 0) < 0) { + git_buf_free(&buf); + return -1; + } + + git_buf_free(&buf); + + /* Chunk body */ + if (len > 0 && gitno_send(socket, buffer, len, 0) < 0) + return -1; + + /* Chunk footer */ + if (gitno_send(socket, "\r\n", 2, 0) < 0) + return -1; + + return 0; +} + static int http_stream_read( git_smart_subtransport_stream *stream, char *buffer, @@ -363,8 +400,8 @@ static int http_stream_read( { http_stream *s = (http_stream *)stream; http_subtransport *t = OWNING_SUBTRANSPORT(s); - git_buf request = GIT_BUF_INIT; parser_context ctx; + size_t bytes_parsed; replay: *bytes_read = 0; @@ -372,11 +409,11 @@ replay: assert(t->connected); if (!s->sent_request) { + git_buf request = GIT_BUF_INIT; + clear_parser_state(t); - if (gen_request(&request, t->path, t->host, - t->cred, t->auth_mechanism, s->verb, - s->service, s->service_url, 0) < 0) { + if (gen_request(&request, s, 0) < 0) { giterr_set(GITERR_NET, "Failed to generate request"); return -1; } @@ -387,52 +424,151 @@ replay: } git_buf_free(&request); + s->sent_request = 1; } - t->parse_buffer.offset = 0; + if (!s->received_response) { + if (s->chunked) { + assert(s->verb == post_verb); - if (t->parse_finished) - return 0; + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0 && + write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; - if (gitno_recv(&t->parse_buffer) < 0) - return -1; + s->chunk_buffer_len = 0; - /* This call to http_parser_execute will result in invocations of the on_* - * family of callbacks. The most interesting of these is - * on_body_fill_buffer, which is called when data is ready to be copied - * into the target buffer. We need to marshal the buffer, buf_size, and - * bytes_read parameters to this callback. */ - ctx.t = t; - ctx.s = s; - ctx.buffer = buffer; - ctx.buf_size = buf_size; - ctx.bytes_read = bytes_read; + /* Write the final chunk. */ + if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0) + return -1; + } - /* Set the context, call the parser, then unset the context. */ - t->parser.data = &ctx; - - http_parser_execute(&t->parser, - &t->settings, - t->parse_buffer.data, - t->parse_buffer.offset); - - t->parser.data = NULL; - - /* If there was a handled authentication failure, then parse_error - * will have signaled us that we should replay the request. */ - if (PARSE_ERROR_REPLAY == t->parse_error) { - s->sent_request = 0; - goto replay; + s->received_response = 1; } - if (t->parse_error < 0) - return -1; + while (!*bytes_read && !t->parse_finished) { + t->parse_buffer.offset = 0; + + if (gitno_recv(&t->parse_buffer) < 0) + return -1; + + /* This call to http_parser_execute will result in invocations of the + * on_* family of callbacks. The most interesting of these is + * on_body_fill_buffer, which is called when data is ready to be copied + * into the target buffer. We need to marshal the buffer, buf_size, and + * bytes_read parameters to this callback. */ + ctx.t = t; + ctx.s = s; + ctx.buffer = buffer; + ctx.buf_size = buf_size; + ctx.bytes_read = bytes_read; + + /* Set the context, call the parser, then unset the context. */ + t->parser.data = &ctx; + + bytes_parsed = http_parser_execute(&t->parser, + &t->settings, + t->parse_buffer.data, + t->parse_buffer.offset); + + t->parser.data = NULL; + + /* If there was a handled authentication failure, then parse_error + * will have signaled us that we should replay the request. */ + if (PARSE_ERROR_REPLAY == t->parse_error) { + s->sent_request = 0; + goto replay; + } + + if (t->parse_error < 0) + return -1; + + if (bytes_parsed != t->parse_buffer.offset) { + giterr_set(GITERR_NET, + "HTTP parser error: %s", + http_errno_description((enum http_errno)t->parser.http_errno)); + return -1; + } + } return 0; } -static int http_stream_write( +static int http_stream_write_chunked( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + http_stream *s = (http_stream *)stream; + http_subtransport *t = OWNING_SUBTRANSPORT(s); + + assert(t->connected); + + /* Send the request, if necessary */ + if (!s->sent_request) { + git_buf request = GIT_BUF_INIT; + + clear_parser_state(t); + + if (gen_request(&request, s, 0) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } + + if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) { + git_buf_free(&request); + return -1; + } + + git_buf_free(&request); + + s->sent_request = 1; + } + + if (len > CHUNK_SIZE) { + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0) { + if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + } + + /* Write chunk directly */ + if (write_chunk(&t->socket, buffer, len) < 0) + return -1; + } + else { + /* Append as much to the buffer as we can */ + int count = min(CHUNK_SIZE - s->chunk_buffer_len, len); + + if (!s->chunk_buffer) + s->chunk_buffer = git__malloc(CHUNK_SIZE); + + memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count); + s->chunk_buffer_len += count; + buffer += count; + len -= count; + + /* Is the buffer full? If so, then flush */ + if (CHUNK_SIZE == s->chunk_buffer_len) { + if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + + if (len > 0) { + memcpy(s->chunk_buffer, buffer, len); + s->chunk_buffer_len = len; + } + } + } + + return 0; +} + +static int http_stream_write_single( git_smart_subtransport_stream *stream, const char *buffer, size_t len) @@ -443,30 +579,27 @@ static int http_stream_write( assert(t->connected); - /* Since we have to write the Content-Length header up front, we're - * basically limited to a single call to write() per request. */ - assert(!s->sent_request); - - if (!s->sent_request) { - clear_parser_state(t); - - if (gen_request(&request, t->path, t->host, - t->cred, t->auth_mechanism, s->verb, - s->service, s->service_url, len) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); - return -1; - } - - if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) - goto on_error; - - if (len && gitno_send(&t->socket, buffer, len, 0) < 0) - goto on_error; - - git_buf_free(&request); - s->sent_request = 1; + if (s->sent_request) { + giterr_set(GITERR_NET, "Subtransport configured for only one write"); + return -1; } + clear_parser_state(t); + + if (gen_request(&request, s, len) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } + + if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) + goto on_error; + + if (len && gitno_send(&t->socket, buffer, len, 0) < 0) + goto on_error; + + git_buf_free(&request); + s->sent_request = 1; + return 0; on_error: @@ -478,6 +611,9 @@ static void http_stream_free(git_smart_subtransport_stream *stream) { http_stream *s = (http_stream *)stream; + if (s->chunk_buffer) + git__free(s->chunk_buffer); + git__free(s); } @@ -489,12 +625,12 @@ static int http_stream_alloc(http_subtransport *t, if (!stream) return -1; - s = (http_stream *)git__calloc(sizeof(http_stream), 1); + s = git__calloc(sizeof(http_stream), 1); GITERR_CHECK_ALLOC(s); s->parent.subtransport = &t->parent; s->parent.read = http_stream_read; - s->parent.write = http_stream_write; + s->parent.write = http_stream_write_single; s->parent.free = http_stream_free; *stream = (git_smart_subtransport_stream *)s; @@ -537,14 +673,54 @@ static int http_uploadpack( return 0; } +static int http_receivepack_ls( + http_subtransport *t, + git_smart_subtransport_stream **stream) +{ + http_stream *s; + + if (http_stream_alloc(t, stream) < 0) + return -1; + + s = (http_stream *)*stream; + + s->service = receive_pack_service; + s->service_url = receive_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int http_receivepack( + http_subtransport *t, + git_smart_subtransport_stream **stream) +{ + http_stream *s; + + if (http_stream_alloc(t, stream) < 0) + return -1; + + s = (http_stream *)*stream; + + /* Use Transfer-Encoding: chunked for this request */ + s->chunked = 1; + s->parent.write = http_stream_write_chunked; + + s->service = receive_pack_service; + s->service_url = receive_pack_service_url; + s->verb = post_verb; + + return 0; +} + static int http_action( git_smart_subtransport_stream **stream, - git_smart_subtransport *smart_transport, + git_smart_subtransport *subtransport, const char *url, git_smart_service_t action) { - http_subtransport *t = (http_subtransport *)smart_transport; - const char *default_port; + http_subtransport *t = (http_subtransport *)subtransport; + const char *default_port = NULL; int flags = 0, ret; if (!stream) @@ -562,6 +738,9 @@ static int http_action( t->use_ssl = 1; } + if (!default_port) + return -1; + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) return ret; @@ -569,7 +748,13 @@ static int http_action( t->path = strchr(url, '/'); } - if (!t->connected || !http_should_keep_alive(&t->parser)) { + if (!t->connected || + !http_should_keep_alive(&t->parser) || + !http_body_is_final(&t->parser)) { + + if (t->socket.socket) + gitno_close(&t->socket); + if (t->use_ssl) { int transport_flags; @@ -588,9 +773,6 @@ static int http_action( t->connected = 1; } - t->parse_finished = 0; - t->parse_error = 0; - switch (action) { case GIT_SERVICE_UPLOADPACK_LS: @@ -598,28 +780,53 @@ static int http_action( case GIT_SERVICE_UPLOADPACK: return http_uploadpack(t, stream); + + case GIT_SERVICE_RECEIVEPACK_LS: + return http_receivepack_ls(t, stream); + + case GIT_SERVICE_RECEIVEPACK: + return http_receivepack(t, stream); } *stream = NULL; return -1; } -static void http_free(git_smart_subtransport *smart_transport) +static int http_close(git_smart_subtransport *subtransport) { - http_subtransport *t = (http_subtransport *) smart_transport; + http_subtransport *t = (http_subtransport *) subtransport; clear_parser_state(t); - if (t->socket.socket) + if (t->socket.socket) { gitno_close(&t->socket); + memset(&t->socket, 0x0, sizeof(gitno_socket)); + } if (t->cred) { t->cred->free(t->cred); t->cred = NULL; } - git__free(t->host); - git__free(t->port); + if (t->host) { + git__free(t->host); + t->host = NULL; + } + + if (t->port) { + git__free(t->port); + t->port = NULL; + } + + return 0; +} + +static void http_free(git_smart_subtransport *subtransport) +{ + http_subtransport *t = (http_subtransport *) subtransport; + + http_close(subtransport); + git__free(t); } @@ -630,11 +837,12 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own if (!out) return -1; - t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1); + t = git__calloc(sizeof(http_subtransport), 1); GITERR_CHECK_ALLOC(t); t->owner = (transport_smart *)owner; t->parent.action = http_action; + t->parent.close = http_close; t->parent.free = http_free; t->settings.on_header_field = on_header_field; diff --git a/src/transports/local.c b/src/transports/local.c index 51544416d..62e8024b5 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -26,6 +26,7 @@ typedef struct { git_transport parent; + git_remote *owner; char *url; int direction; int flags; @@ -42,7 +43,7 @@ static int add_ref(transport_local *t, const char *name) git_object *obj = NULL, *target = NULL; git_buf buf = GIT_BUF_INIT; - head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head)); + head = git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); head->name = git__strdup(name); @@ -77,7 +78,7 @@ static int add_ref(transport_local *t, const char *name) } /* And if it's a tag, peel it, and add it to the list */ - head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head)); + head = git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); if (git_buf_join(&buf, 0, name, peeled) < 0) return -1; @@ -339,13 +340,11 @@ cleanup: return error; } -static int local_is_connected(git_transport *transport, int *connected) +static int local_is_connected(git_transport *transport) { transport_local *t = (transport_local *)transport; - *connected = t->connected; - - return 0; + return t->connected; } static int local_read_flags(git_transport *transport, int *flags) @@ -398,7 +397,7 @@ static void local_free(git_transport *transport) * Public API * **************/ -int git_transport_local(git_transport **out, void *param) +int git_transport_local(git_transport **out, git_remote *owner, void *param) { transport_local *t; @@ -419,6 +418,8 @@ int git_transport_local(git_transport **out, void *param) t->parent.read_flags = local_read_flags; t->parent.cancel = local_cancel; + t->owner = owner; + *out = (git_transport *) t; return 0; diff --git a/src/transports/smart.c b/src/transports/smart.c index e8dbbef5c..94d389b52 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -29,12 +29,18 @@ static int git_smart__recv_cb(gitno_buffer *buf) return (int)(buf->offset - old_len); } -GIT_INLINE(void) git_smart__reset_stream(transport_smart *t) +GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport) { if (t->current_stream) { t->current_stream->free(t->current_stream); t->current_stream = NULL; } + + if (close_subtransport && + t->wrapped->close(t->wrapped) < 0) + return -1; + + return 0; } static int git_smart__set_callbacks( @@ -63,8 +69,11 @@ static int git_smart__connect( git_smart_subtransport_stream *stream; int error; git_pkt *pkt; + git_pkt_ref *first; + git_smart_service_t service; - git_smart__reset_stream(t); + if (git_smart__reset_stream(t, true) < 0) + return -1; t->url = git__strdup(url); GITERR_CHECK_ALLOC(t->url); @@ -73,55 +82,64 @@ static int git_smart__connect( t->flags = flags; t->cred_acquire_cb = cred_acquire_cb; - if (GIT_DIRECTION_FETCH == direction) - { - if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 0) - return error; - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = stream; - - gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - /* 2 flushes for RPC; 1 for stateful */ - if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) - return error; - - /* Strip the comment packet for RPC */ - if (t->rpc) { - pkt = (git_pkt *)git_vector_get(&t->refs, 0); - - if (!pkt || GIT_PKT_COMMENT != pkt->type) { - giterr_set(GITERR_NET, "Invalid response"); - return -1; - } else { - /* Remove the comment pkt from the list */ - git_vector_remove(&t->refs, 0); - git__free(pkt); - } - } - - /* We now have loaded the refs. */ - t->have_refs = 1; - - if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0) - return -1; - - if (t->rpc) - git_smart__reset_stream(t); - - /* We're now logically connected. */ - t->connected = 1; - - return 0; - } - else - { - giterr_set(GITERR_NET, "Push not implemented"); + if (GIT_DIRECTION_FETCH == t->direction) + service = GIT_SERVICE_UPLOADPACK_LS; + else if (GIT_DIRECTION_PUSH == t->direction) + service = GIT_SERVICE_RECEIVEPACK_LS; + else { + giterr_set(GITERR_NET, "Invalid direction"); return -1; } - return -1; + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0) + return error; + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + /* 2 flushes for RPC; 1 for stateful */ + if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) + return error; + + /* Strip the comment packet for RPC */ + if (t->rpc) { + pkt = (git_pkt *)git_vector_get(&t->refs, 0); + + if (!pkt || GIT_PKT_COMMENT != pkt->type) { + giterr_set(GITERR_NET, "Invalid response"); + return -1; + } else { + /* Remove the comment pkt from the list */ + git_vector_remove(&t->refs, 0); + git__free(pkt); + } + } + + /* We now have loaded the refs. */ + t->have_refs = 1; + + first = (git_pkt_ref *)git_vector_get(&t->refs, 0); + + /* Detect capabilities */ + if (git_smart__detect_caps(first, &t->caps) < 0) + return -1; + + /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */ + if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") && + git_oid_iszero(&first->head.oid)) { + git_vector_clear(&t->refs); + git_pkt_free((git_pkt *)first); + } + + if (t->rpc && git_smart__reset_stream(t, false) < 0) + return -1; + + /* We're now logically connected. */ + t->connected = 1; + + return 0; } static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload) @@ -156,29 +174,55 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len git_smart_subtransport_stream *stream; int error; - if (t->rpc) - git_smart__reset_stream(t); + if (t->rpc && git_smart__reset_stream(t, false) < 0) + return -1; - if (GIT_DIRECTION_FETCH == t->direction) { - if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) - return error; - - /* If this is a stateful implementation, the stream we get back should be the same */ - assert(t->rpc || t->current_stream == stream); - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = stream; - - if ((error = stream->write(stream, (const char *)data, len)) < 0) - return error; - - gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - return 0; + if (GIT_DIRECTION_FETCH != t->direction) { + giterr_set(GITERR_NET, "This operation is only valid for fetch"); + return -1; } - giterr_set(GITERR_NET, "Push not implemented"); - return -1; + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + assert(t->rpc || t->current_stream == stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + if ((error = stream->write(stream, (const char *)data, len)) < 0) + return error; + + gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; +} + +int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream) +{ + int error; + + if (t->rpc && git_smart__reset_stream(t, false) < 0) + return -1; + + if (GIT_DIRECTION_PUSH != t->direction) { + giterr_set(GITERR_NET, "This operation is only valid for push"); + return -1; + } + + if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + assert(t->rpc || t->current_stream == *stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = *stream; + + gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; } static void git_smart__cancel(git_transport *transport) @@ -188,13 +232,11 @@ static void git_smart__cancel(git_transport *transport) git_atomic_set(&t->cancelled, 1); } -static int git_smart__is_connected(git_transport *transport, int *connected) +static int git_smart__is_connected(git_transport *transport) { transport_smart *t = (transport_smart *)transport; - *connected = t->connected; - - return 0; + return t->connected; } static int git_smart__read_flags(git_transport *transport, int *flags) @@ -209,21 +251,37 @@ static int git_smart__read_flags(git_transport *transport, int *flags) static int git_smart__close(git_transport *transport) { transport_smart *t = (transport_smart *)transport; - - git_smart__reset_stream(t); + git_vector *refs = &t->refs; + git_vector *common = &t->common; + unsigned int i; + git_pkt *p; + int ret; + + ret = git_smart__reset_stream(t, true); + + git_vector_foreach(refs, i, p) + git_pkt_free(p); + + git_vector_free(refs); + + git_vector_foreach(common, i, p) + git_pkt_free(p); + + git_vector_free(common); + + if (t->url) { + git__free(t->url); + t->url = NULL; + } t->connected = 0; - return 0; + return ret; } static void git_smart__free(git_transport *transport) { transport_smart *t = (transport_smart *)transport; - git_vector *refs = &t->refs; - git_vector *common = &t->common; - unsigned int i; - git_pkt *p; /* Make sure that the current stream is closed, if we have one. */ git_smart__close(transport); @@ -231,21 +289,10 @@ static void git_smart__free(git_transport *transport) /* Free the subtransport */ t->wrapped->free(t->wrapped); - git_vector_foreach(refs, i, p) { - git_pkt_free(p); - } - git_vector_free(refs); - - git_vector_foreach(common, i, p) { - git_pkt_free(p); - } - git_vector_free(common); - - git__free(t->url); git__free(t); } -int git_transport_smart(git_transport **out, void *param) +int git_transport_smart(git_transport **out, git_remote *owner, void *param) { transport_smart *t; git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; @@ -253,7 +300,7 @@ int git_transport_smart(git_transport **out, void *param) if (!param) return -1; - t = (transport_smart *)git__calloc(sizeof(transport_smart), 1); + t = git__calloc(sizeof(transport_smart), 1); GITERR_CHECK_ALLOC(t); t->parent.set_callbacks = git_smart__set_callbacks; @@ -262,11 +309,13 @@ int git_transport_smart(git_transport **out, void *param) t->parent.free = git_smart__free; t->parent.negotiate_fetch = git_smart__negotiate_fetch; t->parent.download_pack = git_smart__download_pack; + t->parent.push = git_smart__push; t->parent.ls = git_smart__ls; t->parent.is_connected = git_smart__is_connected; t->parent.read_flags = git_smart__read_flags; t->parent.cancel = git_smart__cancel; + t->owner = owner; t->rpc = definition->rpc; if (git_vector_init(&t->refs, 16, NULL) < 0) { diff --git a/src/transports/smart.h b/src/transports/smart.h index b37c4ba96..ea2784bb1 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -8,6 +8,7 @@ #include "vector.h" #include "netops.h" #include "buffer.h" +#include "push.h" #define GIT_SIDE_BAND_DATA 1 #define GIT_SIDE_BAND_PROGRESS 2 @@ -18,6 +19,8 @@ #define GIT_CAP_SIDE_BAND "side-band" #define GIT_CAP_SIDE_BAND_64K "side-band-64k" #define GIT_CAP_INCLUDE_TAG "include-tag" +#define GIT_CAP_DELETE_REFS "delete-refs" +#define GIT_CAP_REPORT_STATUS "report-status" enum git_pkt_type { GIT_PKT_CMD, @@ -31,6 +34,9 @@ enum git_pkt_type { GIT_PKT_ERR, GIT_PKT_DATA, GIT_PKT_PROGRESS, + GIT_PKT_OK, + GIT_PKT_NG, + GIT_PKT_UNPACK, }; /* Used for multi-ack */ @@ -85,19 +91,38 @@ typedef struct { char error[GIT_FLEX_ARRAY]; } git_pkt_err; +typedef struct { + enum git_pkt_type type; + char *ref; +} git_pkt_ok; + +typedef struct { + enum git_pkt_type type; + char *ref; + char *msg; +} git_pkt_ng; + +typedef struct { + enum git_pkt_type type; + int unpack_ok; +} git_pkt_unpack; + typedef struct transport_smart_caps { int common:1, ofs_delta:1, multi_ack: 1, side_band:1, side_band_64k:1, - include_tag:1; + include_tag:1, + delete_refs:1, + report_status:1; } transport_smart_caps; typedef void (*packetsize_cb)(size_t received, void *payload); typedef struct { git_transport parent; + git_remote *owner; char *url; git_cred_acquire_cb cred_acquire_cb; int direction; @@ -123,6 +148,7 @@ typedef struct { /* smart_protocol.c */ int git_smart__store_refs(transport_smart *t, int flushes); int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps); +int git_smart__push(git_transport *transport, git_push *push); int git_smart__negotiate_fetch( git_transport *transport, @@ -139,6 +165,7 @@ int git_smart__download_pack( /* smart.c */ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); +int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); /* smart_pkt.c */ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 26fc0e4aa..c0674301b 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -214,6 +214,83 @@ error_out: return error; } +static int ok_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ok *pkt; + const char *ptr; + + pkt = git__malloc(sizeof(*pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_OK; + + line += 3; /* skip "ok " */ + ptr = strchr(line, '\n'); + len = ptr - line; + + pkt->ref = git__malloc(len + 1); + GITERR_CHECK_ALLOC(pkt->ref); + + memcpy(pkt->ref, line, len); + pkt->ref[len] = '\0'; + + *out = (git_pkt *)pkt; + return 0; +} + +static int ng_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ng *pkt; + const char *ptr; + + pkt = git__malloc(sizeof(*pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_NG; + + line += 3; /* skip "ng " */ + ptr = strchr(line, ' '); + len = ptr - line; + + pkt->ref = git__malloc(len + 1); + GITERR_CHECK_ALLOC(pkt->ref); + + memcpy(pkt->ref, line, len); + pkt->ref[len] = '\0'; + + line = ptr + 1; + ptr = strchr(line, '\n'); + len = ptr - line; + + pkt->msg = git__malloc(len + 1); + GITERR_CHECK_ALLOC(pkt->msg); + + memcpy(pkt->msg, line, len); + pkt->msg[len] = '\0'; + + *out = (git_pkt *)pkt; + return 0; +} + +static int unpack_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_unpack *pkt; + + GIT_UNUSED(len); + + pkt = git__malloc(sizeof(*pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_UNPACK; + if (!git__prefixcmp(line, "unpack ok")) + pkt->unpack_ok = 1; + else + pkt->unpack_ok = 0; + + *out = (git_pkt *)pkt; + return 0; +} + static int32_t parse_len(const char *line) { char num[PKT_LEN_SIZE + 1]; @@ -311,6 +388,12 @@ int git_pkt_parse_line( ret = err_pkt(head, line, len); else if (*line == '#') ret = comment_pkt(head, line, len); + else if (!git__prefixcmp(line, "ok")) + ret = ok_pkt(head, line, len); + else if (!git__prefixcmp(line, "ng")) + ret = ng_pkt(head, line, len); + else if (!git__prefixcmp(line, "unpack")) + ret = unpack_pkt(head, line, len); else ret = ref_pkt(head, line, len); @@ -326,6 +409,17 @@ void git_pkt_free(git_pkt *pkt) git__free(p->head.name); } + if (pkt->type == GIT_PKT_OK) { + git_pkt_ok *p = (git_pkt_ok *) pkt; + git__free(p->ref); + } + + if (pkt->type == GIT_PKT_NG) { + git_pkt_ng *p = (git_pkt_ng *) pkt; + git__free(p->ref); + git__free(p->msg); + } + git__free(pkt); } diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 99d34e23b..80ff72681 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -4,9 +4,14 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ +#include "git2.h" + #include "smart.h" #include "refs.h" #include "repository.h" +#include "push.h" +#include "pack-objects.h" +#include "remote.h" #define NETWORK_XFER_THRESHOLD (100*1024) @@ -18,6 +23,11 @@ int git_smart__store_refs(transport_smart *t, int flushes) const char *line_end; git_pkt *pkt; + /* Clear existing refs in case git_remote_connect() is called again + * after git_remote_disconnect(). + */ + git_vector_clear(refs); + do { if (buf->offset > 0) error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); @@ -71,37 +81,43 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) if (*ptr == ' ') ptr++; - if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { + if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { caps->common = caps->ofs_delta = 1; ptr += strlen(GIT_CAP_OFS_DELTA); continue; } - if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { + if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { caps->common = caps->multi_ack = 1; ptr += strlen(GIT_CAP_MULTI_ACK); continue; } - if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { + if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { caps->common = caps->include_tag = 1; ptr += strlen(GIT_CAP_INCLUDE_TAG); continue; } /* Keep side-band check after side-band-64k */ - if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { + if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { caps->common = caps->side_band_64k = 1; ptr += strlen(GIT_CAP_SIDE_BAND_64K); continue; } - if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { + if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { caps->common = caps->side_band = 1; ptr += strlen(GIT_CAP_SIDE_BAND); continue; } + if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) { + caps->common = caps->delete_refs = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } + /* We don't know this capability, so skip it */ ptr = strchr(ptr, ' '); } @@ -471,7 +487,8 @@ on_success: error = 0; on_error: - writepack->free(writepack); + if (writepack) + writepack->free(writepack); /* Trailing execution of progress_cb, if necessary */ if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) @@ -479,3 +496,218 @@ on_error: return error; } + +static int gen_pktline(git_buf *buf, git_push *push) +{ + git_remote_head *head; + push_spec *spec; + unsigned int i, j, len; + char hex[41]; hex[40] = '\0'; + + git_vector_foreach(&push->specs, i, spec) { + len = 2*GIT_OID_HEXSZ + 7; + + if (i == 0) { + len +=1; /* '\0' */ + if (push->report_status) + len += strlen(GIT_CAP_REPORT_STATUS); + } + + if (spec->lref) { + len += spec->rref ? strlen(spec->rref) : strlen(spec->lref); + + if (git_oid_iszero(&spec->roid)) { + + /* + * Create remote reference + */ + git_oid_fmt(hex, &spec->loid); + git_buf_printf(buf, "%04x%s %s %s", len, + GIT_OID_HEX_ZERO, hex, + spec->rref ? spec->rref : spec->lref); + + } else { + + /* + * Update remote reference + */ + git_oid_fmt(hex, &spec->roid); + git_buf_printf(buf, "%04x%s ", len, hex); + + git_oid_fmt(hex, &spec->loid); + git_buf_printf(buf, "%s %s", hex, + spec->rref ? spec->rref : spec->lref); + } + } else { + /* + * Delete remote reference + */ + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->rref, head->name)) { + len += strlen(spec->rref); + + git_oid_fmt(hex, &head->oid); + git_buf_printf(buf, "%04x%s %s %s", len, + hex, GIT_OID_HEX_ZERO, head->name); + + break; + } + } + } + + if (i == 0) { + git_buf_putc(buf, '\0'); + if (push->report_status) + git_buf_printf(buf, GIT_CAP_REPORT_STATUS); + } + + git_buf_putc(buf, '\n'); + } + git_buf_puts(buf, "0000"); + return git_buf_oom(buf) ? -1 : 0; +} + +static int parse_report(gitno_buffer *buf, git_push *push) +{ + git_pkt *pkt; + const char *line_end; + int error, recvd; + + for (;;) { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, buf->data, + &line_end, buf->offset); + else + error = GIT_EBUFS; + + if (error < 0 && error != GIT_EBUFS) + return -1; + + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) + return -1; + + if (recvd == 0) { + giterr_set(GITERR_NET, "Early EOF"); + return -1; + } + continue; + } + + gitno_consume(buf, line_end); + + if (pkt->type == GIT_PKT_OK) { + push_status *status = git__malloc(sizeof(push_status)); + GITERR_CHECK_ALLOC(status); + status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); + status->msg = NULL; + git_pkt_free(pkt); + if (git_vector_insert(&push->status, status) < 0) { + git__free(status); + return -1; + } + continue; + } + + if (pkt->type == GIT_PKT_NG) { + push_status *status = git__malloc(sizeof(push_status)); + GITERR_CHECK_ALLOC(status); + status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); + status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); + git_pkt_free(pkt); + if (git_vector_insert(&push->status, status) < 0) { + git__free(status); + return -1; + } + continue; + } + + if (pkt->type == GIT_PKT_UNPACK) { + push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; + git_pkt_free(pkt); + continue; + } + + if (pkt->type == GIT_PKT_FLUSH) { + git_pkt_free(pkt); + return 0; + } + + git_pkt_free(pkt); + giterr_set(GITERR_NET, "report-status: protocol error"); + return -1; + } +} + +static int stream_thunk(void *buf, size_t size, void *data) +{ + git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data; + + return s->write(s, (const char *)buf, size); +} + +int git_smart__push(git_transport *transport, git_push *push) +{ + transport_smart *t = (transport_smart *)transport; + git_smart_subtransport_stream *s; + git_buf pktline = GIT_BUF_INIT; + char *url = NULL; + int error = -1; + +#ifdef PUSH_DEBUG +{ + git_remote_head *head; + push_spec *spec; + unsigned int i; + char hex[41]; hex[40] = '\0'; + + git_vector_foreach(&push->remote->refs, i, head) { + git_oid_fmt(hex, &head->oid); + fprintf(stderr, "%s (%s)\n", hex, head->name); + } + + git_vector_foreach(&push->specs, i, spec) { + git_oid_fmt(hex, &spec->roid); + fprintf(stderr, "%s (%s) -> ", hex, spec->lref); + git_oid_fmt(hex, &spec->loid); + fprintf(stderr, "%s (%s)\n", hex, spec->rref ? + spec->rref : spec->lref); + } +} +#endif + + if (git_smart__get_push_stream(t, &s) < 0 || + gen_pktline(&pktline, push) < 0 || + s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0 || + git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0) + goto on_error; + + /* If we sent nothing or the server doesn't support report-status, then + * we consider the pack to have been unpacked successfully */ + if (!push->specs.length || !push->report_status) + push->unpack_ok = 1; + else if (parse_report(&t->buffer, push) < 0) + goto on_error; + + /* If we updated at least one ref, then we need to re-acquire the list of + * refs so the caller can call git_remote_update_tips afterward. TODO: Use + * the data from the push report to do this without another network call */ + if (push->specs.length) { + git_cred_acquire_cb cred_cb = t->cred_acquire_cb; + int flags = t->flags; + + url = git__strdup(t->url); + + if (!url || t->parent.close(&t->parent) < 0 || + t->parent.connect(&t->parent, url, cred_cb, GIT_DIRECTION_PUSH, flags)) + goto on_error; + } + + error = 0; + +on_error: + git__free(url); + git_buf_free(&pktline); + + return error; +} diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index f3abe8598..fe4b8025f 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -13,25 +13,35 @@ #include "posix.h" #include "netops.h" #include "smart.h" +#include "remote.h" +#include "repository.h" #include #pragma comment(lib, "winhttp") +/* For UuidCreate */ +#pragma comment(lib, "rpcrt4") + #define WIDEN2(s) L ## s #define WIDEN(s) WIDEN2(s) #define MAX_CONTENT_TYPE_LEN 100 #define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 +#define CACHED_POST_BODY_BUF_SIZE 4096 +#define UUID_LENGTH_CCH 32 static const char *prefix_http = "http://"; static const char *prefix_https = "https://"; static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; static const char *upload_pack_service_url = "/git-upload-pack"; +static const char *receive_pack_service = "receive-pack"; +static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack"; +static const char *receive_pack_service_url = "/git-receive-pack"; static const wchar_t *get_verb = L"GET"; static const wchar_t *post_verb = L"POST"; -static const wchar_t *basic_authtype = L"Basic"; static const wchar_t *pragma_nocache = L"Pragma: no-cache"; +static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked"; static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; @@ -48,8 +58,13 @@ typedef struct { const char *service_url; const wchar_t *verb; HINTERNET request; + char *chunk_buffer; + unsigned chunk_buffer_len; + HANDLE post_body; + DWORD post_body_len; unsigned sent_request : 1, - received_response : 1; + received_response : 1, + chunked : 1; } winhttp_stream; typedef struct { @@ -87,7 +102,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred) goto on_error; } - wide = (wchar_t *)git__malloc(wide_len * sizeof(wchar_t)); + wide = git__malloc(wide_len * sizeof(wchar_t)); if (!wide) goto on_error; @@ -126,9 +141,11 @@ static int winhttp_stream_connect(winhttp_stream *s) { winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); git_buf buf = GIT_BUF_INIT; + char *proxy_url = NULL; wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN]; wchar_t *types[] = { L"*/*", NULL }; BOOL peerdist = FALSE; + int error = -1; /* Prepare URL */ git_buf_printf(&buf, "%s%s", t->path, s->service_url); @@ -153,6 +170,36 @@ static int winhttp_stream_connect(winhttp_stream *s) goto on_error; } + /* Set proxy if necessary */ + if (git_remote__get_http_proxy(t->owner->owner, t->use_ssl, &proxy_url) < 0) + goto on_error; + + if (proxy_url) { + WINHTTP_PROXY_INFO proxy_info; + size_t wide_len; + + git__utf8_to_16(url, GIT_WIN_PATH, proxy_url); + + wide_len = wcslen(url); + + /* Strip any trailing forward slash on the proxy URL; + * WinHTTP doesn't like it if one is present */ + if (L'/' == url[wide_len - 1]) + url[wide_len - 1] = L'\0'; + + proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy_info.lpszProxy = url; + proxy_info.lpszProxyBypass = NULL; + + if (!WinHttpSetOption(s->request, + WINHTTP_OPTION_PROXY, + &proxy_info, + sizeof(WINHTTP_PROXY_INFO))) { + giterr_set(GITERR_OS, "Failed to set proxy"); + goto on_error; + } + } + /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP * adds itself. This option may not be supported by the underlying * platform, so we do not error-check it */ @@ -205,11 +252,12 @@ static int winhttp_stream_connect(winhttp_stream *s) /* We've done everything up to calling WinHttpSendRequest. */ - return 0; + error = 0; on_error: + git__free(proxy_url); git_buf_free(&buf); - return -1; + return error; } static int parse_unauthorized_response( @@ -217,57 +265,65 @@ static int parse_unauthorized_response( int *allowed_types, int *auth_mechanism) { - DWORD index, buf_size, last_error; - int error = 0; - wchar_t *buf = NULL; + DWORD supported, first, target; *allowed_types = 0; + *auth_mechanism = 0; - for (index = 0; ; index++) { - /* Make a first call to ask for the size of the buffer to allocate - * to hold the WWW-Authenticate header */ - if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, - WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER, - &buf_size, &index)) - { - last_error = GetLastError(); - - if (ERROR_WINHTTP_HEADER_NOT_FOUND == last_error) { - /* End of enumeration */ - break; - } else if (ERROR_INSUFFICIENT_BUFFER == last_error) { - git__free(buf); - buf = (wchar_t *)git__malloc(buf_size); - - if (!buf) { - error = -1; - break; - } - } else { - giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header"); - error = -1; - break; - } - } - - /* Actually receive the data into our now-allocated buffer */ - if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, - WINHTTP_HEADER_NAME_BY_INDEX, buf, - &buf_size, &index)) { - giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header"); - error = -1; - break; - } - - if (!wcsncmp(buf, basic_authtype, 5) && - (buf[5] == L'\0' || buf[5] == L' ')) { - *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; - *auth_mechanism = GIT_WINHTTP_AUTH_BASIC; - } + /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes(). + * We can assume this was already done, since we know we are unauthorized. + */ + if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) { + giterr_set(GITERR_OS, "Failed to parse supported auth schemes"); + return -1; } - git__free(buf); - return error; + if (WINHTTP_AUTH_SCHEME_BASIC & supported) { + *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *auth_mechanism = GIT_WINHTTP_AUTH_BASIC; + } + + return 0; +} + +static int write_chunk(HINTERNET request, const char *buffer, size_t len) +{ + DWORD bytes_written; + git_buf buf = GIT_BUF_INIT; + + /* Chunk header */ + git_buf_printf(&buf, "%X\r\n", len); + + if (git_buf_oom(&buf)) + return -1; + + if (!WinHttpWriteData(request, + git_buf_cstr(&buf), git_buf_len(&buf), + &bytes_written)) { + git_buf_free(&buf); + giterr_set(GITERR_OS, "Failed to write chunk header"); + return -1; + } + + git_buf_free(&buf); + + /* Chunk body */ + if (!WinHttpWriteData(request, + buffer, len, + &bytes_written)) { + giterr_set(GITERR_OS, "Failed to write chunk"); + return -1; + } + + /* Chunk footer */ + if (!WinHttpWriteData(request, + "\r\n", 2, + &bytes_written)) { + giterr_set(GITERR_OS, "Failed to write chunk footer"); + return -1; + } + + return 0; } static int winhttp_stream_read( @@ -285,22 +341,84 @@ replay: if (!s->request && winhttp_stream_connect(s) < 0) return -1; - if (!s->sent_request && - !WinHttpSendRequest(s->request, - WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, - 0, 0)) { - giterr_set(GITERR_OS, "Failed to send request"); - return -1; - } - - s->sent_request = 1; - if (!s->received_response) { - DWORD status_code, status_code_length, content_type_length; + DWORD status_code, status_code_length, content_type_length, bytes_written; char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; + if (!s->sent_request) { + if (!WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + s->post_body_len, 0)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + s->sent_request = 1; + } + + if (s->chunked) { + assert(s->verb == post_verb); + + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0 && + write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + + /* Write the final chunk. */ + if (!WinHttpWriteData(s->request, + "0\r\n\r\n", 5, + &bytes_written)) { + giterr_set(GITERR_OS, "Failed to write final chunk"); + return -1; + } + } + else if (s->post_body) { + char *buffer; + DWORD len = s->post_body_len, bytes_read; + + if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body, + 0, 0, FILE_BEGIN) && + NO_ERROR != GetLastError()) { + giterr_set(GITERR_OS, "Failed to reset file pointer"); + return -1; + } + + buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); + + while (len > 0) { + DWORD bytes_written; + + if (!ReadFile(s->post_body, buffer, + min(CACHED_POST_BODY_BUF_SIZE, len), + &bytes_read, NULL) || + !bytes_read) { + git__free(buffer); + giterr_set(GITERR_OS, "Failed to read from temp file"); + return -1; + } + + if (!WinHttpWriteData(s->request, buffer, + bytes_read, &bytes_written)) { + git__free(buffer); + giterr_set(GITERR_OS, "Failed to write data"); + return -1; + } + + len -= bytes_read; + assert(bytes_read == bytes_written); + } + + git__free(buffer); + + /* Eagerly close the temp file */ + CloseHandle(s->post_body); + s->post_body = NULL; + } + if (!WinHttpReceiveResponse(s->request, 0)) { giterr_set(GITERR_OS, "Failed to receive response"); return -1; @@ -376,7 +494,7 @@ replay: if (!WinHttpReadData(s->request, (LPVOID)buffer, - (DWORD)buf_size, + buf_size, &dw_bytes_read)) { giterr_set(GITERR_OS, "Failed to read data"); @@ -388,7 +506,7 @@ replay: return 0; } -static int winhttp_stream_write( +static int winhttp_stream_write_single( git_smart_subtransport_stream *stream, const char *buffer, size_t len) @@ -400,10 +518,13 @@ static int winhttp_stream_write( if (!s->request && winhttp_stream_connect(s) < 0) return -1; - /* Since we have to write the Content-Length header up front, we're - * basically limited to a single call to write() per request. */ - if (!s->sent_request && - !WinHttpSendRequest(s->request, + /* This implementation of write permits only a single call. */ + if (s->sent_request) { + giterr_set(GITERR_NET, "Subtransport configured for only one write"); + return -1; + } + + if (!WinHttpSendRequest(s->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, (DWORD)len, 0)) { @@ -417,7 +538,7 @@ static int winhttp_stream_write( (LPCVOID)buffer, (DWORD)len, &bytes_written)) { - giterr_set(GITERR_OS, "Failed to send request"); + giterr_set(GITERR_OS, "Failed to write data"); return -1; } @@ -426,10 +547,198 @@ static int winhttp_stream_write( return 0; } +static int put_uuid_string(LPWSTR buffer, DWORD buffer_len_cch) +{ + UUID uuid; + RPC_STATUS status = UuidCreate(&uuid); + int result; + + if (RPC_S_OK != status && + RPC_S_UUID_LOCAL_ONLY != status && + RPC_S_UUID_NO_ADDRESS != status) { + giterr_set(GITERR_NET, "Unable to generate name for temp file"); + return -1; + } + + if (buffer_len_cch < (UUID_LENGTH_CCH + 1)) { + giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name"); + return -1; + } + + result = wsprintfW(buffer, L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", + uuid.Data1, uuid.Data2, uuid.Data3, + uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], + uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); + + if (result != UUID_LENGTH_CCH) { + giterr_set(GITERR_OS, "Unable to generate name for temp file"); + return -1; + } + + return 0; +} + +static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch) +{ + int len; + + if (!GetTempPathW(buffer_len_cch, buffer)) { + giterr_set(GITERR_OS, "Failed to get temp path"); + return -1; + } + + len = wcslen(buffer); + + /* 1 prefix character for the backslash, 1 postfix for + * the null terminator */ + if (buffer_len_cch - len < 1 + UUID_LENGTH_CCH + 1) { + giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name"); + return -1; + } + + if (buffer[len - 1] != '\\') + buffer[len++] = '\\'; + + if (put_uuid_string(&buffer[len], UUID_LENGTH_CCH + 1) < 0) + return -1; + + return 0; +} + +static int winhttp_stream_write_buffered( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + DWORD bytes_written; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + /* Buffer the payload, using a temporary file so we delegate + * memory management of the data to the operating system. */ + if (!s->post_body) { + wchar_t temp_path[MAX_PATH + 1]; + + if (get_temp_file(temp_path, MAX_PATH + 1) < 0) + return -1; + + s->post_body = CreateFileW(temp_path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_DELETE, NULL, + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + + if (INVALID_HANDLE_VALUE == s->post_body) { + s->post_body = NULL; + giterr_set(GITERR_OS, "Failed to create temporary file"); + return -1; + } + } + + if (!WriteFile(s->post_body, buffer, len, &bytes_written, NULL)) { + giterr_set(GITERR_OS, "Failed to write to temporary file"); + return -1; + } + + assert((DWORD)len == bytes_written); + + s->post_body_len += bytes_written; + + return 0; +} + +static int winhttp_stream_write_chunked( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + if (!s->sent_request) { + /* Send Transfer-Encoding: chunked header */ + if (!WinHttpAddRequestHeaders(s->request, + transfer_encoding, (ULONG) -1L, + WINHTTP_ADDREQ_FLAG_ADD)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + return -1; + } + + if (!WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + s->sent_request = 1; + } + + if (len > CACHED_POST_BODY_BUF_SIZE) { + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0) { + if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + } + + /* Write chunk directly */ + if (write_chunk(s->request, buffer, len) < 0) + return -1; + } + else { + /* Append as much to the buffer as we can */ + int count = min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len); + + if (!s->chunk_buffer) + s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); + + memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count); + s->chunk_buffer_len += count; + buffer += count; + len -= count; + + /* Is the buffer full? If so, then flush */ + if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) { + if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + + /* Is there any remaining data from the source? */ + if (len > 0) { + memcpy(s->chunk_buffer, buffer, len); + s->chunk_buffer_len = len; + } + } + } + + return 0; +} + static void winhttp_stream_free(git_smart_subtransport_stream *stream) { winhttp_stream *s = (winhttp_stream *)stream; + if (s->chunk_buffer) { + git__free(s->chunk_buffer); + s->chunk_buffer = NULL; + } + + if (s->post_body) { + CloseHandle(s->post_body); + s->post_body = NULL; + } + if (s->request) { WinHttpCloseHandle(s->request); s->request = NULL; @@ -438,22 +747,23 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream) git__free(s); } -static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream) +static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream) { winhttp_stream *s; if (!stream) return -1; - s = (winhttp_stream *)git__calloc(sizeof(winhttp_stream), 1); + s = git__calloc(sizeof(winhttp_stream), 1); GITERR_CHECK_ALLOC(s); s->parent.subtransport = &t->parent; s->parent.read = winhttp_stream_read; - s->parent.write = winhttp_stream_write; + s->parent.write = winhttp_stream_write_single; s->parent.free = winhttp_stream_free; - *stream = (git_smart_subtransport_stream *)s; + *stream = s; + return 0; } @@ -520,20 +830,8 @@ static int winhttp_connect( static int winhttp_uploadpack_ls( winhttp_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) + winhttp_stream *s) { - winhttp_stream *s; - - if (!t->connection && - winhttp_connect(t, url) < 0) - return -1; - - if (winhttp_stream_alloc(t, stream) < 0) - return -1; - - s = (winhttp_stream *)*stream; - s->service = upload_pack_service; s->service_url = upload_pack_ls_service_url; s->verb = get_verb; @@ -543,20 +841,8 @@ static int winhttp_uploadpack_ls( static int winhttp_uploadpack( winhttp_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) + winhttp_stream *s) { - winhttp_stream *s; - - if (!t->connection && - winhttp_connect(t, url) < 0) - return -1; - - if (winhttp_stream_alloc(t, stream) < 0) - return -1; - - s = (winhttp_stream *)*stream; - s->service = upload_pack_service; s->service_url = upload_pack_service_url; s->verb = post_verb; @@ -564,13 +850,53 @@ static int winhttp_uploadpack( return 0; } +static int winhttp_receivepack_ls( + winhttp_subtransport *t, + winhttp_stream *s) +{ + s->service = receive_pack_service; + s->service_url = receive_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int winhttp_receivepack( + winhttp_subtransport *t, + winhttp_stream *s) +{ + /* WinHTTP only supports Transfer-Encoding: chunked + * on Windows Vista (NT 6.0) and higher. */ + s->chunked = LOBYTE(LOWORD(GetVersion())) >= 6; + + if (s->chunked) + s->parent.write = winhttp_stream_write_chunked; + else + s->parent.write = winhttp_stream_write_buffered; + + s->service = receive_pack_service; + s->service_url = receive_pack_service_url; + s->verb = post_verb; + + return 0; +} + static int winhttp_action( git_smart_subtransport_stream **stream, - git_smart_subtransport *smart_transport, + git_smart_subtransport *subtransport, const char *url, git_smart_service_t action) { - winhttp_subtransport *t = (winhttp_subtransport *)smart_transport; + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + winhttp_stream *s; + int ret = -1; + + if (!t->connection && + winhttp_connect(t, url) < 0) + return -1; + + if (winhttp_stream_alloc(t, &s) < 0) + return -1; if (!stream) return -1; @@ -578,22 +904,45 @@ static int winhttp_action( switch (action) { case GIT_SERVICE_UPLOADPACK_LS: - return winhttp_uploadpack_ls(t, url, stream); + ret = winhttp_uploadpack_ls(t, s); + break; case GIT_SERVICE_UPLOADPACK: - return winhttp_uploadpack(t, url, stream); + ret = winhttp_uploadpack(t, s); + break; + + case GIT_SERVICE_RECEIVEPACK_LS: + ret = winhttp_receivepack_ls(t, s); + break; + + case GIT_SERVICE_RECEIVEPACK: + ret = winhttp_receivepack(t, s); + break; + + default: + assert(0); } - *stream = NULL; - return -1; + if (!ret) + *stream = &s->parent; + + return ret; } -static void winhttp_free(git_smart_subtransport *smart_transport) +static int winhttp_close(git_smart_subtransport *subtransport) { - winhttp_subtransport *t = (winhttp_subtransport *) smart_transport; - - git__free(t->host); - git__free(t->port); + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + int ret = 0; + + if (t->host) { + git__free(t->host); + t->host = NULL; + } + + if (t->port) { + git__free(t->port); + t->port = NULL; + } if (t->cred) { t->cred->free(t->cred); @@ -601,15 +950,32 @@ static void winhttp_free(git_smart_subtransport *smart_transport) } if (t->connection) { - WinHttpCloseHandle(t->connection); + if (!WinHttpCloseHandle(t->connection)) { + giterr_set(GITERR_OS, "Unable to close connection"); + ret = -1; + } + t->connection = NULL; } if (t->session) { - WinHttpCloseHandle(t->session); + if (!WinHttpCloseHandle(t->session)) { + giterr_set(GITERR_OS, "Unable to close session"); + ret = -1; + } + t->session = NULL; } + return ret; +} + +static void winhttp_free(git_smart_subtransport *subtransport) +{ + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + + winhttp_close(subtransport); + git__free(t); } @@ -620,11 +986,12 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own if (!out) return -1; - t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1); + t = git__calloc(sizeof(winhttp_subtransport), 1); GITERR_CHECK_ALLOC(t); t->owner = (transport_smart *)owner; t->parent.action = winhttp_action; + t->parent.close = winhttp_close; t->parent.free = winhttp_free; *out = (git_smart_subtransport *) t; diff --git a/tests-clar/network/fetch.c b/tests-clar/network/fetch.c index b9eaedf43..84c947291 100644 --- a/tests-clar/network/fetch.c +++ b/tests-clar/network/fetch.c @@ -48,8 +48,8 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) git_remote_set_autotag(remote, flag); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); cl_git_pass(git_remote_download(remote, progress, &bytes_received)); - git_remote_disconnect(remote); cl_git_pass(git_remote_update_tips(remote)); + git_remote_disconnect(remote); cl_assert_equal_i(counter, n); cl_assert(bytes_received > 0); diff --git a/tests-clar/network/push.c b/tests-clar/network/push.c new file mode 100644 index 000000000..acc376de7 --- /dev/null +++ b/tests-clar/network/push.c @@ -0,0 +1,518 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "posix.h" +#include "vector.h" +#include "../submodule/submodule_helpers.h" +#include "push_util.h" + +CL_IN_CATEGORY("network") + +static git_repository *_repo; + +static char *_remote_url; +static char *_remote_user; +static char *_remote_pass; + +static git_remote *_remote; +static record_callbacks_data _record_cbs_data = {{ 0 }}; +static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data); + +static git_oid _oid_b6; +static git_oid _oid_b5; +static git_oid _oid_b4; +static git_oid _oid_b3; +static git_oid _oid_b2; +static git_oid _oid_b1; + +/* git_oid *oid, git_repository *repo, (string literal) blob */ +#define CREATE_BLOB(oid, repo, blob) git_blob_create_frombuffer(oid, repo, blob, sizeof(blob) - 1) + +static int cred_acquire_cb(git_cred **cred, const char *url, unsigned int allowed_types) +{ + GIT_UNUSED(url); + + if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 || + git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass) < 0) + return -1; + + return 0; +} + +typedef struct { + const char *ref; + const char *msg; +} push_status; + +/** + * git_push_status_foreach callback that records status entries. + * @param data (git_vector *) of push_status instances + */ +static int record_push_status_cb(const char *ref, const char *msg, void *data) +{ + git_vector *statuses = (git_vector *)data; + push_status *s; + + cl_assert(s = git__malloc(sizeof(*s))); + s->ref = ref; + s->msg = msg; + + git_vector_insert(statuses, s); + + return 0; +} + +static void do_verify_push_status(git_push *push, const push_status expected[], const size_t expected_len) +{ + git_vector actual = GIT_VECTOR_INIT; + push_status *iter; + bool failed = false; + size_t i; + + git_push_status_foreach(push, record_push_status_cb, &actual); + + if (expected_len != actual.length) + failed = true; + else + git_vector_foreach(&actual, i, iter) + if (strcmp(expected[i].ref, iter->ref) || + (expected[i].msg && strcmp(expected[i].msg, iter->msg))) { + failed = true; + break; + } + + if (failed) { + git_buf msg = GIT_BUF_INIT; + + git_buf_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n"); + + for(i = 0; i < expected_len; i++) { + git_buf_printf(&msg, "%s: %s\n", + expected[i].ref, + expected[i].msg ? expected[i].msg : ""); + } + + git_buf_puts(&msg, "\nACTUAL:\n"); + + git_vector_foreach(&actual, i, iter) + git_buf_printf(&msg, "%s: %s\n", iter->ref, iter->msg); + + cl_fail(git_buf_cstr(&msg)); + + git_buf_free(&msg); + } + + git_vector_foreach(&actual, i, iter) + git__free(iter); + + git_vector_free(&actual); +} + +/** + * Verifies that after git_push_finish(), refs on a remote have the expected + * names, oids, and order. + * + * @param remote remote to verify + * @param expected_refs expected remote refs after push + * @param expected_refs_len length of expected_refs + */ +static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) +{ + git_vector actual_refs = GIT_VECTOR_INIT; + + git_remote_ls(remote, record_ref_cb, &actual_refs); + verify_remote_refs(&actual_refs, expected_refs, expected_refs_len); + + git_vector_free(&actual_refs); +} + +void test_network_push__initialize(void) +{ + git_vector delete_specs = GIT_VECTOR_INIT; + size_t i; + char *curr_del_spec; + + _repo = cl_git_sandbox_init("push_src"); + + cl_fixture_sandbox("testrepo.git"); + cl_rename("push_src/submodule/.gitted", "push_src/submodule/.git"); + + rewrite_gitmodules(git_repository_workdir(_repo)); + + /* git log --format=oneline --decorate --graph + * *-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, b6) merge b3, b4, and b5 to b6 + * |\ \ + * | | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git' + * | * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt + * | |/ + * * | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt + * |/ + * * a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt + * * 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt + */ + git_oid_fromstr(&_oid_b6, "951bbbb90e2259a4c8950db78946784fb53fcbce"); + git_oid_fromstr(&_oid_b5, "fa38b91f199934685819bea316186d8b008c52a2"); + git_oid_fromstr(&_oid_b4, "27b7ce66243eb1403862d05f958c002312df173d"); + git_oid_fromstr(&_oid_b3, "d9b63a88223d8367516f50bd131a5f7349b7f3e4"); + git_oid_fromstr(&_oid_b2, "a78705c3b2725f931d3ee05348d83cc26700f247"); + git_oid_fromstr(&_oid_b1, "a78705c3b2725f931d3ee05348d83cc26700f247"); + + /* Remote URL environment variable must be set. User and password are optional. */ + _remote_url = cl_getenv("GITTEST_REMOTE_URL"); + _remote_user = cl_getenv("GITTEST_REMOTE_USER"); + _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); + _remote = NULL; + + if (_remote_url) { + cl_git_pass(git_remote_add(&_remote, _repo, "test", _remote_url)); + + git_remote_set_cred_acquire_cb(_remote, cred_acquire_cb); + record_callbacks_data_clear(&_record_cbs_data); + git_remote_set_callbacks(_remote, &_record_cbs); + + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH)); + + /* Clean up previously pushed branches. Fails if receive.denyDeletes is + * set on the remote. Also, on Git 1.7.0 and newer, you must run + * 'git config receive.denyDeleteCurrent ignore' in the remote repo in + * order to delete the remote branch pointed to by HEAD (usually master). + * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt + */ + cl_git_pass(git_remote_ls(_remote, delete_ref_cb, &delete_specs)); + if (delete_specs.length) { + git_push *push; + + cl_git_pass(git_push_new(&push, _remote)); + + git_vector_foreach(&delete_specs, i, curr_del_spec) { + git_push_add_refspec(push, curr_del_spec); + git__free(curr_del_spec); + } + + cl_git_pass(git_push_finish(push)); + git_push_free(push); + } + + git_remote_disconnect(_remote); + git_vector_free(&delete_specs); + + /* Now that we've deleted everything, fetch from the remote */ + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_download(_remote, NULL, NULL)); + cl_git_pass(git_remote_update_tips(_remote)); + git_remote_disconnect(_remote); + } else + printf("GITTEST_REMOTE_URL unset; skipping push test\n"); +} + +void test_network_push__cleanup(void) +{ + if (_remote) + git_remote_free(_remote); + + record_callbacks_data_clear(&_record_cbs_data); + + cl_fixture_cleanup("testrepo.git"); + cl_git_sandbox_cleanup(); +} + +/** + * Calls push and relists refs on remote to verify success. + * + * @param refspecs refspecs to push + * @param refspecs_len length of refspecs + * @param expected_refs expected remote refs after push + * @param expected_refs_len length of expected_refs + * @param expected_ret expected return value from git_push_finish() + */ +static void do_push(const char *refspecs[], size_t refspecs_len, + push_status expected_statuses[], size_t expected_statuses_len, + expected_ref expected_refs[], size_t expected_refs_len, int expected_ret) +{ + git_push *push; + size_t i; + int ret; + + if (_remote) { + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH)); + + cl_git_pass(git_push_new(&push, _remote)); + + for (i = 0; i < refspecs_len; i++) + cl_git_pass(git_push_add_refspec(push, refspecs[i])); + + if (expected_ret < 0) { + cl_git_fail(ret = git_push_finish(push)); + cl_assert_equal_i(0, git_push_unpack_ok(push)); + } + else { + cl_git_pass(ret = git_push_finish(push)); + cl_assert_equal_i(1, git_push_unpack_ok(push)); + } + + do_verify_push_status(push, expected_statuses, expected_statuses_len); + + cl_assert_equal_i(expected_ret, ret); + + git_push_free(push); + + verify_refs(_remote, expected_refs, expected_refs_len); + + cl_git_pass(git_remote_update_tips(_remote)); + + git_remote_disconnect(_remote); + } +} + +/* Call push_finish() without ever calling git_push_add_refspec() */ +void test_network_push__noop(void) +{ + do_push(NULL, 0, NULL, 0, NULL, 0, 0); +} + +void test_network_push__b1(void) +{ + const char *specs[] = { "refs/heads/b1:refs/heads/b1" }; + push_status exp_stats[] = { { "refs/heads/b1", NULL } }; + expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0); +} + +void test_network_push__b2(void) +{ + const char *specs[] = { "refs/heads/b2:refs/heads/b2" }; + push_status exp_stats[] = { { "refs/heads/b2", NULL } }; + expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0); +} + +void test_network_push__b3(void) +{ + const char *specs[] = { "refs/heads/b3:refs/heads/b3" }; + push_status exp_stats[] = { { "refs/heads/b3", NULL } }; + expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0); +} + +void test_network_push__b4(void) +{ + const char *specs[] = { "refs/heads/b4:refs/heads/b4" }; + push_status exp_stats[] = { { "refs/heads/b4", NULL } }; + expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0); +} + +void test_network_push__b5(void) +{ + const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; + push_status exp_stats[] = { { "refs/heads/b5", NULL } }; + expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0); +} + +void test_network_push__multi(void) +{ + const char *specs[] = { + "refs/heads/b1:refs/heads/b1", + "refs/heads/b2:refs/heads/b2", + "refs/heads/b3:refs/heads/b3", + "refs/heads/b4:refs/heads/b4", + "refs/heads/b5:refs/heads/b5" + }; + push_status exp_stats[] = { + { "refs/heads/b1", NULL }, + { "refs/heads/b2", NULL }, + { "refs/heads/b3", NULL }, + { "refs/heads/b4", NULL }, + { "refs/heads/b5", NULL } + }; + expected_ref exp_refs[] = { + { "refs/heads/b1", &_oid_b1 }, + { "refs/heads/b2", &_oid_b2 }, + { "refs/heads/b3", &_oid_b3 }, + { "refs/heads/b4", &_oid_b4 }, + { "refs/heads/b5", &_oid_b5 } + }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0); +} + +void test_network_push__implicit_tgt(void) +{ + const char *specs1[] = { "refs/heads/b1:" }; + push_status exp_stats1[] = { { "refs/heads/b1", NULL } }; + expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } }; + + const char *specs2[] = { "refs/heads/b2:" }; + push_status exp_stats2[] = { { "refs/heads/b2", NULL } }; + expected_ref exp_refs2[] = { + { "refs/heads/b1", &_oid_b1 }, + { "refs/heads/b2", &_oid_b2 } + }; + + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0); + do_push(specs2, ARRAY_SIZE(specs2), + exp_stats2, ARRAY_SIZE(exp_stats2), + exp_refs2, ARRAY_SIZE(exp_refs2), 0); +} + +void test_network_push__fast_fwd(void) +{ + /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */ + + const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" }; + push_status exp_stats_init[] = { { "refs/heads/b1", NULL } }; + expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } }; + + const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" }; + push_status exp_stats_ff[] = { { "refs/heads/b1", NULL } }; + expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } }; + + /* Do a force push to reset b1 in target back to _oid_b1 */ + const char *specs_reset[] = { "+refs/heads/b1:refs/heads/b1" }; + /* Force should have no effect on a fast forward push */ + const char *specs_ff_force[] = { "+refs/heads/b6:refs/heads/b1" }; + + do_push(specs_init, ARRAY_SIZE(specs_init), + exp_stats_init, ARRAY_SIZE(exp_stats_init), + exp_refs_init, ARRAY_SIZE(exp_refs_init), 0); + + do_push(specs_ff, ARRAY_SIZE(specs_ff), + exp_stats_ff, ARRAY_SIZE(exp_stats_ff), + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0); + + do_push(specs_reset, ARRAY_SIZE(specs_reset), + exp_stats_init, ARRAY_SIZE(exp_stats_init), + exp_refs_init, ARRAY_SIZE(exp_refs_init), 0); + + do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force), + exp_stats_ff, ARRAY_SIZE(exp_stats_ff), + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0); +} + +void test_network_push__force(void) +{ + const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"}; + push_status exp_stats1[] = { { "refs/heads/tgt", NULL } }; + expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } }; + + const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"}; + + const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"}; + push_status exp_stats2_force[] = { { "refs/heads/tgt", NULL } }; + expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } }; + + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0); + + do_push(specs2, ARRAY_SIZE(specs2), + NULL, 0, + exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD); + + /* Non-fast-forward update with force should pass. */ + do_push(specs2_force, ARRAY_SIZE(specs2_force), + exp_stats2_force, ARRAY_SIZE(exp_stats2_force), + exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0); +} + +void test_network_push__delete(void) +{ + const char *specs1[] = { + "refs/heads/b1:refs/heads/tgt1", + "refs/heads/b1:refs/heads/tgt2" + }; + push_status exp_stats1[] = { + { "refs/heads/tgt1", NULL }, + { "refs/heads/tgt2", NULL } + }; + expected_ref exp_refs1[] = { + { "refs/heads/tgt1", &_oid_b1 }, + { "refs/heads/tgt2", &_oid_b1 } + }; + + const char *specs_del_fake[] = { ":refs/heads/fake" }; + /* Force has no effect for delete. */ + const char *specs_del_fake_force[] = { "+:refs/heads/fake" }; + + const char *specs_delete[] = { ":refs/heads/tgt1" }; + push_status exp_stats_delete[] = { { "refs/heads/tgt1", NULL } }; + expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } }; + /* Force has no effect for delete. */ + const char *specs_delete_force[] = { "+:refs/heads/tgt1" }; + + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0); + + /* Deleting a non-existent branch should fail before the request is sent to + * the server because the client cannot find the old oid for the ref. + */ + do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake), + NULL, 0, + exp_refs1, ARRAY_SIZE(exp_refs1), -1); + do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force), + NULL, 0, + exp_refs1, ARRAY_SIZE(exp_refs1), -1); + + /* Delete one of the pushed branches. */ + do_push(specs_delete, ARRAY_SIZE(specs_delete), + exp_stats_delete, ARRAY_SIZE(exp_stats_delete), + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0); + + /* Re-push branches and retry delete with force. */ + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0); + do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force), + exp_stats_delete, ARRAY_SIZE(exp_stats_delete), + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0); +} + +void test_network_push__bad_refspecs(void) +{ + /* All classes of refspecs that should be rejected by + * git_push_add_refspec() should go in this test. + */ + git_push *push; + + if (_remote) { + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH)); + cl_git_pass(git_push_new(&push, _remote)); + + /* Unexpanded branch names not supported */ + cl_git_fail(git_push_add_refspec(push, "b6:b6")); + + git_push_free(push); + } +} + +void test_network_push__expressions(void) +{ + /* TODO: Expressions in refspecs doesn't actually work yet */ + const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" }; + + const char *specs_right_expr[] = { "refs/heads/b2:refs/heads/b2~1" }; + push_status exp_stats_right_expr[] = { { "refs/heads/b2~1", "funny refname" } }; + + /* TODO: Find a more precise way of checking errors than a exit code of -1. */ + do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr), + NULL, 0, + NULL, 0, -1); + + do_push(specs_right_expr, ARRAY_SIZE(specs_right_expr), + exp_stats_right_expr, ARRAY_SIZE(exp_stats_right_expr), + NULL, 0, 0); +} diff --git a/tests-clar/network/push_util.c b/tests-clar/network/push_util.c new file mode 100644 index 000000000..2e457844d --- /dev/null +++ b/tests-clar/network/push_util.c @@ -0,0 +1,126 @@ + +#include "clar_libgit2.h" +#include "buffer.h" +#include "vector.h" +#include "push_util.h" + +const git_oid OID_ZERO = {{ 0 }}; + +void updated_tip_free(updated_tip *t) +{ + git__free(t->name); + git__free(t->old_oid); + git__free(t->new_oid); + git__free(t); +} + +void record_callbacks_data_clear(record_callbacks_data *data) +{ + size_t i; + updated_tip *tip; + + git_vector_foreach(&data->updated_tips, i, tip) + updated_tip_free(tip); + + git_vector_free(&data->updated_tips); +} + +int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) +{ + updated_tip *t; + record_callbacks_data *record_data = (record_callbacks_data *)data; + + cl_assert(t = git__malloc(sizeof(*t))); + + cl_assert(t->name = git__strdup(refname)); + cl_assert(t->old_oid = git__malloc(sizeof(*t->old_oid))); + git_oid_cpy(t->old_oid, a); + + cl_assert(t->new_oid = git__malloc(sizeof(*t->new_oid))); + git_oid_cpy(t->new_oid, b); + + git_vector_insert(&record_data->updated_tips, t); + + return 0; +} + +int delete_ref_cb(git_remote_head *head, void *payload) +{ + git_vector *delete_specs = (git_vector *)payload; + git_buf del_spec = GIT_BUF_INIT; + + /* Ignore malformed ref names (which also saves us from tag^{} */ + if (!git_reference_is_valid_name(head->name)) + return 0; + + /* Create a refspec that deletes a branch in the remote */ + if (strcmp(head->name, "refs/heads/master")) { + cl_git_pass(git_buf_putc(&del_spec, ':')); + cl_git_pass(git_buf_puts(&del_spec, head->name)); + cl_git_pass(git_vector_insert(delete_specs, git_buf_detach(&del_spec))); + } + + return 0; +} + +int record_ref_cb(git_remote_head *head, void *payload) +{ + git_vector *refs = (git_vector *) payload; + return git_vector_insert(refs, head); +} + +void verify_remote_refs(git_vector *actual_refs, const expected_ref expected_refs[], size_t expected_refs_len) +{ + size_t i, j = 0; + git_buf msg = GIT_BUF_INIT; + git_remote_head *actual; + char *oid_str; + bool master_present = false; + + /* We don't care whether "master" is present on the other end or not */ + git_vector_foreach(actual_refs, i, actual) { + if (!strcmp(actual->name, "refs/heads/master")) { + master_present = true; + break; + } + } + + if (expected_refs_len + (master_present ? 1 : 0) != actual_refs->length) + goto failed; + + git_vector_foreach(actual_refs, i, actual) { + if (master_present && !strcmp(actual->name, "refs/heads/master")) + continue; + + if (strcmp(expected_refs[j].name, actual->name) || + git_oid_cmp(expected_refs[j].oid, &actual->oid)) + goto failed; + + j++; + } + + return; + +failed: + git_buf_puts(&msg, "Expected and actual refs differ:\nEXPECTED:\n"); + + for(i = 0; i < expected_refs_len; i++) { + cl_assert(oid_str = git_oid_allocfmt(expected_refs[i].oid)); + cl_git_pass(git_buf_printf(&msg, "%s = %s\n", expected_refs[i].name, oid_str)); + git__free(oid_str); + } + + git_buf_puts(&msg, "\nACTUAL:\n"); + git_vector_foreach(actual_refs, i, actual) { + if (master_present && !strcmp(actual->name, "refs/heads/master")) + continue; + + cl_assert(oid_str = git_oid_allocfmt(&actual->oid)); + cl_git_pass(git_buf_printf(&msg, "%s = %s\n", actual->name, oid_str)); + git__free(oid_str); + } + + cl_fail(git_buf_cstr(&msg)); + + git_buf_free(&msg); +} diff --git a/tests-clar/network/push_util.h b/tests-clar/network/push_util.h new file mode 100644 index 000000000..2f4dffce4 --- /dev/null +++ b/tests-clar/network/push_util.h @@ -0,0 +1,68 @@ +#ifndef INCLUDE_cl_push_util_h__ +#define INCLUDE_cl_push_util_h__ + +#include "git2/oid.h" + +/* Constant for zero oid */ +extern const git_oid OID_ZERO; + +/** + * Macro for initializing git_remote_callbacks to use test helpers that + * record data in a record_callbacks_data instance. + * @param data pointer to a record_callbacks_data instance + */ +#define RECORD_CALLBACKS_INIT(data) { NULL, NULL, record_update_tips_cb, data } + +typedef struct { + char *name; + git_oid *old_oid; + git_oid *new_oid; +} updated_tip; + +typedef struct { + git_vector updated_tips; +} record_callbacks_data; + +typedef struct { + const char *name; + const git_oid *oid; +} expected_ref; + +void updated_tip_free(updated_tip *t); + +void record_callbacks_data_clear(record_callbacks_data *data); + +/** + * Callback for git_remote_update_tips that records updates + * + * @param data (git_vector *) of updated_tip instances + */ +int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data); + +/** + * Callback for git_remote_list that adds refspecs to delete each ref + * + * @param head a ref on the remote + * @param payload a git_push instance + */ +int delete_ref_cb(git_remote_head *head, void *payload); + +/** + * Callback for git_remote_list that adds refspecs to vector + * + * @param head a ref on the remote + * @param payload (git_vector *) of git_remote_head instances + */ +int record_ref_cb(git_remote_head *head, void *payload); + +/** + * Verifies that refs on remote stored by record_ref_cb match the expected + * names, oids, and order. + * + * @param actual_refs actual refs stored by record_ref_cb() + * @param expected_refs expected remote refs + * @param expected_refs_len length of expected_refs + */ +void verify_remote_refs(git_vector *actual_refs, const expected_ref expected_refs[], size_t expected_refs_len); + +#endif /* INCLUDE_cl_push_util_h__ */ diff --git a/tests-clar/object/lookup.c b/tests-clar/object/lookup.c index 01435bc04..cfa6d4678 100644 --- a/tests-clar/object/lookup.c +++ b/tests-clar/object/lookup.c @@ -62,3 +62,4 @@ void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void) cl_assert_equal_i( GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG)); } + diff --git a/tests-clar/resources/push.sh b/tests-clar/resources/push.sh new file mode 100644 index 000000000..607117675 --- /dev/null +++ b/tests-clar/resources/push.sh @@ -0,0 +1,55 @@ +#!/bin/sh +#creates push_src repo for libgit2 push tests. +set -eu + +#Create src repo for push +mkdir push_src +pushd push_src + git init + + echo a > a.txt + git add . + git commit -m 'added a.txt' + + mkdir fold + echo b > fold/b.txt + git add . + git commit -m 'added fold and fold/b.txt' + + git branch b1 #b1 and b2 are the same + git branch b2 + + git checkout -b b3 + echo edit >> a.txt + git add . + git commit -m 'edited a.txt' + + git checkout -b b4 master + echo edit >> fold\b.txt + git add . + git commit -m 'edited fold\b.txt' + + git checkout -b b5 master + git submodule add ../testrepo.git submodule + git commit -m "added submodule named 'submodule' pointing to '../testrepo.git'" + + git checkout master + git merge -m "merge b3, b4, and b5 to master" b3 b4 b5 + + #Log commits to include in testcase + git log --format=oneline --decorate --graph + #*-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, master) merge b3, b4, and b5 to master + #|\ \ + #| | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git' + #| * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt + #| |/ + #* | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt + #|/ + #* a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt + #* 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt + + #fix paths so that we can add repo folders under libgit2 repo + #rename .git to .gitted + find . -name .git -exec mv -i '{}' '{}ted' \; + mv -i .gitmodules gitmodules +popd diff --git a/tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG b/tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG new file mode 100644 index 000000000..b1295084c --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +added submodule named 'submodule' pointing to '../testrepo.git' diff --git a/tests-clar/resources/push_src/.gitted/HEAD b/tests-clar/resources/push_src/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/push_src/.gitted/ORIG_HEAD b/tests-clar/resources/push_src/.gitted/ORIG_HEAD new file mode 100644 index 000000000..afadf9d26 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/ORIG_HEAD @@ -0,0 +1 @@ +a78705c3b2725f931d3ee05348d83cc26700f247 diff --git a/tests-clar/resources/push_src/.gitted/config b/tests-clar/resources/push_src/.gitted/config new file mode 100644 index 000000000..51de0311b --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[submodule "submodule"] + url = m:/dd/libgit2/tests-clar/resources/testrepo.git diff --git a/tests-clar/resources/push_src/.gitted/description b/tests-clar/resources/push_src/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/push_src/.gitted/index b/tests-clar/resources/push_src/.gitted/index new file mode 100644 index 000000000..0ef6594b3 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/index differ diff --git a/tests-clar/resources/push_src/.gitted/info/exclude b/tests-clar/resources/push_src/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/push_src/.gitted/logs/HEAD b/tests-clar/resources/push_src/.gitted/logs/HEAD new file mode 100644 index 000000000..4ef336f84 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/logs/HEAD @@ -0,0 +1,10 @@ +0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu 1352923200 -0500 commit (initial): added a.txt +5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923200 -0500 commit: added fold and fold/b.txt +a78705c3b2725f931d3ee05348d83cc26700f247 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923201 -0500 checkout: moving from master to b3 +a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu 1352923201 -0500 commit: edited a.txt +d9b63a88223d8367516f50bd131a5f7349b7f3e4 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923201 -0500 checkout: moving from b3 to b4 +a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu 1352923201 -0500 commit: edited fold\b.txt +27b7ce66243eb1403862d05f958c002312df173d a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923201 -0500 checkout: moving from b4 to b5 +a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git' +fa38b91f199934685819bea316186d8b008c52a2 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923207 -0500 checkout: moving from b5 to master +a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy. diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b1 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b1 new file mode 100644 index 000000000..390a03d5c --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b1 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923200 -0500 branch: Created from master diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b2 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b2 new file mode 100644 index 000000000..390a03d5c --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b2 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923200 -0500 branch: Created from master diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b3 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b3 new file mode 100644 index 000000000..01e302c44 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b3 @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923201 -0500 branch: Created from HEAD +a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu 1352923201 -0500 commit: edited a.txt diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b4 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b4 new file mode 100644 index 000000000..7afddc54e --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b4 @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923201 -0500 branch: Created from master +a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu 1352923201 -0500 commit: edited fold\b.txt diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b5 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b5 new file mode 100644 index 000000000..bc22567f7 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b5 @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923201 -0500 branch: Created from master +a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git' diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/master b/tests-clar/resources/push_src/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..8aafa9ca4 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu 1352923200 -0500 commit (initial): added a.txt +5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu 1352923200 -0500 commit: added fold and fold/b.txt +a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy. diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/HEAD b/tests-clar/resources/push_src/.gitted/modules/submodule/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/config b/tests-clar/resources/push_src/.gitted/modules/submodule/config new file mode 100644 index 000000000..59810077d --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/config @@ -0,0 +1,15 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + worktree = ../../../submodule + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = m:/dd/libgit2/tests-clar/resources/testrepo.git +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/description b/tests-clar/resources/push_src/.gitted/modules/submodule/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/index b/tests-clar/resources/push_src/.gitted/modules/submodule/index new file mode 100644 index 000000000..8e44080f3 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/index differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude b/tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD new file mode 100644 index 000000000..aedcdf295 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master new file mode 100644 index 000000000..aedcdf295 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD new file mode 100644 index 000000000..aedcdf295 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 new file mode 100644 index 000000000..d1c032fce Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 new file mode 100644 index 000000000..cedb2a22e Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 new file mode 100644 index 000000000..93a16f146 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd new file mode 100644 index 000000000..ba0bfb30c Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd new file mode 100644 index 000000000..3ec541288 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 new file mode 100644 index 000000000..6048d4bad Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b new file mode 100644 index 000000000..225c45734 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 new file mode 100644 index 000000000..cb1ed5712 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d new file mode 100644 index 000000000..df40d99af Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 new file mode 100644 index 000000000..0a1500a6f Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 new file mode 100644 index 000000000..321eaa867 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc new file mode 100644 index 000000000..9bb5b623b Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 new file mode 100644 index 000000000..7ca4ceed5 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 new file mode 100644 index 000000000..8953b6cef --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 @@ -0,0 +1,2 @@ +xQ +0D)6ͦ "xO-FbEo0 Ǥ,ske[Pn8R,EpD?g}^3 <GhYK8ЖDA);gݧjp4-r;sGA4ۺ=(in7IKFE \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea new file mode 100644 index 000000000..b4e5aa186 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 new file mode 100644 index 000000000..351cff823 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 new file mode 100644 index 000000000..c1f22c54f --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 @@ -0,0 +1,2 @@ +x 1ENi@k2 "X$YW0YcÅszMD08!s Xgd::@X0Pw"F/RUzmZZV}|/o5I!1z:vUim}/> +F- \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a new file mode 100644 index 000000000..2ef4faa0f Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af new file mode 100644 index 000000000..716b0c64b --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af @@ -0,0 +1 @@ +xAj!?009o}H6}jUPPZ&Y AԛpFdpz[fYPqLJ.,Z`Ů.`v q $5+9Ot>/DE/龡W*eVdf1>覭ěʙFThk.i^0?PR, \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 new file mode 100644 index 000000000..23c462f34 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d new file mode 100644 index 000000000..2f9b6b6e3 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 new file mode 100644 index 000000000..5df58dda5 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe new file mode 100644 index 000000000..71019a636 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe @@ -0,0 +1,2 @@ +xM F]s41x(IKݽ/_P@!8)es + N&FGSƄh{+CZzvF7Z-kx\[P8GK/^ l>.4 \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 new file mode 100644 index 000000000..4cc3f4dff --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 @@ -0,0 +1 @@ +x+)JMU044b040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3Py`%E\&g|0{Ӎ1X \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 new file mode 100644 index 000000000..bf7b2bb68 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 differ diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 new file mode 100644 index 000000000..7f1cfb23c --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 @@ -0,0 +1,2 @@ +xM +0F]d2;XEȎ5R AE &n}ZA \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a new file mode 100644 index 000000000..a79612435 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a @@ -0,0 +1,3 @@ +x[ +0E*fդ "W0-Ft݁pS[Yx^ +Db CLhut}8X*4ZsYUA X3RM) s6輢Mរ&Jm;}<\@ޏpĀv?jۺL?H \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f new file mode 100644 index 000000000..f8588696b --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f @@ -0,0 +1,2 @@ +x;j1Dmdǎ|M3`V{ >QvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵ IKNiZ%S + U~̽>' w [ DGڡQ-M>dO}\8g_ШoYr \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 new file mode 100644 index 000000000..29c8e824d --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 @@ -0,0 +1,3 @@ +xQ +!@sBQ" ٱ r{RȞyE + mH&Er7S!*u΄2>#\V8|Gt-ybhFU/J-|M}W+GK \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 b/tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 new file mode 100644 index 000000000..dc10f6831 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 b/tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 new file mode 100644 index 000000000..45c4d9208 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 b/tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 new file mode 100644 index 000000000..883182138 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 b/tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 new file mode 100644 index 000000000..586bf17a4 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d b/tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d new file mode 100644 index 000000000..bcaaa91c2 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d differ diff --git a/tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 b/tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 new file mode 100644 index 000000000..e814d07b0 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce b/tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce new file mode 100644 index 000000000..596cd43de --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce @@ -0,0 +1,2 @@ +x[J0}*f2$Eim +\8:DfZ &65^,%lm١~;KJRXT蕄(!{¯DZqPqsPӈB\;EEgs`IeoE(YMFC9uu>|#?i.׻qD90FEENzܶ<\,\a_a.gd \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 b/tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 new file mode 100644 index 000000000..6ad835e86 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd b/tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd new file mode 100644 index 000000000..4e650aaa1 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd differ diff --git a/tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 b/tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 new file mode 100644 index 000000000..fcb2b32f3 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c b/tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c new file mode 100644 index 000000000..ad4272638 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c differ diff --git a/tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 b/tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 new file mode 100644 index 000000000..b471e2155 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 @@ -0,0 +1,2 @@ +xA +0E]sd$ "xi2ՂmzwSErgj ![͎%wbY(zC/G\tw({r$C`Y[=DCu&V8!s=]8ޛT#|;ltWhfM}UQDM \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 b/tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 new file mode 100644 index 000000000..9f6b1502f Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 b/tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 new file mode 100644 index 000000000..b9813576d Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a b/tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a new file mode 100644 index 000000000..888354fc2 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a differ diff --git a/tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 b/tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 new file mode 100644 index 000000000..13d9bca20 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 @@ -0,0 +1,2 @@ +xJ15>urrneo {f[)_DmIi7 +7Ĩ8ꔌ.n܃W)_T;x,(li[D\K墓XΓP?>W~|_Wؤxs6IcJNP}~ -מ/󄳭G X \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 b/tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 new file mode 100644 index 000000000..1cdc048c0 Binary files /dev/null and b/tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/pack/dummy b/tests-clar/resources/push_src/.gitted/objects/pack/dummy new file mode 100644 index 000000000..e69de29bb diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b1 b/tests-clar/resources/push_src/.gitted/refs/heads/b1 new file mode 100644 index 000000000..afadf9d26 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/heads/b1 @@ -0,0 +1 @@ +a78705c3b2725f931d3ee05348d83cc26700f247 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b2 b/tests-clar/resources/push_src/.gitted/refs/heads/b2 new file mode 100644 index 000000000..afadf9d26 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/heads/b2 @@ -0,0 +1 @@ +a78705c3b2725f931d3ee05348d83cc26700f247 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b3 b/tests-clar/resources/push_src/.gitted/refs/heads/b3 new file mode 100644 index 000000000..3056bb436 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/heads/b3 @@ -0,0 +1 @@ +d9b63a88223d8367516f50bd131a5f7349b7f3e4 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b4 b/tests-clar/resources/push_src/.gitted/refs/heads/b4 new file mode 100644 index 000000000..efed6f064 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/heads/b4 @@ -0,0 +1 @@ +27b7ce66243eb1403862d05f958c002312df173d diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b5 b/tests-clar/resources/push_src/.gitted/refs/heads/b5 new file mode 100644 index 000000000..cf313ad05 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/heads/b5 @@ -0,0 +1 @@ +fa38b91f199934685819bea316186d8b008c52a2 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b6 b/tests-clar/resources/push_src/.gitted/refs/heads/b6 new file mode 100644 index 000000000..711e466ae --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/heads/b6 @@ -0,0 +1 @@ +951bbbb90e2259a4c8950db78946784fb53fcbce diff --git a/tests-clar/resources/push_src/a.txt b/tests-clar/resources/push_src/a.txt new file mode 100644 index 000000000..f7eac1c51 --- /dev/null +++ b/tests-clar/resources/push_src/a.txt @@ -0,0 +1,2 @@ +a +edit diff --git a/tests-clar/resources/push_src/fold/b.txt b/tests-clar/resources/push_src/fold/b.txt new file mode 100644 index 000000000..617807982 --- /dev/null +++ b/tests-clar/resources/push_src/fold/b.txt @@ -0,0 +1 @@ +b diff --git a/tests-clar/resources/push_src/foldb.txt b/tests-clar/resources/push_src/foldb.txt new file mode 100644 index 000000000..5b38718be --- /dev/null +++ b/tests-clar/resources/push_src/foldb.txt @@ -0,0 +1 @@ +edit diff --git a/tests-clar/resources/push_src/gitmodules b/tests-clar/resources/push_src/gitmodules new file mode 100644 index 000000000..f1734dfc1 --- /dev/null +++ b/tests-clar/resources/push_src/gitmodules @@ -0,0 +1,3 @@ +[submodule "submodule"] + path = submodule + url = ../testrepo.git diff --git a/tests-clar/resources/push_src/submodule/.gitted b/tests-clar/resources/push_src/submodule/.gitted new file mode 100644 index 000000000..3ffcf960a --- /dev/null +++ b/tests-clar/resources/push_src/submodule/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/submodule diff --git a/tests-clar/resources/push_src/submodule/README b/tests-clar/resources/push_src/submodule/README new file mode 100644 index 000000000..ca8c64728 --- /dev/null +++ b/tests-clar/resources/push_src/submodule/README @@ -0,0 +1 @@ +hey there diff --git a/tests-clar/resources/push_src/submodule/branch_file.txt b/tests-clar/resources/push_src/submodule/branch_file.txt new file mode 100644 index 000000000..a26902575 --- /dev/null +++ b/tests-clar/resources/push_src/submodule/branch_file.txt @@ -0,0 +1,2 @@ +hi +bye! diff --git a/tests-clar/resources/push_src/submodule/new.txt b/tests-clar/resources/push_src/submodule/new.txt new file mode 100644 index 000000000..8e0884e36 --- /dev/null +++ b/tests-clar/resources/push_src/submodule/new.txt @@ -0,0 +1 @@ +my new file