Merge branch 'development' into gsoc-push
diff --git a/include/git2.h b/include/git2.h
index d555439..95475c5 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 38b7fe0..b4463f7 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -28,6 +28,7 @@
 	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 fd6ae95..44bfc5e 100644
--- a/include/git2/object.h
+++ b/include/git2/object.h
@@ -95,6 +95,14 @@
 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 0000000..900a183
--- /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 6471acc..3ecdbc4 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -189,6 +189,12 @@
 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 @@
 } 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 @@
 	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 01ddbf3..7f92506 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -191,6 +191,7 @@
 
 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 747bbf7..007b2e3 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 942a2f7..12adb42 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 @@
 	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 df502e6..675b782 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -542,10 +542,11 @@
 
 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 @@
 	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 2e45eb8..4b3c0f4 100644
--- a/src/object.c
+++ b/src/object.c
@@ -413,3 +413,16 @@
 	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;
+}
diff --git a/src/pkt.c b/src/pkt.c
index 91f9b65..8a8c48a 100644
--- a/src/pkt.c
+++ b/src/pkt.c
@@ -215,6 +215,83 @@
 	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 @@
 		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 @@
 		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/pkt.h b/src/pkt.h
index 0fdb5c7..9ae86be 100644
--- a/src/pkt.h
+++ b/src/pkt.h
@@ -26,6 +26,9 @@
 	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 @@
 	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 affad51..8f14bea 100644
--- a/src/protocol.c
+++ b/src/protocol.c
@@ -101,6 +101,11 @@
 			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 0000000..ec447df
--- /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 3bbdf6e..749cbc6 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 e05ea05..b4eddbd 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -430,6 +430,11 @@
 	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 4c944b9..0152fcd 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 @@
 		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 @@
 	/**
 	 * 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_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 b757495..5d5f0c6 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -106,7 +106,8 @@
 	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 @@
 {
 	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 93dd0c3..18fed17 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -41,7 +41,7 @@
 	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 @@
 	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 @@
 #endif
 } transport_http;
 
-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)
+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 *username, const char *password)
+{
+	git_buf_clear(buf);
+
 	if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
 		path = "/";
 
@@ -73,6 +100,14 @@
 	}
 	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 @@
 	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 (content_length) {
-		if (gitno_send((git_transport *) t, data, content_length, 0) < 0)
-			goto cleanup;
-	}
+		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;
+		}
 
-	error = 0;
+		if (gitno_send(transport, request.ptr, request.size, 0) < 0) {
+			git_buf_free(&request);
+			return -1;
+		}
+		git_buf_free(&request);
 
-cleanup:
+		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);
+
 	git_buf_free(&request);
-	return error;
+	return 0;
 
 #else
 	wchar_t *verb;
@@ -128,11 +228,14 @@
 		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 @@
 	}
 
 	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 @@
 	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 @@
 		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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 7cbcc61..179f153 100644
--- a/tests-clar/object/lookup.c
+++ b/tests-clar/object/lookup.c
@@ -61,3 +61,15 @@
 	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);
+}