Commit f1e5c506 by Vicent Martí

Merge pull request #1110 from libgit2/features/push_rebased

Push! By schu, phkelley, and congyiwu
parents e2934db2 36c730da
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#include "git2/remote.h" #include "git2/remote.h"
#include "git2/clone.h" #include "git2/clone.h"
#include "git2/checkout.h" #include "git2/checkout.h"
#include "git2/push.h"
#include "git2/attr.h" #include "git2/attr.h"
#include "git2/ignore.h" #include "git2/ignore.h"
......
...@@ -86,6 +86,11 @@ GIT_BEGIN_DECL ...@@ -86,6 +86,11 @@ GIT_BEGIN_DECL
#define GIT_PATH_MAX 4096 #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 * Return the version of the libgit2 library
* being currently used. * being currently used.
* *
......
...@@ -29,6 +29,7 @@ enum { ...@@ -29,6 +29,7 @@ enum {
GIT_EBAREREPO = -8, GIT_EBAREREPO = -8,
GIT_EORPHANEDHEAD = -9, GIT_EORPHANEDHEAD = -9,
GIT_EUNMERGED = -10, GIT_EUNMERGED = -10,
GIT_ENONFASTFORWARD = -11,
GIT_PASSTHROUGH = -30, GIT_PASSTHROUGH = -30,
GIT_ITEROVER = -31, GIT_ITEROVER = -31,
......
/*
* 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
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "indexer.h" #include "indexer.h"
#include "net.h" #include "net.h"
#include "types.h"
/** /**
* @file git2/transport.h * @file git2/transport.h
...@@ -102,8 +103,8 @@ typedef struct git_transport { ...@@ -102,8 +103,8 @@ typedef struct git_transport {
git_headlist_cb list_cb, git_headlist_cb list_cb,
void *payload); void *payload);
/* Reserved until push is implemented. */ /* Executes the push whose context is in the git_push object. */
int (*push)(struct git_transport *transport); int (*push)(struct git_transport *transport, git_push *push);
/* This function may be called after a successful call to connect(), when /* This function may be called after a successful call to connect(), when
* the direction is FETCH. The function performs a negotiation to calculate * the direction is FETCH. The function performs a negotiation to calculate
...@@ -123,7 +124,7 @@ typedef struct git_transport { ...@@ -123,7 +124,7 @@ typedef struct git_transport {
void *progress_payload); void *progress_payload);
/* Checks to see if the transport is connected */ /* 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() */ /* Reads the flags value previously passed into connect() */
int (*read_flags)(struct git_transport *transport, int *flags); int (*read_flags)(struct git_transport *transport, int *flags);
...@@ -145,10 +146,11 @@ typedef struct git_transport { ...@@ -145,10 +146,11 @@ typedef struct git_transport {
* git:// or http://) and a transport object is returned to the caller. * git:// or http://) and a transport object is returned to the caller.
* *
* @param out The newly created transport (out) * @param out The newly created transport (out)
* @param owner The git_remote which will own this transport
* @param url The URL to connect to * @param url The URL to connect to
* @return 0 or an error code * @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 * 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); ...@@ -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); GIT_EXTERN(int) git_transport_valid_url(const char *url);
/* Signature of a function which creates a transport */ /* 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 /* Transports which come with libgit2 (match git_transport_cb). The expected
* value for "param" is listed in-line below. */ * value for "param" is listed in-line below. */
...@@ -169,34 +171,40 @@ typedef int (*git_transport_cb)(git_transport **out, void *payload); ...@@ -169,34 +171,40 @@ typedef int (*git_transport_cb)(git_transport **out, void *payload);
/** /**
* Create an instance of the dummy transport. * 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. * @param payload You must pass NULL for this parameter.
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_transport_dummy( GIT_EXTERN(int) git_transport_dummy(
git_transport **transport, git_transport **out,
git_remote *owner,
/* NULL */ void *payload); /* NULL */ void *payload);
/** /**
* Create an instance of the local transport. * 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. * @param payload You must pass NULL for this parameter.
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_transport_local( GIT_EXTERN(int) git_transport_local(
git_transport **transport, git_transport **out,
git_remote *owner,
/* NULL */ void *payload); /* NULL */ void *payload);
/** /**
* Create an instance of the smart transport. * 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 * @param payload A pointer to a git_smart_subtransport_definition
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_transport_smart( GIT_EXTERN(int) git_transport_smart(
git_transport **transport, git_transport **out,
git_remote *owner,
/* (git_smart_subtransport_definition *) */ void *payload); /* (git_smart_subtransport_definition *) */ void *payload);
/* /*
...@@ -221,6 +229,8 @@ GIT_EXTERN(int) git_transport_smart( ...@@ -221,6 +229,8 @@ GIT_EXTERN(int) git_transport_smart(
typedef enum { typedef enum {
GIT_SERVICE_UPLOADPACK_LS = 1, GIT_SERVICE_UPLOADPACK_LS = 1,
GIT_SERVICE_UPLOADPACK = 2, GIT_SERVICE_UPLOADPACK = 2,
GIT_SERVICE_RECEIVEPACK_LS = 3,
GIT_SERVICE_RECEIVEPACK = 4,
} git_smart_service_t; } git_smart_service_t;
struct git_smart_subtransport; struct git_smart_subtransport;
...@@ -255,6 +265,14 @@ typedef struct git_smart_subtransport { ...@@ -255,6 +265,14 @@ typedef struct git_smart_subtransport {
const char *url, const char *url,
git_smart_service_t action); 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); void (* free)(struct git_smart_subtransport *transport);
} git_smart_subtransport; } git_smart_subtransport;
......
...@@ -187,6 +187,7 @@ typedef enum { ...@@ -187,6 +187,7 @@ typedef enum {
typedef struct git_refspec git_refspec; typedef struct git_refspec git_refspec;
typedef struct git_remote git_remote; typedef struct git_remote git_remote;
typedef struct git_push git_push;
typedef struct git_remote_head git_remote_head; typedef struct git_remote_head git_remote_head;
typedef struct git_remote_callbacks git_remote_callbacks; typedef struct git_remote_callbacks git_remote_callbacks;
......
...@@ -373,3 +373,4 @@ int git_object_peel( ...@@ -373,3 +373,4 @@ int git_object_peel(
git_object_free(deref); git_object_free(deref);
return -1; return -1;
} }
...@@ -604,12 +604,6 @@ on_error: ...@@ -604,12 +604,6 @@ on_error:
return -1; 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) static int write_pack_buf(void *buf, size_t size, void *data)
{ {
git_buf *b = (git_buf *)data; git_buf *b = (git_buf *)data;
...@@ -1233,12 +1227,6 @@ static int prepare_pack(git_packbuilder *pb) ...@@ -1233,12 +1227,6 @@ static int prepare_pack(git_packbuilder *pb)
#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; } #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) int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload)
{ {
PREPARE_PACK; PREPARE_PACK;
...@@ -1264,6 +1252,10 @@ static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *pay ...@@ -1264,6 +1252,10 @@ static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *pay
git_packbuilder *pb = payload; git_packbuilder *pb = payload;
git_buf buf = GIT_BUF_INIT; 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, root);
git_buf_puts(&buf, git_tree_entry_name(entry)); git_buf_puts(&buf, git_tree_entry_name(entry));
......
...@@ -82,7 +82,6 @@ struct git_packbuilder { ...@@ -82,7 +82,6 @@ struct git_packbuilder {
bool done; bool done;
}; };
int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s);
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb); int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
#endif /* INCLUDE_pack_objects_h__ */ #endif /* INCLUDE_pack_objects_h__ */
/*
* 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);
}
/*
* 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
...@@ -17,8 +17,6 @@ ...@@ -17,8 +17,6 @@
#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) #define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17)
#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
struct git_reflog_entry { struct git_reflog_entry {
git_oid oid_old; git_oid oid_old;
git_oid oid_cur; git_oid oid_cur;
......
...@@ -488,7 +488,7 @@ int git_remote_connect(git_remote *remote, git_direction direction) ...@@ -488,7 +488,7 @@ int git_remote_connect(git_remote *remote, git_direction direction)
/* A transport could have been supplied in advance with /* A transport could have been supplied in advance with
* git_remote_set_transport */ * git_remote_set_transport */
if (!t && git_transport_new(&t, url) < 0) if (!t && git_transport_new(&t, remote, url) < 0)
return -1; return -1;
if (t->set_callbacks && if (t->set_callbacks &&
...@@ -507,6 +507,10 @@ int git_remote_connect(git_remote *remote, git_direction direction) ...@@ -507,6 +507,10 @@ int git_remote_connect(git_remote *remote, git_direction direction)
on_error: on_error:
t->free(t); t->free(t);
if (t == remote->transport)
remote->transport = NULL;
return -1; return -1;
} }
...@@ -514,7 +518,7 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) ...@@ -514,7 +518,7 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{ {
assert(remote); assert(remote);
if (!remote->transport) { if (!git_remote_connected(remote)) {
giterr_set(GITERR_NET, "The remote is not connected"); giterr_set(GITERR_NET, "The remote is not connected");
return -1; return -1;
} }
...@@ -522,6 +526,63 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) ...@@ -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); 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.<name>.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( int git_remote_download(
git_remote *remote, git_remote *remote,
git_transfer_progress_callback progress_cb, git_transfer_progress_callback progress_cb,
...@@ -687,7 +748,7 @@ int git_remote_update_tips(git_remote *remote) ...@@ -687,7 +748,7 @@ int git_remote_update_tips(git_remote *remote)
git_vector_init(&update_heads, 16, NULL) < 0) git_vector_init(&update_heads, 16, NULL) < 0)
return -1; 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; goto on_error;
/* Let's go find HEAD, if it exists. Check only the first ref in the vector. */ /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */
...@@ -779,22 +840,20 @@ on_error: ...@@ -779,22 +840,20 @@ on_error:
int git_remote_connected(git_remote *remote) int git_remote_connected(git_remote *remote)
{ {
int connected;
assert(remote); assert(remote);
if (!remote->transport || !remote->transport->is_connected) if (!remote->transport || !remote->transport->is_connected)
return 0; return 0;
/* Ask the transport if it's connected. */ /* Ask the transport if it's connected. */
remote->transport->is_connected(remote->transport, &connected); return remote->transport->is_connected(remote->transport);
return connected;
} }
void git_remote_stop(git_remote *remote) void git_remote_stop(git_remote *remote)
{ {
if (remote->transport->cancel) assert(remote);
if (remote->transport && remote->transport->cancel)
remote->transport->cancel(remote->transport); remote->transport->cancel(remote->transport);
} }
......
...@@ -34,5 +34,6 @@ struct git_remote { ...@@ -34,5 +34,6 @@ struct git_remote {
}; };
const char* git_remote__urlfordirection(struct git_remote *remote, int direction); 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 #endif
...@@ -76,15 +76,16 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * ...@@ -76,15 +76,16 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
* Public API * * 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(transport);
GIT_UNUSED(owner);
GIT_UNUSED(param); GIT_UNUSED(param);
giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
return -1; 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_cb fn;
git_transport *transport; git_transport *transport;
...@@ -96,7 +97,7 @@ int git_transport_new(git_transport **out, const char *url) ...@@ -96,7 +97,7 @@ int git_transport_new(git_transport **out, const char *url)
return -1; return -1;
} }
error = fn(&transport, param); error = fn(&transport, owner, param);
if (error < 0) if (error < 0)
return error; return error;
......
...@@ -34,7 +34,7 @@ int git_cred_userpass_plaintext_new( ...@@ -34,7 +34,7 @@ int git_cred_userpass_plaintext_new(
if (!cred) if (!cred)
return -1; 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); GITERR_CHECK_ALLOC(c);
c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT; c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
static const char prefix_git[] = "git://"; static const char prefix_git[] = "git://";
static const char cmd_uploadpack[] = "git-upload-pack"; static const char cmd_uploadpack[] = "git-upload-pack";
static const char cmd_receivepack[] = "git-receive-pack";
typedef struct { typedef struct {
git_smart_subtransport_stream parent; git_smart_subtransport_stream parent;
...@@ -153,7 +154,7 @@ static int git_stream_alloc( ...@@ -153,7 +154,7 @@ static int git_stream_alloc(
if (!stream) if (!stream)
return -1; return -1;
s = (git_stream *)git__calloc(sizeof(git_stream), 1); s = git__calloc(sizeof(git_stream), 1);
GITERR_CHECK_ALLOC(s); GITERR_CHECK_ALLOC(s);
s->parent.subtransport = &t->parent; s->parent.subtransport = &t->parent;
...@@ -173,7 +174,7 @@ static int git_stream_alloc( ...@@ -173,7 +174,7 @@ static int git_stream_alloc(
return 0; return 0;
} }
static int git_git_uploadpack_ls( static int _git_uploadpack_ls(
git_subtransport *t, git_subtransport *t,
const char *url, const char *url,
git_smart_subtransport_stream **stream) git_smart_subtransport_stream **stream)
...@@ -211,7 +212,7 @@ on_error: ...@@ -211,7 +212,7 @@ on_error:
return -1; return -1;
} }
static int git_git_uploadpack( static int _git_uploadpack(
git_subtransport *t, git_subtransport *t,
const char *url, const char *url,
git_smart_subtransport_stream **stream) git_smart_subtransport_stream **stream)
...@@ -227,29 +228,100 @@ static int git_git_uploadpack( ...@@ -227,29 +228,100 @@ static int git_git_uploadpack(
return -1; 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( static int _git_action(
git_smart_subtransport_stream **stream, git_smart_subtransport_stream **stream,
git_smart_subtransport *smart_transport, git_smart_subtransport *subtransport,
const char *url, const char *url,
git_smart_service_t action) git_smart_service_t action)
{ {
git_subtransport *t = (git_subtransport *) smart_transport; git_subtransport *t = (git_subtransport *) subtransport;
switch (action) { switch (action) {
case GIT_SERVICE_UPLOADPACK_LS: case GIT_SERVICE_UPLOADPACK_LS:
return git_git_uploadpack_ls(t, url, stream); return _git_uploadpack_ls(t, url, stream);
case GIT_SERVICE_UPLOADPACK: 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; *stream = NULL;
return -1; return -1;
} }
static void _git_free(git_smart_subtransport *smart_transport) static int _git_close(git_smart_subtransport *subtransport)
{
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 *) smart_transport; git_subtransport *t = (git_subtransport *) subtransport;
assert(!t->current_stream); assert(!t->current_stream);
...@@ -263,11 +335,12 @@ int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owne ...@@ -263,11 +335,12 @@ int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owne
if (!out) if (!out)
return -1; return -1;
t = (git_subtransport *)git__calloc(sizeof(git_subtransport), 1); t = git__calloc(sizeof(git_subtransport), 1);
GITERR_CHECK_ALLOC(t); GITERR_CHECK_ALLOC(t);
t->owner = owner; t->owner = owner;
t->parent.action = _git_action; t->parent.action = _git_action;
t->parent.close = _git_close;
t->parent.free = _git_free; t->parent.free = _git_free;
*out = (git_smart_subtransport *) t; *out = (git_smart_subtransport *) t;
......
...@@ -17,6 +17,9 @@ static const char *prefix_https = "https://"; ...@@ -17,6 +17,9 @@ static const char *prefix_https = "https://";
static const char *upload_pack_service = "upload-pack"; 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_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/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 *get_verb = "GET";
static const char *post_verb = "POST"; static const char *post_verb = "POST";
static const char *basic_authtype = "Basic"; static const char *basic_authtype = "Basic";
...@@ -26,6 +29,8 @@ static const char *basic_authtype = "Basic"; ...@@ -26,6 +29,8 @@ static const char *basic_authtype = "Basic";
#define PARSE_ERROR_GENERIC -1 #define PARSE_ERROR_GENERIC -1
#define PARSE_ERROR_REPLAY -2 #define PARSE_ERROR_REPLAY -2
#define CHUNK_SIZE 4096
enum last_cb { enum last_cb {
NONE, NONE,
FIELD, FIELD,
...@@ -41,7 +46,11 @@ typedef struct { ...@@ -41,7 +46,11 @@ typedef struct {
const char *service; const char *service;
const char *service_url; const char *service_url;
const char *verb; 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; } http_stream;
typedef struct { typedef struct {
...@@ -106,33 +115,33 @@ on_error: ...@@ -106,33 +115,33 @@ on_error:
static int gen_request( static int gen_request(
git_buf *buf, git_buf *buf,
const char *path, http_stream *s,
const char *host, size_t content_length)
git_cred *cred,
http_authmechanism_t auth_mechanism,
const char *op,
const char *service,
const char *service_url,
ssize_t content_length)
{ {
if (!path) http_subtransport *t = OWNING_SUBTRANSPORT(s);
path = "/";
if (!t->path)
t->path = "/";
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url); 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_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
git_buf_printf(buf, "Host: %s\r\n", host); git_buf_printf(buf, "Host: %s\r\n", t->host);
if (content_length > 0) {
git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service); if (s->chunked || content_length > 0) {
git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service); 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); git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
} else { } else
git_buf_puts(buf, "Accept: */*\r\n"); git_buf_puts(buf, "Accept: */*\r\n");
}
/* Apply credentials to the request */ /* Apply credentials to the request */
if (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
auth_mechanism == GIT_HTTP_AUTH_BASIC && t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&
apply_basic_credential(buf, cred) < 0) apply_basic_credential(buf, t->cred) < 0)
return -1; return -1;
git_buf_puts(buf, "\r\n"); git_buf_puts(buf, "\r\n");
...@@ -168,7 +177,7 @@ static int on_header_ready(http_subtransport *t) ...@@ -168,7 +177,7 @@ static int on_header_ready(http_subtransport *t)
git_buf *value = &t->parse_header_value; git_buf *value = &t->parse_header_value;
char *dup; 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)); t->content_type = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->content_type); GITERR_CHECK_ALLOC(t->content_type);
} }
...@@ -355,6 +364,34 @@ static void clear_parser_state(http_subtransport *t) ...@@ -355,6 +364,34 @@ static void clear_parser_state(http_subtransport *t)
git_vector_free(&t->www_authenticate); 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( static int http_stream_read(
git_smart_subtransport_stream *stream, git_smart_subtransport_stream *stream,
char *buffer, char *buffer,
...@@ -363,8 +400,8 @@ static int http_stream_read( ...@@ -363,8 +400,8 @@ static int http_stream_read(
{ {
http_stream *s = (http_stream *)stream; http_stream *s = (http_stream *)stream;
http_subtransport *t = OWNING_SUBTRANSPORT(s); http_subtransport *t = OWNING_SUBTRANSPORT(s);
git_buf request = GIT_BUF_INIT;
parser_context ctx; parser_context ctx;
size_t bytes_parsed;
replay: replay:
*bytes_read = 0; *bytes_read = 0;
...@@ -372,11 +409,11 @@ replay: ...@@ -372,11 +409,11 @@ replay:
assert(t->connected); assert(t->connected);
if (!s->sent_request) { if (!s->sent_request) {
git_buf request = GIT_BUF_INIT;
clear_parser_state(t); clear_parser_state(t);
if (gen_request(&request, t->path, t->host, if (gen_request(&request, s, 0) < 0) {
t->cred, t->auth_mechanism, s->verb,
s->service, s->service_url, 0) < 0) {
giterr_set(GITERR_NET, "Failed to generate request"); giterr_set(GITERR_NET, "Failed to generate request");
return -1; return -1;
} }
...@@ -387,19 +424,37 @@ replay: ...@@ -387,19 +424,37 @@ replay:
} }
git_buf_free(&request); git_buf_free(&request);
s->sent_request = 1; 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) /* Flush, if necessary */
return 0; if (s->chunk_buffer_len > 0 &&
write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
/* Write the final chunk. */
if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0)
return -1;
}
s->received_response = 1;
}
while (!*bytes_read && !t->parse_finished) {
t->parse_buffer.offset = 0;
if (gitno_recv(&t->parse_buffer) < 0) if (gitno_recv(&t->parse_buffer) < 0)
return -1; return -1;
/* This call to http_parser_execute will result in invocations of the on_* /* This call to http_parser_execute will result in invocations of the
* family of callbacks. The most interesting of these is * on_* family of callbacks. The most interesting of these is
* on_body_fill_buffer, which is called when data is ready to be copied * 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 * into the target buffer. We need to marshal the buffer, buf_size, and
* bytes_read parameters to this callback. */ * bytes_read parameters to this callback. */
...@@ -412,7 +467,7 @@ replay: ...@@ -412,7 +467,7 @@ replay:
/* Set the context, call the parser, then unset the context. */ /* Set the context, call the parser, then unset the context. */
t->parser.data = &ctx; t->parser.data = &ctx;
http_parser_execute(&t->parser, bytes_parsed = http_parser_execute(&t->parser,
&t->settings, &t->settings,
t->parse_buffer.data, t->parse_buffer.data,
t->parse_buffer.offset); t->parse_buffer.offset);
...@@ -429,10 +484,91 @@ replay: ...@@ -429,10 +484,91 @@ replay:
if (t->parse_error < 0) if (t->parse_error < 0)
return -1; 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; 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, git_smart_subtransport_stream *stream,
const char *buffer, const char *buffer,
size_t len) size_t len)
...@@ -443,16 +579,14 @@ static int http_stream_write( ...@@ -443,16 +579,14 @@ static int http_stream_write(
assert(t->connected); assert(t->connected);
/* Since we have to write the Content-Length header up front, we're if (s->sent_request) {
* basically limited to a single call to write() per request. */ giterr_set(GITERR_NET, "Subtransport configured for only one write");
assert(!s->sent_request); return -1;
}
if (!s->sent_request) {
clear_parser_state(t); clear_parser_state(t);
if (gen_request(&request, t->path, t->host, if (gen_request(&request, s, len) < 0) {
t->cred, t->auth_mechanism, s->verb,
s->service, s->service_url, len) < 0) {
giterr_set(GITERR_NET, "Failed to generate request"); giterr_set(GITERR_NET, "Failed to generate request");
return -1; return -1;
} }
...@@ -465,7 +599,6 @@ static int http_stream_write( ...@@ -465,7 +599,6 @@ static int http_stream_write(
git_buf_free(&request); git_buf_free(&request);
s->sent_request = 1; s->sent_request = 1;
}
return 0; return 0;
...@@ -478,6 +611,9 @@ static void http_stream_free(git_smart_subtransport_stream *stream) ...@@ -478,6 +611,9 @@ static void http_stream_free(git_smart_subtransport_stream *stream)
{ {
http_stream *s = (http_stream *)stream; http_stream *s = (http_stream *)stream;
if (s->chunk_buffer)
git__free(s->chunk_buffer);
git__free(s); git__free(s);
} }
...@@ -489,12 +625,12 @@ static int http_stream_alloc(http_subtransport *t, ...@@ -489,12 +625,12 @@ static int http_stream_alloc(http_subtransport *t,
if (!stream) if (!stream)
return -1; return -1;
s = (http_stream *)git__calloc(sizeof(http_stream), 1); s = git__calloc(sizeof(http_stream), 1);
GITERR_CHECK_ALLOC(s); GITERR_CHECK_ALLOC(s);
s->parent.subtransport = &t->parent; s->parent.subtransport = &t->parent;
s->parent.read = http_stream_read; 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; s->parent.free = http_stream_free;
*stream = (git_smart_subtransport_stream *)s; *stream = (git_smart_subtransport_stream *)s;
...@@ -537,14 +673,54 @@ static int http_uploadpack( ...@@ -537,14 +673,54 @@ static int http_uploadpack(
return 0; 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( static int http_action(
git_smart_subtransport_stream **stream, git_smart_subtransport_stream **stream,
git_smart_subtransport *smart_transport, git_smart_subtransport *subtransport,
const char *url, const char *url,
git_smart_service_t action) git_smart_service_t action)
{ {
http_subtransport *t = (http_subtransport *)smart_transport; http_subtransport *t = (http_subtransport *)subtransport;
const char *default_port; const char *default_port = NULL;
int flags = 0, ret; int flags = 0, ret;
if (!stream) if (!stream)
...@@ -562,6 +738,9 @@ static int http_action( ...@@ -562,6 +738,9 @@ static int http_action(
t->use_ssl = 1; t->use_ssl = 1;
} }
if (!default_port)
return -1;
if ((ret = gitno_extract_host_and_port(&t->host, &t->port, if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
url, default_port)) < 0) url, default_port)) < 0)
return ret; return ret;
...@@ -569,7 +748,13 @@ static int http_action( ...@@ -569,7 +748,13 @@ static int http_action(
t->path = strchr(url, '/'); 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) { if (t->use_ssl) {
int transport_flags; int transport_flags;
...@@ -588,9 +773,6 @@ static int http_action( ...@@ -588,9 +773,6 @@ static int http_action(
t->connected = 1; t->connected = 1;
} }
t->parse_finished = 0;
t->parse_error = 0;
switch (action) switch (action)
{ {
case GIT_SERVICE_UPLOADPACK_LS: case GIT_SERVICE_UPLOADPACK_LS:
...@@ -598,28 +780,53 @@ static int http_action( ...@@ -598,28 +780,53 @@ static int http_action(
case GIT_SERVICE_UPLOADPACK: case GIT_SERVICE_UPLOADPACK:
return http_uploadpack(t, stream); 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; *stream = NULL;
return -1; 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); clear_parser_state(t);
if (t->socket.socket) if (t->socket.socket) {
gitno_close(&t->socket); gitno_close(&t->socket);
memset(&t->socket, 0x0, sizeof(gitno_socket));
}
if (t->cred) { if (t->cred) {
t->cred->free(t->cred); t->cred->free(t->cred);
t->cred = NULL; t->cred = NULL;
} }
if (t->host) {
git__free(t->host); git__free(t->host);
t->host = NULL;
}
if (t->port) {
git__free(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); git__free(t);
} }
...@@ -630,11 +837,12 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own ...@@ -630,11 +837,12 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
if (!out) if (!out)
return -1; return -1;
t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1); t = git__calloc(sizeof(http_subtransport), 1);
GITERR_CHECK_ALLOC(t); GITERR_CHECK_ALLOC(t);
t->owner = (transport_smart *)owner; t->owner = (transport_smart *)owner;
t->parent.action = http_action; t->parent.action = http_action;
t->parent.close = http_close;
t->parent.free = http_free; t->parent.free = http_free;
t->settings.on_header_field = on_header_field; t->settings.on_header_field = on_header_field;
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
typedef struct { typedef struct {
git_transport parent; git_transport parent;
git_remote *owner;
char *url; char *url;
int direction; int direction;
int flags; int flags;
...@@ -42,7 +43,7 @@ static int add_ref(transport_local *t, const char *name) ...@@ -42,7 +43,7 @@ static int add_ref(transport_local *t, const char *name)
git_object *obj = NULL, *target = NULL; git_object *obj = NULL, *target = NULL;
git_buf buf = GIT_BUF_INIT; 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); GITERR_CHECK_ALLOC(head);
head->name = git__strdup(name); head->name = git__strdup(name);
...@@ -77,7 +78,7 @@ static int add_ref(transport_local *t, const char *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 */ /* 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); GITERR_CHECK_ALLOC(head);
if (git_buf_join(&buf, 0, name, peeled) < 0) if (git_buf_join(&buf, 0, name, peeled) < 0)
return -1; return -1;
...@@ -339,13 +340,11 @@ cleanup: ...@@ -339,13 +340,11 @@ cleanup:
return error; 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; transport_local *t = (transport_local *)transport;
*connected = t->connected; return t->connected;
return 0;
} }
static int local_read_flags(git_transport *transport, int *flags) static int local_read_flags(git_transport *transport, int *flags)
...@@ -398,7 +397,7 @@ static void local_free(git_transport *transport) ...@@ -398,7 +397,7 @@ static void local_free(git_transport *transport)
* Public API * * 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; transport_local *t;
...@@ -419,6 +418,8 @@ int git_transport_local(git_transport **out, void *param) ...@@ -419,6 +418,8 @@ int git_transport_local(git_transport **out, void *param)
t->parent.read_flags = local_read_flags; t->parent.read_flags = local_read_flags;
t->parent.cancel = local_cancel; t->parent.cancel = local_cancel;
t->owner = owner;
*out = (git_transport *) t; *out = (git_transport *) t;
return 0; return 0;
......
...@@ -29,12 +29,18 @@ static int git_smart__recv_cb(gitno_buffer *buf) ...@@ -29,12 +29,18 @@ static int git_smart__recv_cb(gitno_buffer *buf)
return (int)(buf->offset - old_len); 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) { if (t->current_stream) {
t->current_stream->free(t->current_stream); t->current_stream->free(t->current_stream);
t->current_stream = NULL; t->current_stream = NULL;
} }
if (close_subtransport &&
t->wrapped->close(t->wrapped) < 0)
return -1;
return 0;
} }
static int git_smart__set_callbacks( static int git_smart__set_callbacks(
...@@ -63,8 +69,11 @@ static int git_smart__connect( ...@@ -63,8 +69,11 @@ static int git_smart__connect(
git_smart_subtransport_stream *stream; git_smart_subtransport_stream *stream;
int error; int error;
git_pkt *pkt; 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); t->url = git__strdup(url);
GITERR_CHECK_ALLOC(t->url); GITERR_CHECK_ALLOC(t->url);
...@@ -73,9 +82,16 @@ static int git_smart__connect( ...@@ -73,9 +82,16 @@ static int git_smart__connect(
t->flags = flags; t->flags = flags;
t->cred_acquire_cb = cred_acquire_cb; t->cred_acquire_cb = cred_acquire_cb;
if (GIT_DIRECTION_FETCH == direction) if (GIT_DIRECTION_FETCH == t->direction)
{ service = GIT_SERVICE_UPLOADPACK_LS;
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 0) else if (GIT_DIRECTION_PUSH == t->direction)
service = GIT_SERVICE_RECEIVEPACK_LS;
else {
giterr_set(GITERR_NET, "Invalid direction");
return -1;
}
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
return error; return error;
/* Save off the current stream (i.e. socket) that we are working with */ /* Save off the current stream (i.e. socket) that we are working with */
...@@ -104,24 +120,26 @@ static int git_smart__connect( ...@@ -104,24 +120,26 @@ static int git_smart__connect(
/* We now have loaded the refs. */ /* We now have loaded the refs. */
t->have_refs = 1; t->have_refs = 1;
if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0) first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
/* Detect capabilities */
if (git_smart__detect_caps(first, &t->caps) < 0)
return -1; return -1;
if (t->rpc) /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
git_smart__reset_stream(t); 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. */ /* We're now logically connected. */
t->connected = 1; t->connected = 1;
return 0; return 0;
}
else
{
giterr_set(GITERR_NET, "Push not implemented");
return -1;
}
return -1;
} }
static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload) static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
...@@ -156,10 +174,14 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len ...@@ -156,10 +174,14 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len
git_smart_subtransport_stream *stream; git_smart_subtransport_stream *stream;
int error; int error;
if (t->rpc) if (t->rpc && git_smart__reset_stream(t, false) < 0)
git_smart__reset_stream(t); return -1;
if (GIT_DIRECTION_FETCH != t->direction) {
giterr_set(GITERR_NET, "This operation is only valid for fetch");
return -1;
}
if (GIT_DIRECTION_FETCH == t->direction) {
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
return error; return error;
...@@ -175,10 +197,32 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len ...@@ -175,10 +197,32 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
return 0; return 0;
} }
giterr_set(GITERR_NET, "Push not implemented"); 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; 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) static void git_smart__cancel(git_transport *transport)
...@@ -188,13 +232,11 @@ 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); 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; transport_smart *t = (transport_smart *)transport;
*connected = t->connected; return t->connected;
return 0;
} }
static int git_smart__read_flags(git_transport *transport, int *flags) 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) ...@@ -209,21 +251,37 @@ static int git_smart__read_flags(git_transport *transport, int *flags)
static int git_smart__close(git_transport *transport) static int git_smart__close(git_transport *transport)
{ {
transport_smart *t = (transport_smart *)transport; transport_smart *t = (transport_smart *)transport;
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_smart__reset_stream(t); 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; t->connected = 0;
return 0; return ret;
} }
static void git_smart__free(git_transport *transport) static void git_smart__free(git_transport *transport)
{ {
transport_smart *t = (transport_smart *)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. */ /* Make sure that the current stream is closed, if we have one. */
git_smart__close(transport); git_smart__close(transport);
...@@ -231,21 +289,10 @@ static void git_smart__free(git_transport *transport) ...@@ -231,21 +289,10 @@ static void git_smart__free(git_transport *transport)
/* Free the subtransport */ /* Free the subtransport */
t->wrapped->free(t->wrapped); 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); 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; transport_smart *t;
git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
...@@ -253,7 +300,7 @@ int git_transport_smart(git_transport **out, void *param) ...@@ -253,7 +300,7 @@ int git_transport_smart(git_transport **out, void *param)
if (!param) if (!param)
return -1; return -1;
t = (transport_smart *)git__calloc(sizeof(transport_smart), 1); t = git__calloc(sizeof(transport_smart), 1);
GITERR_CHECK_ALLOC(t); GITERR_CHECK_ALLOC(t);
t->parent.set_callbacks = git_smart__set_callbacks; t->parent.set_callbacks = git_smart__set_callbacks;
...@@ -262,11 +309,13 @@ int git_transport_smart(git_transport **out, void *param) ...@@ -262,11 +309,13 @@ int git_transport_smart(git_transport **out, void *param)
t->parent.free = git_smart__free; t->parent.free = git_smart__free;
t->parent.negotiate_fetch = git_smart__negotiate_fetch; t->parent.negotiate_fetch = git_smart__negotiate_fetch;
t->parent.download_pack = git_smart__download_pack; t->parent.download_pack = git_smart__download_pack;
t->parent.push = git_smart__push;
t->parent.ls = git_smart__ls; t->parent.ls = git_smart__ls;
t->parent.is_connected = git_smart__is_connected; t->parent.is_connected = git_smart__is_connected;
t->parent.read_flags = git_smart__read_flags; t->parent.read_flags = git_smart__read_flags;
t->parent.cancel = git_smart__cancel; t->parent.cancel = git_smart__cancel;
t->owner = owner;
t->rpc = definition->rpc; t->rpc = definition->rpc;
if (git_vector_init(&t->refs, 16, NULL) < 0) { if (git_vector_init(&t->refs, 16, NULL) < 0) {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "vector.h" #include "vector.h"
#include "netops.h" #include "netops.h"
#include "buffer.h" #include "buffer.h"
#include "push.h"
#define GIT_SIDE_BAND_DATA 1 #define GIT_SIDE_BAND_DATA 1
#define GIT_SIDE_BAND_PROGRESS 2 #define GIT_SIDE_BAND_PROGRESS 2
...@@ -18,6 +19,8 @@ ...@@ -18,6 +19,8 @@
#define GIT_CAP_SIDE_BAND "side-band" #define GIT_CAP_SIDE_BAND "side-band"
#define GIT_CAP_SIDE_BAND_64K "side-band-64k" #define GIT_CAP_SIDE_BAND_64K "side-band-64k"
#define GIT_CAP_INCLUDE_TAG "include-tag" #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 { enum git_pkt_type {
GIT_PKT_CMD, GIT_PKT_CMD,
...@@ -31,6 +34,9 @@ enum git_pkt_type { ...@@ -31,6 +34,9 @@ enum git_pkt_type {
GIT_PKT_ERR, GIT_PKT_ERR,
GIT_PKT_DATA, GIT_PKT_DATA,
GIT_PKT_PROGRESS, GIT_PKT_PROGRESS,
GIT_PKT_OK,
GIT_PKT_NG,
GIT_PKT_UNPACK,
}; };
/* Used for multi-ack */ /* Used for multi-ack */
...@@ -85,19 +91,38 @@ typedef struct { ...@@ -85,19 +91,38 @@ typedef struct {
char error[GIT_FLEX_ARRAY]; char error[GIT_FLEX_ARRAY];
} git_pkt_err; } 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 { typedef struct transport_smart_caps {
int common:1, int common:1,
ofs_delta:1, ofs_delta:1,
multi_ack: 1, multi_ack: 1,
side_band:1, side_band:1,
side_band_64k:1, side_band_64k:1,
include_tag:1; include_tag:1,
delete_refs:1,
report_status:1;
} transport_smart_caps; } transport_smart_caps;
typedef void (*packetsize_cb)(size_t received, void *payload); typedef void (*packetsize_cb)(size_t received, void *payload);
typedef struct { typedef struct {
git_transport parent; git_transport parent;
git_remote *owner;
char *url; char *url;
git_cred_acquire_cb cred_acquire_cb; git_cred_acquire_cb cred_acquire_cb;
int direction; int direction;
...@@ -123,6 +148,7 @@ typedef struct { ...@@ -123,6 +148,7 @@ typedef struct {
/* smart_protocol.c */ /* smart_protocol.c */
int git_smart__store_refs(transport_smart *t, int flushes); 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__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( int git_smart__negotiate_fetch(
git_transport *transport, git_transport *transport,
...@@ -139,6 +165,7 @@ int git_smart__download_pack( ...@@ -139,6 +165,7 @@ int git_smart__download_pack(
/* smart.c */ /* smart.c */
int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); 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 */ /* smart_pkt.c */
int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
......
...@@ -214,6 +214,83 @@ error_out: ...@@ -214,6 +214,83 @@ error_out:
return error; 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) static int32_t parse_len(const char *line)
{ {
char num[PKT_LEN_SIZE + 1]; char num[PKT_LEN_SIZE + 1];
...@@ -311,6 +388,12 @@ int git_pkt_parse_line( ...@@ -311,6 +388,12 @@ int git_pkt_parse_line(
ret = err_pkt(head, line, len); ret = err_pkt(head, line, len);
else if (*line == '#') else if (*line == '#')
ret = comment_pkt(head, line, len); 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 else
ret = ref_pkt(head, line, len); ret = ref_pkt(head, line, len);
...@@ -326,6 +409,17 @@ void git_pkt_free(git_pkt *pkt) ...@@ -326,6 +409,17 @@ void git_pkt_free(git_pkt *pkt)
git__free(p->head.name); 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); git__free(pkt);
} }
......
...@@ -4,9 +4,14 @@ ...@@ -4,9 +4,14 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with * This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#include "git2.h"
#include "smart.h" #include "smart.h"
#include "refs.h" #include "refs.h"
#include "repository.h" #include "repository.h"
#include "push.h"
#include "pack-objects.h"
#include "remote.h"
#define NETWORK_XFER_THRESHOLD (100*1024) #define NETWORK_XFER_THRESHOLD (100*1024)
...@@ -18,6 +23,11 @@ int git_smart__store_refs(transport_smart *t, int flushes) ...@@ -18,6 +23,11 @@ int git_smart__store_refs(transport_smart *t, int flushes)
const char *line_end; const char *line_end;
git_pkt *pkt; git_pkt *pkt;
/* Clear existing refs in case git_remote_connect() is called again
* after git_remote_disconnect().
*/
git_vector_clear(refs);
do { do {
if (buf->offset > 0) if (buf->offset > 0)
error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); 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) ...@@ -71,37 +81,43 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
if (*ptr == ' ') if (*ptr == ' ')
ptr++; ptr++;
if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
caps->common = caps->ofs_delta = 1; caps->common = caps->ofs_delta = 1;
ptr += strlen(GIT_CAP_OFS_DELTA); ptr += strlen(GIT_CAP_OFS_DELTA);
continue; continue;
} }
if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
caps->common = caps->multi_ack = 1; caps->common = caps->multi_ack = 1;
ptr += strlen(GIT_CAP_MULTI_ACK); ptr += strlen(GIT_CAP_MULTI_ACK);
continue; continue;
} }
if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
caps->common = caps->include_tag = 1; caps->common = caps->include_tag = 1;
ptr += strlen(GIT_CAP_INCLUDE_TAG); ptr += strlen(GIT_CAP_INCLUDE_TAG);
continue; continue;
} }
/* Keep side-band check after side-band-64k */ /* 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; caps->common = caps->side_band_64k = 1;
ptr += strlen(GIT_CAP_SIDE_BAND_64K); ptr += strlen(GIT_CAP_SIDE_BAND_64K);
continue; continue;
} }
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
caps->common = caps->side_band = 1; caps->common = caps->side_band = 1;
ptr += strlen(GIT_CAP_SIDE_BAND); ptr += strlen(GIT_CAP_SIDE_BAND);
continue; 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 */ /* We don't know this capability, so skip it */
ptr = strchr(ptr, ' '); ptr = strchr(ptr, ' ');
} }
...@@ -471,6 +487,7 @@ on_success: ...@@ -471,6 +487,7 @@ on_success:
error = 0; error = 0;
on_error: on_error:
if (writepack)
writepack->free(writepack); writepack->free(writepack);
/* Trailing execution of progress_cb, if necessary */ /* Trailing execution of progress_cb, if necessary */
...@@ -479,3 +496,218 @@ on_error: ...@@ -479,3 +496,218 @@ on_error:
return 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;
}
...@@ -13,25 +13,35 @@ ...@@ -13,25 +13,35 @@
#include "posix.h" #include "posix.h"
#include "netops.h" #include "netops.h"
#include "smart.h" #include "smart.h"
#include "remote.h"
#include "repository.h"
#include <winhttp.h> #include <winhttp.h>
#pragma comment(lib, "winhttp") #pragma comment(lib, "winhttp")
/* For UuidCreate */
#pragma comment(lib, "rpcrt4")
#define WIDEN2(s) L ## s #define WIDEN2(s) L ## s
#define WIDEN(s) WIDEN2(s) #define WIDEN(s) WIDEN2(s)
#define MAX_CONTENT_TYPE_LEN 100 #define MAX_CONTENT_TYPE_LEN 100
#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 #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_http = "http://";
static const char *prefix_https = "https://"; static const char *prefix_https = "https://";
static const char *upload_pack_service = "upload-pack"; 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_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/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 *get_verb = L"GET";
static const wchar_t *post_verb = L"POST"; 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 *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 | static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_UNKNOWN_CA; SECURITY_FLAG_IGNORE_UNKNOWN_CA;
...@@ -48,8 +58,13 @@ typedef struct { ...@@ -48,8 +58,13 @@ typedef struct {
const char *service_url; const char *service_url;
const wchar_t *verb; const wchar_t *verb;
HINTERNET request; HINTERNET request;
char *chunk_buffer;
unsigned chunk_buffer_len;
HANDLE post_body;
DWORD post_body_len;
unsigned sent_request : 1, unsigned sent_request : 1,
received_response : 1; received_response : 1,
chunked : 1;
} winhttp_stream; } winhttp_stream;
typedef struct { typedef struct {
...@@ -87,7 +102,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred) ...@@ -87,7 +102,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred)
goto on_error; goto on_error;
} }
wide = (wchar_t *)git__malloc(wide_len * sizeof(wchar_t)); wide = git__malloc(wide_len * sizeof(wchar_t));
if (!wide) if (!wide)
goto on_error; goto on_error;
...@@ -126,9 +141,11 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -126,9 +141,11 @@ static int winhttp_stream_connect(winhttp_stream *s)
{ {
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
char *proxy_url = NULL;
wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN]; wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN];
wchar_t *types[] = { L"*/*", NULL }; wchar_t *types[] = { L"*/*", NULL };
BOOL peerdist = FALSE; BOOL peerdist = FALSE;
int error = -1;
/* Prepare URL */ /* Prepare URL */
git_buf_printf(&buf, "%s%s", t->path, s->service_url); git_buf_printf(&buf, "%s%s", t->path, s->service_url);
...@@ -153,6 +170,36 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -153,6 +170,36 @@ static int winhttp_stream_connect(winhttp_stream *s)
goto on_error; 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 /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
* adds itself. This option may not be supported by the underlying * adds itself. This option may not be supported by the underlying
* platform, so we do not error-check it */ * platform, so we do not error-check it */
...@@ -205,11 +252,12 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -205,11 +252,12 @@ static int winhttp_stream_connect(winhttp_stream *s)
/* We've done everything up to calling WinHttpSendRequest. */ /* We've done everything up to calling WinHttpSendRequest. */
return 0; error = 0;
on_error: on_error:
git__free(proxy_url);
git_buf_free(&buf); git_buf_free(&buf);
return -1; return error;
} }
static int parse_unauthorized_response( static int parse_unauthorized_response(
...@@ -217,57 +265,65 @@ static int parse_unauthorized_response( ...@@ -217,57 +265,65 @@ static int parse_unauthorized_response(
int *allowed_types, int *allowed_types,
int *auth_mechanism) int *auth_mechanism)
{ {
DWORD index, buf_size, last_error; DWORD supported, first, target;
int error = 0;
wchar_t *buf = NULL;
*allowed_types = 0; *allowed_types = 0;
*auth_mechanism = 0;
for (index = 0; ; index++) { /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
/* Make a first call to ask for the size of the buffer to allocate * We can assume this was already done, since we know we are unauthorized.
* to hold the WWW-Authenticate header */ */
if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {
WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER, giterr_set(GITERR_OS, "Failed to parse supported auth schemes");
&buf_size, &index)) return -1;
{
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;
} }
if (WINHTTP_AUTH_SCHEME_BASIC & supported) {
*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
*auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
} }
/* Actually receive the data into our now-allocated buffer */ return 0;
if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, }
WINHTTP_HEADER_NAME_BY_INDEX, buf,
&buf_size, &index)) { static int write_chunk(HINTERNET request, const char *buffer, size_t len)
giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header"); {
error = -1; DWORD bytes_written;
break; 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;
} }
if (!wcsncmp(buf, basic_authtype, 5) && git_buf_free(&buf);
(buf[5] == L'\0' || buf[5] == L' ')) {
*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; /* Chunk body */
*auth_mechanism = GIT_WINHTTP_AUTH_BASIC; 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;
} }
git__free(buf); return 0;
return error;
} }
static int winhttp_stream_read( static int winhttp_stream_read(
...@@ -285,21 +341,83 @@ replay: ...@@ -285,21 +341,83 @@ replay:
if (!s->request && winhttp_stream_connect(s) < 0) if (!s->request && winhttp_stream_connect(s) < 0)
return -1; return -1;
if (!s->sent_request && if (!s->received_response) {
!WinHttpSendRequest(s->request, 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_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0)) { s->post_body_len, 0)) {
giterr_set(GITERR_OS, "Failed to send request"); giterr_set(GITERR_OS, "Failed to send request");
return -1; return -1;
} }
s->sent_request = 1; s->sent_request = 1;
}
if (!s->received_response) { if (s->chunked) {
DWORD status_code, status_code_length, content_type_length; assert(s->verb == post_verb);
char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; /* 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)) { if (!WinHttpReceiveResponse(s->request, 0)) {
giterr_set(GITERR_OS, "Failed to receive response"); giterr_set(GITERR_OS, "Failed to receive response");
...@@ -376,7 +494,7 @@ replay: ...@@ -376,7 +494,7 @@ replay:
if (!WinHttpReadData(s->request, if (!WinHttpReadData(s->request,
(LPVOID)buffer, (LPVOID)buffer,
(DWORD)buf_size, buf_size,
&dw_bytes_read)) &dw_bytes_read))
{ {
giterr_set(GITERR_OS, "Failed to read data"); giterr_set(GITERR_OS, "Failed to read data");
...@@ -388,7 +506,7 @@ replay: ...@@ -388,7 +506,7 @@ replay:
return 0; return 0;
} }
static int winhttp_stream_write( static int winhttp_stream_write_single(
git_smart_subtransport_stream *stream, git_smart_subtransport_stream *stream,
const char *buffer, const char *buffer,
size_t len) size_t len)
...@@ -400,10 +518,13 @@ static int winhttp_stream_write( ...@@ -400,10 +518,13 @@ static int winhttp_stream_write(
if (!s->request && winhttp_stream_connect(s) < 0) if (!s->request && winhttp_stream_connect(s) < 0)
return -1; return -1;
/* Since we have to write the Content-Length header up front, we're /* This implementation of write permits only a single call. */
* basically limited to a single call to write() per request. */ if (s->sent_request) {
if (!s->sent_request && giterr_set(GITERR_NET, "Subtransport configured for only one write");
!WinHttpSendRequest(s->request, return -1;
}
if (!WinHttpSendRequest(s->request,
WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, WINHTTP_NO_REQUEST_DATA, 0,
(DWORD)len, 0)) { (DWORD)len, 0)) {
...@@ -417,7 +538,7 @@ static int winhttp_stream_write( ...@@ -417,7 +538,7 @@ static int winhttp_stream_write(
(LPCVOID)buffer, (LPCVOID)buffer,
(DWORD)len, (DWORD)len,
&bytes_written)) { &bytes_written)) {
giterr_set(GITERR_OS, "Failed to send request"); giterr_set(GITERR_OS, "Failed to write data");
return -1; return -1;
} }
...@@ -426,10 +547,198 @@ static int winhttp_stream_write( ...@@ -426,10 +547,198 @@ static int winhttp_stream_write(
return 0; 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) static void winhttp_stream_free(git_smart_subtransport_stream *stream)
{ {
winhttp_stream *s = (winhttp_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) { if (s->request) {
WinHttpCloseHandle(s->request); WinHttpCloseHandle(s->request);
s->request = NULL; s->request = NULL;
...@@ -438,22 +747,23 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream) ...@@ -438,22 +747,23 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream)
git__free(s); 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; winhttp_stream *s;
if (!stream) if (!stream)
return -1; return -1;
s = (winhttp_stream *)git__calloc(sizeof(winhttp_stream), 1); s = git__calloc(sizeof(winhttp_stream), 1);
GITERR_CHECK_ALLOC(s); GITERR_CHECK_ALLOC(s);
s->parent.subtransport = &t->parent; s->parent.subtransport = &t->parent;
s->parent.read = winhttp_stream_read; 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; s->parent.free = winhttp_stream_free;
*stream = (git_smart_subtransport_stream *)s; *stream = s;
return 0; return 0;
} }
...@@ -520,20 +830,8 @@ static int winhttp_connect( ...@@ -520,20 +830,8 @@ static int winhttp_connect(
static int winhttp_uploadpack_ls( static int winhttp_uploadpack_ls(
winhttp_subtransport *t, winhttp_subtransport *t,
const char *url, winhttp_stream *s)
git_smart_subtransport_stream **stream)
{ {
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 = upload_pack_service;
s->service_url = upload_pack_ls_service_url; s->service_url = upload_pack_ls_service_url;
s->verb = get_verb; s->verb = get_verb;
...@@ -543,22 +841,41 @@ static int winhttp_uploadpack_ls( ...@@ -543,22 +841,41 @@ static int winhttp_uploadpack_ls(
static int winhttp_uploadpack( static int winhttp_uploadpack(
winhttp_subtransport *t, winhttp_subtransport *t,
const char *url, winhttp_stream *s)
git_smart_subtransport_stream **stream)
{ {
winhttp_stream *s; s->service = upload_pack_service;
s->service_url = upload_pack_service_url;
s->verb = post_verb;
if (!t->connection && return 0;
winhttp_connect(t, url) < 0) }
return -1;
if (winhttp_stream_alloc(t, stream) < 0) static int winhttp_receivepack_ls(
return -1; winhttp_subtransport *t,
winhttp_stream *s)
{
s->service = receive_pack_service;
s->service_url = receive_pack_ls_service_url;
s->verb = get_verb;
s = (winhttp_stream *)*stream; return 0;
}
s->service = upload_pack_service; static int winhttp_receivepack(
s->service_url = upload_pack_service_url; 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; s->verb = post_verb;
return 0; return 0;
...@@ -566,11 +883,20 @@ static int winhttp_uploadpack( ...@@ -566,11 +883,20 @@ static int winhttp_uploadpack(
static int winhttp_action( static int winhttp_action(
git_smart_subtransport_stream **stream, git_smart_subtransport_stream **stream,
git_smart_subtransport *smart_transport, git_smart_subtransport *subtransport,
const char *url, const char *url,
git_smart_service_t action) 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) if (!stream)
return -1; return -1;
...@@ -578,22 +904,45 @@ static int winhttp_action( ...@@ -578,22 +904,45 @@ static int winhttp_action(
switch (action) switch (action)
{ {
case GIT_SERVICE_UPLOADPACK_LS: case GIT_SERVICE_UPLOADPACK_LS:
return winhttp_uploadpack_ls(t, url, stream); ret = winhttp_uploadpack_ls(t, s);
break;
case GIT_SERVICE_UPLOADPACK: 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; if (!ret)
return -1; *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; winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
int ret = 0;
if (t->host) {
git__free(t->host); git__free(t->host);
t->host = NULL;
}
if (t->port) {
git__free(t->port); git__free(t->port);
t->port = NULL;
}
if (t->cred) { if (t->cred) {
t->cred->free(t->cred); t->cred->free(t->cred);
...@@ -601,15 +950,32 @@ static void winhttp_free(git_smart_subtransport *smart_transport) ...@@ -601,15 +950,32 @@ static void winhttp_free(git_smart_subtransport *smart_transport)
} }
if (t->connection) { if (t->connection) {
WinHttpCloseHandle(t->connection); if (!WinHttpCloseHandle(t->connection)) {
giterr_set(GITERR_OS, "Unable to close connection");
ret = -1;
}
t->connection = NULL; t->connection = NULL;
} }
if (t->session) { if (t->session) {
WinHttpCloseHandle(t->session); if (!WinHttpCloseHandle(t->session)) {
giterr_set(GITERR_OS, "Unable to close session");
ret = -1;
}
t->session = NULL; 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); git__free(t);
} }
...@@ -620,11 +986,12 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own ...@@ -620,11 +986,12 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
if (!out) if (!out)
return -1; return -1;
t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1); t = git__calloc(sizeof(winhttp_subtransport), 1);
GITERR_CHECK_ALLOC(t); GITERR_CHECK_ALLOC(t);
t->owner = (transport_smart *)owner; t->owner = (transport_smart *)owner;
t->parent.action = winhttp_action; t->parent.action = winhttp_action;
t->parent.close = winhttp_close;
t->parent.free = winhttp_free; t->parent.free = winhttp_free;
*out = (git_smart_subtransport *) t; *out = (git_smart_subtransport *) t;
......
...@@ -48,8 +48,8 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) ...@@ -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); git_remote_set_autotag(remote, flag);
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
cl_git_pass(git_remote_download(remote, progress, &bytes_received)); cl_git_pass(git_remote_download(remote, progress, &bytes_received));
git_remote_disconnect(remote);
cl_git_pass(git_remote_update_tips(remote)); cl_git_pass(git_remote_update_tips(remote));
git_remote_disconnect(remote);
cl_assert_equal_i(counter, n); cl_assert_equal_i(counter, n);
cl_assert(bytes_received > 0); cl_assert(bytes_received > 0);
......
#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 : "<NULL>");
}
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);
}
#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);
}
#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__ */
...@@ -62,3 +62,4 @@ void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void) ...@@ -62,3 +62,4 @@ void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void)
cl_assert_equal_i( cl_assert_equal_i(
GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG)); GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG));
} }
#!/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
added submodule named 'submodule' pointing to '../testrepo.git'
a78705c3b2725f931d3ee05348d83cc26700f247
[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
Unnamed repository; edit this file 'description' to name the repository.
# 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]
# *~
0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit (initial): added a.txt
5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit: added fold and fold/b.txt
a78705c3b2725f931d3ee05348d83cc26700f247 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from master to b3
a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited a.txt
d9b63a88223d8367516f50bd131a5f7349b7f3e4 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from b3 to b4
a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited fold\b.txt
27b7ce66243eb1403862d05f958c002312df173d a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from b4 to b5
a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu <congyiwu@gmail.com> 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git'
fa38b91f199934685819bea316186d8b008c52a2 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 checkout: moving from b5 to master
a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy.
0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 branch: Created from master
0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 branch: Created from master
0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from HEAD
a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited a.txt
0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from master
a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited fold\b.txt
0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from master
a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu <congyiwu@gmail.com> 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git'
0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit (initial): added a.txt
5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit: added fold and fold/b.txt
a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy.
[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
Unnamed repository; edit this file 'description' to name the repository.
# 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]
# *~
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
# pack-refs with: peeled
a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2
a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/cannot-fetch
e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/chomped
258f0e2a959a364e40ed6603d5d44fbb24765b10 refs/remotes/origin/haacked
a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/master
a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/not-good
41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/remotes/origin/packed
4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test
763d71aadf09a7951596c9746c024e7eece7c7af refs/remotes/origin/subtrees
e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test
9fd738e8f7967c078dceed8190330fc8648ee56a refs/remotes/origin/track-local
e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/trailing
521d87c1ec3aef9824daf6d96cc0ae3710766d91 refs/tags/annotated_tag_to_blob
^1385f264afb75a56a5bec74243be9b367ba4ca08
7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b
^e90810b8df3e80c413d903f631643c716887138d
849a5e34a26815e821f865b8479f5815a47af0fe refs/tags/hard_tag
^a65fedf39aefe402d3bb6e24df4d4f5fe4547750
1385f264afb75a56a5bec74243be9b367ba4ca08 refs/tags/point_to_blob
b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/test
^e90810b8df3e80c413d903f631643c716887138d
849a5e34a26815e821f865b8479f5815a47af0fe refs/tags/wrapped_tag
^a65fedf39aefe402d3bb6e24df4d4f5fe4547750
a78705c3b2725f931d3ee05348d83cc26700f247
a78705c3b2725f931d3ee05348d83cc26700f247
d9b63a88223d8367516f50bd131a5f7349b7f3e4
27b7ce66243eb1403862d05f958c002312df173d
fa38b91f199934685819bea316186d8b008c52a2
951bbbb90e2259a4c8950db78946784fb53fcbce
[submodule "submodule"]
path = submodule
url = ../testrepo.git
gitdir: ../.git/modules/submodule
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment