diff options
-rw-r--r-- | include/git2.h | 1 | ||||
-rw-r--r-- | include/git2/errors.h | 1 | ||||
-rw-r--r-- | include/git2/object.h | 8 | ||||
-rw-r--r-- | include/git2/push.h | 80 | ||||
-rw-r--r-- | include/git2/remote.h | 15 | ||||
-rw-r--r-- | include/git2/types.h | 1 | ||||
-rw-r--r-- | src/common.h | 2 | ||||
-rw-r--r-- | src/errors.c | 45 | ||||
-rw-r--r-- | src/netops.c | 4 | ||||
-rw-r--r-- | src/object.c | 13 | ||||
-rw-r--r-- | src/pkt.c | 94 | ||||
-rw-r--r-- | src/pkt.h | 19 | ||||
-rw-r--r-- | src/protocol.c | 5 | ||||
-rw-r--r-- | src/push.c | 653 | ||||
-rw-r--r-- | src/reflog.h | 2 | ||||
-rw-r--r-- | src/remote.c | 5 | ||||
-rw-r--r-- | src/transport.h | 13 | ||||
-rw-r--r-- | src/transports/git.c | 8 | ||||
-rw-r--r-- | src/transports/http.c | 192 | ||||
-rw-r--r-- | tests-clar/object/lookup.c | 12 |
20 files changed, 1120 insertions, 53 deletions
diff --git a/include/git2.h b/include/git2.h index d55543986..95475c591 100644 --- a/include/git2.h +++ b/include/git2.h @@ -39,6 +39,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/errors.h b/include/git2/errors.h index 38b7fe0ae..b4463f722 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -28,6 +28,7 @@ enum { GIT_EUSER = -7, GIT_EBAREREPO = -8, GIT_EORPHANEDHEAD = -9, + GIT_ENONFASTFORWARD = -10, GIT_PASSTHROUGH = -30, GIT_ITEROVER = -31, diff --git a/include/git2/object.h b/include/git2/object.h index fd6ae95c1..44bfc5e29 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -95,6 +95,14 @@ GIT_EXTERN(const git_oid *) git_object_id(const git_object *obj); GIT_EXTERN(git_otype) git_object_type(const git_object *obj); /** + * Get the object type of an object id + * + * @param obj the repository object + * @return the object's type + */ +GIT_EXTERN(int) git_object_oid2type(git_otype *type, git_repository *repo, const git_oid *oid); + +/** * Get the repository that owns this object * * Freeing or calling `git_repository_close` on the 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/remote.h b/include/git2/remote.h index 6471acc6a..3ecdbc4d6 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -189,6 +189,12 @@ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); /** + * + * + */ +GIT_EXTERN(int) git_remote_push(git_remote *remote); + +/** * Check whether the remote is connected * * Check whether the remote's underlying transport is connected to the @@ -291,6 +297,14 @@ typedef enum git_remote_completion_type { } git_remote_completion_type; /** + * Auth data for HTTP authentication. + */ +typedef struct http_auth_data { + char *username; + char *password; +} http_auth_data; + +/** * The callback settings structure * * Set the calbacks to be called by the remote. @@ -299,6 +313,7 @@ struct git_remote_callbacks { void (*progress)(const char *str, int len, void *data); int (*completion)(git_remote_completion_type type, void *data); int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + int (*http_auth)(http_auth_data *auth_data, void *data); void *data; }; diff --git a/include/git2/types.h b/include/git2/types.h index 01ddbf3d6..7f92506d3 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -191,6 +191,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/common.h b/src/common.h index 747bbf7ce..007b2e3ea 100644 --- a/src/common.h +++ b/src/common.h @@ -49,6 +49,8 @@ #include <regex.h> +#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" + /** * Check a pointer allocation result, returning -1 if it failed. */ diff --git a/src/errors.c b/src/errors.c index 942a2f799..12adb42af 100644 --- a/src/errors.c +++ b/src/errors.c @@ -9,6 +9,10 @@ #include "posix.h" #include "buffer.h" #include <stdarg.h> +#if GIT_WINHTTP +# include <winhttp.h> +# pragma comment(lib, "winhttp.lib") +#endif /******************************************** * New error handling @@ -62,31 +66,42 @@ void giterr_set(int error_class, const char *string, ...) va_end(arglist); /* automatically suffix strerror(errno) for GITERR_OS errors */ - if (error_class == GITERR_OS) { - - if (unix_error_code != 0) { - git_buf_PUTS(&buf, ": "); - git_buf_puts(&buf, strerror(unix_error_code)); - } + if (unix_error_code != 0) { + git_buf_PUTS(&buf, ": "); + git_buf_puts(&buf, strerror(unix_error_code)); + } #ifdef GIT_WIN32 - else if (win32_error_code != 0) { - LPVOID lpMsgBuf = NULL; + else if (win32_error_code != 0) { + LPTSTR lpMsgBuf = NULL; +#ifdef GIT_WINHTTP + /* WinHttp error codes exist in winhttp.dll rather than the system message table */ + if (win32_error_code >= WINHTTP_ERROR_BASE && win32_error_code <= WINHTTP_ERROR_LAST) { + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + GetModuleHandle(TEXT("winhttp.dll")), win32_error_code, 0, (LPTSTR)&lpMsgBuf, 0, NULL); + } else { +#endif FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, win32_error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL); - - if (lpMsgBuf) { - git_buf_PUTS(&buf, ": "); - git_buf_puts(&buf, lpMsgBuf); - LocalFree(lpMsgBuf); - } + NULL, win32_error_code, 0, (LPTSTR)&lpMsgBuf, 0, NULL); +#ifdef GIT_WINHTTP } #endif + + if (lpMsgBuf) { + git_buf_PUTS(&buf, ": "); + git_buf_puts(&buf, lpMsgBuf); + LocalFree(lpMsgBuf); + } } +#endif if (!git_buf_oom(&buf)) set_error(error_class, git_buf_detach(&buf)); diff --git a/src/netops.c b/src/netops.c index df502e619..675b78273 100644 --- a/src/netops.c +++ b/src/netops.c @@ -542,10 +542,11 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port) { - char *colon, *slash, *delim; + char *colon, *slash, *delim, *at; colon = strchr(url, ':'); slash = strchr(url, '/'); + at = strchr(url, '@'); if (slash == NULL) { giterr_set(GITERR_NET, "Malformed URL: missing /"); @@ -560,6 +561,7 @@ int gitno_extract_host_and_port(char **host, char **port, const char *url, const GITERR_CHECK_ALLOC(*port); delim = colon == NULL ? slash : colon; + url = at ? at + 1 : url; *host = git__strndup(url, delim - url); GITERR_CHECK_ALLOC(*host); diff --git a/src/object.c b/src/object.c index 2e45eb86a..4b3c0f41a 100644 --- a/src/object.c +++ b/src/object.c @@ -413,3 +413,16 @@ int git_object_peel( git_object_free(deref); return -1; } + +int git_object_oid2type(git_otype *type, git_repository *repo, const git_oid *oid) +{ + git_object *obj; + + if (git_object_lookup(&obj, repo, oid, GIT_OBJ_ANY) < 0) + return -1; + + *type = git_object_type(obj); + + git_object_free(obj); + return 0; +} @@ -215,6 +215,83 @@ error_out: return error; } +static int ok_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ok *pkt; + 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); + 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; + 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); + 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); + 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]; @@ -312,6 +389,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); @@ -327,6 +410,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); } @@ -26,6 +26,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 */ @@ -80,6 +83,22 @@ 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; + int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); int git_pkt_buffer_flush(git_buf *buf); int git_pkt_send_flush(GIT_SOCKET s); diff --git a/src/protocol.c b/src/protocol.c index affad5173..8f14beaf0 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -101,6 +101,11 @@ int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps) 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, ' '); diff --git a/src/push.c b/src/push.c new file mode 100644 index 000000000..ec447df68 --- /dev/null +++ b/src/push.c @@ -0,0 +1,653 @@ +/* + * 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 "common.h" +#include "pack.h" +#include "pack-objects.h" +#include "pkt.h" +#include "remote.h" +#include "transport.h" +#include "vector.h" + +#include "git2/commit.h" +#include "git2/index.h" +#include "git2/merge.h" +#include "git2/pack.h" +#include "git2/push.h" +#include "git2/remote.h" +#include "git2/revwalk.h" +#include "git2/tree.h" +#include "git2/version.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; + git_transport_caps caps; + + /* report-status */ + bool unpack_ok; + git_vector status; +}; + +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->caps.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; + } + +#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 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->caps.report_status) + len += strlen(GIT_CAP_REPORT_STATUS); + } + + if (spec->lref) { + if (git_reference_name_to_oid( + &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. + */ + len += strlen(spec->lref); + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->lref, head->name)) { + /* + * Update remote reference + */ + git_oid_cpy(&spec->roid, &head->oid); + 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->lref); + + break; + } + } + + 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->lref); + } + } else { + /* + * Remote reference given; update the given + * reference or create it. + */ + len += strlen(spec->rref); + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->rref, head->name)) { + /* + * Update remote reference + */ + git_oid_cpy(&spec->roid, &head->oid); + 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); + + break; + } + } + + 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); + } + } + + } 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->caps.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 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) { + 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 = -1; + + if (git_vector_init(&commits, 0, NULL) < 0) + return -1; + + if (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 (git_packbuilder_insert(push->pb, o, NULL) < 0) + goto on_error; + } + + git_vector_foreach(&commits, i, o) { + git_object *obj; + + if (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 (git_packbuilder_insert_tree(push->pb, + git_commit_tree_oid((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"); + 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 do_push(git_push *push) +{ + git_transport *t = push->remote->transport; + git_buf pktline = GIT_BUF_INIT; + + if (gen_pktline(&pktline, push) < 0) + goto on_error; + +#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 + + /* + * 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 (git_packbuilder_new(&push->pb, push->repo) < 0) + goto on_error; + + if (queue_objects(push) < 0) + goto on_error; + + if (t->rpc) { + git_buf pack = GIT_BUF_INIT; + + if (git_packbuilder_write_buf(&pack, push->pb) < 0) + goto on_error; + + if (t->push(t, &pktline, &pack) < 0) { + git_buf_free(&pack); + goto on_error; + } + + git_buf_free(&pack); + } else { + if (gitno_send(push->remote->transport, + pktline.ptr, pktline.size, 0) < 0) + goto on_error; + + if (git_packbuilder_send(push->pb, push->remote->transport) < 0) + goto on_error; + } + + git_packbuilder_free(push->pb); + git_buf_free(&pktline); + return 0; + +on_error: + git_packbuilder_free(push->pb); + git_buf_free(&pktline); + return -1; +} + +static int parse_report(git_push *push) +{ + gitno_buffer *buf = &push->remote->transport->buffer; + 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(*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(*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 finish_push(git_push *push) +{ + int error = -1; + + if (push->caps.report_status && parse_report(push) < 0) + goto on_error; + + error = 0; + +on_error: + git_remote_disconnect(push->remote); + return error; +} + +static int cb_filter_refs(git_remote_head *ref, void *data) +{ + git_remote *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) +{ + if (!git_remote_connected(push->remote) && + git_remote_connect(push->remote, GIT_DIR_PUSH) < 0) + return -1; + + if (filter_refs(push->remote) < 0 || do_push(push) < 0) { + git_remote_disconnect(push->remote); + return -1; + } + + return finish_push(push); +} + +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/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 e05ea059f..b4eddbd18 100644 --- a/src/remote.c +++ b/src/remote.c @@ -430,6 +430,11 @@ int git_remote_connect(git_remote *remote, int direction) t->progress_cb = remote->callbacks.progress; t->cb_data = remote->callbacks.data; + if (t->rpc) { + if (remote->callbacks.http_auth) + git_transport_http_set_authcb(t, remote->callbacks.http_auth); + } + t->check_cert = remote->check_cert; if (t->connect(t, direction) < 0) { goto on_error; diff --git a/src/transport.h b/src/transport.h index 4c944b9e7..0152fcdfa 100644 --- a/src/transport.h +++ b/src/transport.h @@ -9,6 +9,9 @@ #include "git2/net.h" #include "git2/indexer.h" +#include "git2/remote.h" + +#include "buffer.h" #include "vector.h" #include "posix.h" #include "common.h" @@ -24,6 +27,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" typedef struct git_transport_caps { int common:1, @@ -31,7 +36,9 @@ typedef struct git_transport_caps { multi_ack: 1, side_band:1, side_band_64k:1, - include_tag:1; + include_tag:1, + delete_refs:1, + report_status:1; } git_transport_caps; #ifdef GIT_SSL @@ -104,7 +111,7 @@ struct git_transport { /** * Push the changes over */ - int (*push)(struct git_transport *transport); + int (*push)(struct git_transport *transport, git_buf *pktline, git_buf *pack); /** * Negotiate the minimal amount of objects that need to be * retrieved @@ -135,6 +142,8 @@ int git_transport_local(struct git_transport **transport); int git_transport_git(struct git_transport **transport); int git_transport_http(struct git_transport **transport); int git_transport_https(struct git_transport **transport); +void git_transport_http_set_authcb(struct git_transport *transport, + int (*auth_cb)(http_auth_data *auth_data, void *data)); int git_transport_dummy(struct git_transport **transport); /** diff --git a/src/transports/git.c b/src/transports/git.c index b757495c5..5d5f0c68a 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -106,7 +106,8 @@ static int do_connect(transport_git *t, const char *url) if (gitno_connect((git_transport *)t, host, port) < 0) goto on_error; - if (send_request((git_transport *)t, NULL, url) < 0) + if (send_request((git_transport *)t, + t->parent.direction ? "git-receive-pack" : NULL, url) < 0) goto on_error; git__free(host); @@ -129,11 +130,6 @@ static int git_connect(git_transport *transport, int direction) { transport_git *t = (transport_git *) transport; - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over git:// is not supported"); - return -1; - } - t->parent.direction = direction; /* Connect and ask for the refs */ diff --git a/src/transports/http.c b/src/transports/http.c index 93dd0c326..18fed17bc 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -41,7 +41,7 @@ typedef struct { int transfer_finished :1, ct_found :1, ct_finished :1, - pack_ready :1; + auth :1; enum last_cb last_cb; http_parser parser; char *content_type; @@ -49,6 +49,9 @@ typedef struct { char *host; char *port; char *service; + char *user; + char *pass; + int (*auth_cb)(http_auth_data *auth_data, void *data); char buffer[65536]; #ifdef GIT_WIN32 WSADATA wsd; @@ -60,9 +63,33 @@ typedef struct { #endif } transport_http; +static void setup_gitno_buffer(git_transport *transport); + + +static int base64_cred(git_buf *b64, const char *username, const char *password) +{ + char *raw; + int error; + + raw = git__malloc(strlen(username) + strlen(password) + 2); + GITERR_CHECK_ALLOC(raw); + + strcpy(raw, username); + strcat(raw, ":"); + strcat(raw, password); + + error = git_buf_put_base64(b64, raw, strlen(raw)); + git__free(raw); + + return error; +} + static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, - const char *service, ssize_t content_length, int ls) + const char *service, ssize_t content_length, int ls, + const char *username, const char *password) { + git_buf_clear(buf); + if (path == NULL) /* Is 'git fetch http://host.com/' valid? */ path = "/"; @@ -73,6 +100,14 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c } git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); git_buf_printf(buf, "Host: %s\r\n", host); + + if (username) { + git_buf_puts(buf, "Authorization: Basic "); + if (base64_cred(buf, username, password) < 0) + return -1; + git_buf_puts(buf, "\r\n"); + } + 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); @@ -88,34 +123,99 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c return 0; } -static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls) +static void reset_transport(transport_http *t) { + git__free(t->content_type); + t->parent.buffer.offset = 0; + t->error = 0; + t->ct_found = 0; + t->ct_finished = 0; + t->transfer_finished = 0; +} + +/* Start reading the response and check if we need to authenticate */ +static int http_authenticate(git_transport *transport) +{ + transport_http *t = (transport_http *) transport; + gitno_buffer *buf = &transport->buffer; + + if (gitno_recv(buf) < 0) { + if (t->auth) + return 1; + } + + return 0; +} + +static int send_request(transport_http *t, const char *service, void *data, size_t data_len, void *more_data, size_t more_data_len, int ls) +{ + git_transport *transport = (git_transport *)t; #ifndef GIT_WINHTTP git_buf request = GIT_BUF_INIT; const char *verb; - int error = -1; + int do_auth; verb = ls ? "GET" : "POST"; - /* Generate and send the HTTP request */ - if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); - return -1; - } + setup_gitno_buffer(transport); - if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0) - goto cleanup; + do { + /* + * Generate and send the HTTP request; repeat in + * case the server requires authorization. + */ + do_auth = 0; + + if (gen_request(&request, t->path, t->host, verb, service, + data_len + more_data_len, ls, t->user, t->pass) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } - if (content_length) { - if (gitno_send((git_transport *) t, data, content_length, 0) < 0) - goto cleanup; - } + if (gitno_send(transport, request.ptr, request.size, 0) < 0) { + git_buf_free(&request); + return -1; + } + git_buf_free(&request); - error = 0; + if (data_len) { + if (gitno_send(transport, data, data_len, 0) < 0) + return -1; + } + + if (more_data_len) { + if (gitno_send(transport, more_data, more_data_len, 0) < 0) + return -1; + } + + if (http_authenticate(transport)) { + http_auth_data auth_data; + + if (t->user) + git__free(t->user); + + if (t->pass) + git__free(t->pass); + + do_auth = 1; + if (t->auth_cb) { + if (t->auth_cb(&auth_data, NULL) < 0) + return -1; + + t->user = auth_data.username; + t->pass = auth_data.password; + reset_transport(t); + } else { + giterr_set(GITERR_NET, "Authorization required\n"); + return -1; + } + + } + + } while (do_auth); -cleanup: git_buf_free(&request); - return error; + return 0; #else wchar_t *verb; @@ -128,11 +228,14 @@ cleanup: L"*/*", NULL, }; + DWORD bytes_written; verb = ls ? L"GET" : L"POST"; buffer = data ? data : WINHTTP_NO_REQUEST_DATA; flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0; + setup_gitno_buffer(transport); + if (ls) git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service); else @@ -170,7 +273,14 @@ cleanup: } if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, - data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) { + data, (DWORD)data_len, (DWORD)(data_len + more_data_len), 0) == FALSE) { + giterr_set(GITERR_OS, "Failed to send request"); + goto on_error; + } + + + if (more_data_len && + WinHttpWriteData(t->request, more_data, more_data_len, &bytes_written) == FALSE) { giterr_set(GITERR_OS, "Failed to send request"); goto on_error; } @@ -307,7 +417,7 @@ static int on_headers_complete(http_parser *parser) git_buf *buf = &t->buf; /* The content-type is text/plain for 404, so don't validate */ - if (parser->status_code == 404) { + if (parser->status_code == 404 || parser->status_code == 401) { git_buf_clear(buf); return 0; } @@ -341,6 +451,12 @@ static int on_message_complete(http_parser *parser) t->error = -1; } + if (parser->status_code == 401) { + giterr_set(GITERR_NET, "Authentication required"); + t->error = -1; + t->auth = 1; + } + return 0; } @@ -437,10 +553,8 @@ static int http_connect(git_transport *transport, int direction) const char *default_port; git_pkt *pkt; - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); - return -1; - } + if (direction == GIT_DIR_PUSH) + service = "receive-pack"; t->parent.direction = direction; @@ -465,10 +579,9 @@ static int http_connect(git_transport *transport, int direction) if ((ret = do_connect(t)) < 0) goto cleanup; - if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0) + if ((ret = send_request(t, t->service, NULL, 0, NULL, 0, 1)) < 0) goto cleanup; - setup_gitno_buffer(transport); if ((ret = git_protocol_store_refs(transport, 2)) < 0) goto cleanup; @@ -476,6 +589,7 @@ static int http_connect(git_transport *transport, int direction) if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) { giterr_set(GITERR_NET, "Invalid HTTP response"); return t->error = -1; + } else { /* Remove the comment pkt from the list */ git_vector_remove(&transport->refs, 0); @@ -492,6 +606,17 @@ cleanup: return ret; } +static int http_push(struct git_transport *transport, git_buf *pktline, git_buf *pack) +{ + transport_http *t = (transport_http *) transport; + int ret; + + if ((ret = send_request(t, "receive-pack", pktline->ptr, pktline->size, pack->ptr, pack->size, 0)) < 0) + return ret; + + return gitno_recv(&transport->buffer); +} + static int http_negotiation_step(struct git_transport *transport, void *data, size_t len) { transport_http *t = (transport_http *) transport; @@ -501,7 +626,7 @@ static int http_negotiation_step(struct git_transport *transport, void *data, si if ((ret = do_connect(t)) < 0) return -1; - if (send_request(t, "upload-pack", data, len, 0) < 0) + if (send_request(t, "upload-pack", data, len, NULL, 0, 0) < 0) return -1; /* Then we need to set up the buffer to grab data from the HTTP response */ @@ -565,11 +690,23 @@ static void http_free(git_transport *transport) git__free(t->content_type); git__free(t->host); git__free(t->port); + git__free(t->user); + git__free(t->pass); git__free(t->service); git__free(t->parent.url); git__free(t); } +void git_transport_http_set_authcb(git_transport *transport, + int (*auth_cb)(http_auth_data *auth_data, void *data)) +{ + transport_http *t; + assert(transport); + + t = (transport_http *) transport; + t->auth_cb = auth_cb; +} + int git_transport_http(git_transport **out) { transport_http *t; @@ -580,6 +717,7 @@ int git_transport_http(git_transport **out) memset(t, 0x0, sizeof(transport_http)); t->parent.connect = http_connect; + t->parent.push = http_push; t->parent.negotiation_step = http_negotiation_step; t->parent.close = http_close; t->parent.free = http_free; diff --git a/tests-clar/object/lookup.c b/tests-clar/object/lookup.c index 7cbcc6140..179f153ad 100644 --- a/tests-clar/object/lookup.c +++ b/tests-clar/object/lookup.c @@ -61,3 +61,15 @@ 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)); } + +void test_object_lookup__lookup_object_type_by_oid(void) +{ + const char *commit = "e90810b8df3e80c413d903f631643c716887138d"; + git_oid oid; + git_otype type; + + cl_git_pass(git_oid_fromstr(&oid, commit)); + + cl_git_pass(git_object_oid2type(&type, g_repo, &oid)); + cl_assert(type == GIT_OBJ_COMMIT); +} |