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 @@
#include "git2/remote.h"
#include "git2/clone.h"
#include "git2/checkout.h"
#include "git2/push.h"
#include "git2/attr.h"
#include "git2/ignore.h"
......
......@@ -86,6 +86,11 @@ GIT_BEGIN_DECL
#define GIT_PATH_MAX 4096
/**
* The string representation of the null object ID.
*/
#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
/**
* Return the version of the libgit2 library
* being currently used.
*
......
......@@ -29,6 +29,7 @@ enum {
GIT_EBAREREPO = -8,
GIT_EORPHANEDHEAD = -9,
GIT_EUNMERGED = -10,
GIT_ENONFASTFORWARD = -11,
GIT_PASSTHROUGH = -30,
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 @@
#include "indexer.h"
#include "net.h"
#include "types.h"
/**
* @file git2/transport.h
......@@ -102,8 +103,8 @@ typedef struct git_transport {
git_headlist_cb list_cb,
void *payload);
/* Reserved until push is implemented. */
int (*push)(struct git_transport *transport);
/* Executes the push whose context is in the git_push object. */
int (*push)(struct git_transport *transport, git_push *push);
/* This function may be called after a successful call to connect(), when
* the direction is FETCH. The function performs a negotiation to calculate
......@@ -123,7 +124,7 @@ typedef struct git_transport {
void *progress_payload);
/* Checks to see if the transport is connected */
int (*is_connected)(struct git_transport *transport, int *connected);
int (*is_connected)(struct git_transport *transport);
/* Reads the flags value previously passed into connect() */
int (*read_flags)(struct git_transport *transport, int *flags);
......@@ -145,10 +146,11 @@ typedef struct git_transport {
* git:// or http://) and a transport object is returned to the caller.
*
* @param out The newly created transport (out)
* @param owner The git_remote which will own this transport
* @param url The URL to connect to
* @return 0 or an error code
*/
GIT_EXTERN(int) git_transport_new(git_transport **out, const char *url);
GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const char *url);
/**
* Function which checks to see if a transport could be created for the
......@@ -161,7 +163,7 @@ GIT_EXTERN(int) git_transport_new(git_transport **out, const char *url);
GIT_EXTERN(int) git_transport_valid_url(const char *url);
/* Signature of a function which creates a transport */
typedef int (*git_transport_cb)(git_transport **out, void *payload);
typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param);
/* Transports which come with libgit2 (match git_transport_cb). The expected
* value for "param" is listed in-line below. */
......@@ -169,34 +171,40 @@ typedef int (*git_transport_cb)(git_transport **out, void *payload);
/**
* Create an instance of the dummy transport.
*
* @param transport The newly created transport (out)
* @param out The newly created transport (out)
* @param owner The git_remote which will own this transport
* @param payload You must pass NULL for this parameter.
* @return 0 or an error code
*/
GIT_EXTERN(int) git_transport_dummy(
git_transport **transport,
git_transport **out,
git_remote *owner,
/* NULL */ void *payload);
/**
* Create an instance of the local transport.
*
* @param transport The newly created transport (out)
* @param out The newly created transport (out)
* @param owner The git_remote which will own this transport
* @param payload You must pass NULL for this parameter.
* @return 0 or an error code
*/
GIT_EXTERN(int) git_transport_local(
git_transport **transport,
git_transport **out,
git_remote *owner,
/* NULL */ void *payload);
/**
* Create an instance of the smart transport.
*
* @param transport The newly created transport (out)
* @param out The newly created transport (out)
* @param owner The git_remote which will own this transport
* @param payload A pointer to a git_smart_subtransport_definition
* @return 0 or an error code
*/
GIT_EXTERN(int) git_transport_smart(
git_transport **transport,
git_transport **out,
git_remote *owner,
/* (git_smart_subtransport_definition *) */ void *payload);
/*
......@@ -221,6 +229,8 @@ GIT_EXTERN(int) git_transport_smart(
typedef enum {
GIT_SERVICE_UPLOADPACK_LS = 1,
GIT_SERVICE_UPLOADPACK = 2,
GIT_SERVICE_RECEIVEPACK_LS = 3,
GIT_SERVICE_RECEIVEPACK = 4,
} git_smart_service_t;
struct git_smart_subtransport;
......@@ -255,6 +265,14 @@ typedef struct git_smart_subtransport {
const char *url,
git_smart_service_t action);
/* Subtransports are guaranteed a call to close() between
* calls to action(), except for the following two "natural" progressions
* of actions against a constant URL.
*
* 1. UPLOADPACK_LS -> UPLOADPACK
* 2. RECEIVEPACK_LS -> RECEIVEPACK */
int (* close)(struct git_smart_subtransport *transport);
void (* free)(struct git_smart_subtransport *transport);
} git_smart_subtransport;
......
......@@ -187,6 +187,7 @@ typedef enum {
typedef struct git_refspec git_refspec;
typedef struct git_remote git_remote;
typedef struct git_push git_push;
typedef struct git_remote_head git_remote_head;
typedef struct git_remote_callbacks git_remote_callbacks;
......
......@@ -373,3 +373,4 @@ int git_object_peel(
git_object_free(deref);
return -1;
}
......@@ -604,12 +604,6 @@ on_error:
return -1;
}
static int send_pack_file(void *buf, size_t size, void *data)
{
gitno_socket *s = (gitno_socket *)data;
return gitno_send(s, buf, size, 0);
}
static int write_pack_buf(void *buf, size_t size, void *data)
{
git_buf *b = (git_buf *)data;
......@@ -1233,12 +1227,6 @@ static int prepare_pack(git_packbuilder *pb)
#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s)
{
PREPARE_PACK;
return write_pack(pb, &send_pack_file, s);
}
int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload)
{
PREPARE_PACK;
......@@ -1264,6 +1252,10 @@ static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *pay
git_packbuilder *pb = payload;
git_buf buf = GIT_BUF_INIT;
/* A commit inside a tree represents a submodule commit and should be skipped. */
if(git_tree_entry_type(entry) == GIT_OBJ_COMMIT)
return 0;
git_buf_puts(&buf, root);
git_buf_puts(&buf, git_tree_entry_name(entry));
......
......@@ -82,7 +82,6 @@ struct git_packbuilder {
bool done;
};
int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s);
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
#endif /* INCLUDE_pack_objects_h__ */
/*
* 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 @@
#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17)
#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
struct git_reflog_entry {
git_oid oid_old;
git_oid oid_cur;
......
......@@ -488,13 +488,13 @@ int git_remote_connect(git_remote *remote, git_direction direction)
/* A transport could have been supplied in advance with
* git_remote_set_transport */
if (!t && git_transport_new(&t, url) < 0)
if (!t && git_transport_new(&t, remote, url) < 0)
return -1;
if (t->set_callbacks &&
t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.payload) < 0)
goto on_error;
if (!remote->check_cert)
flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
......@@ -507,6 +507,10 @@ int git_remote_connect(git_remote *remote, git_direction direction)
on_error:
t->free(t);
if (t == remote->transport)
remote->transport = NULL;
return -1;
}
......@@ -514,7 +518,7 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
assert(remote);
if (!remote->transport) {
if (!git_remote_connected(remote)) {
giterr_set(GITERR_NET, "The remote is not connected");
return -1;
}
......@@ -522,6 +526,63 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
return remote->transport->ls(remote->transport, list_cb, payload);
}
int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url)
{
git_config *cfg;
const char *val;
assert(remote);
if (!proxy_url)
return -1;
*proxy_url = NULL;
if (git_repository_config__weakptr(&cfg, remote->repo) < 0)
return -1;
/* Go through the possible sources for proxy configuration, from most specific
* to least specific. */
/* remote.<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(
git_remote *remote,
git_transfer_progress_callback progress_cb,
......@@ -687,7 +748,7 @@ int git_remote_update_tips(git_remote *remote)
git_vector_init(&update_heads, 16, NULL) < 0)
return -1;
if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0)
if (git_remote_ls(remote, update_tips_callback, &refs) < 0)
goto on_error;
/* Let's go find HEAD, if it exists. Check only the first ref in the vector. */
......@@ -779,22 +840,20 @@ on_error:
int git_remote_connected(git_remote *remote)
{
int connected;
assert(remote);
if (!remote->transport || !remote->transport->is_connected)
return 0;
/* Ask the transport if it's connected. */
remote->transport->is_connected(remote->transport, &connected);
return connected;
return remote->transport->is_connected(remote->transport);
}
void git_remote_stop(git_remote *remote)
{
if (remote->transport->cancel)
assert(remote);
if (remote->transport && remote->transport->cancel)
remote->transport->cancel(remote->transport);
}
......
......@@ -34,5 +34,6 @@ struct git_remote {
};
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url);
#endif
......@@ -76,15 +76,16 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
* Public API *
**************/
int git_transport_dummy(git_transport **transport, void *param)
int git_transport_dummy(git_transport **transport, git_remote *owner, void *param)
{
GIT_UNUSED(transport);
GIT_UNUSED(owner);
GIT_UNUSED(param);
giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
return -1;
}
int git_transport_new(git_transport **out, const char *url)
int git_transport_new(git_transport **out, git_remote *owner, const char *url)
{
git_transport_cb fn;
git_transport *transport;
......@@ -96,7 +97,7 @@ int git_transport_new(git_transport **out, const char *url)
return -1;
}
error = fn(&transport, param);
error = fn(&transport, owner, param);
if (error < 0)
return error;
......
......@@ -34,7 +34,7 @@ int git_cred_userpass_plaintext_new(
if (!cred)
return -1;
c = (git_cred_userpass_plaintext *)git__malloc(sizeof(git_cred_userpass_plaintext));
c = git__malloc(sizeof(git_cred_userpass_plaintext));
GITERR_CHECK_ALLOC(c);
c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;
......
......@@ -13,6 +13,7 @@
static const char prefix_git[] = "git://";
static const char cmd_uploadpack[] = "git-upload-pack";
static const char cmd_receivepack[] = "git-receive-pack";
typedef struct {
git_smart_subtransport_stream parent;
......@@ -153,7 +154,7 @@ static int git_stream_alloc(
if (!stream)
return -1;
s = (git_stream *)git__calloc(sizeof(git_stream), 1);
s = git__calloc(sizeof(git_stream), 1);
GITERR_CHECK_ALLOC(s);
s->parent.subtransport = &t->parent;
......@@ -173,7 +174,7 @@ static int git_stream_alloc(
return 0;
}
static int git_git_uploadpack_ls(
static int _git_uploadpack_ls(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
......@@ -211,7 +212,7 @@ on_error:
return -1;
}
static int git_git_uploadpack(
static int _git_uploadpack(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
......@@ -227,29 +228,100 @@ static int git_git_uploadpack(
return -1;
}
static int _git_receivepack_ls(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
{
char *host, *port;
git_stream *s;
*stream = NULL;
if (!git__prefixcmp(url, prefix_git))
url += strlen(prefix_git);
if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0)
return -1;
s = (git_stream *)*stream;
if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
goto on_error;
if (gitno_connect(&s->socket, host, port, 0) < 0)
goto on_error;
t->current_stream = s;
git__free(host);
git__free(port);
return 0;
on_error:
if (*stream)
git_stream_free(*stream);
git__free(host);
git__free(port);
return -1;
}
static int _git_receivepack(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
{
GIT_UNUSED(url);
if (t->current_stream) {
*stream = &t->current_stream->parent;
return 0;
}
giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
return -1;
}
static int _git_action(
git_smart_subtransport_stream **stream,
git_smart_subtransport *smart_transport,
git_smart_subtransport *subtransport,
const char *url,
git_smart_service_t action)
{
git_subtransport *t = (git_subtransport *) smart_transport;
git_subtransport *t = (git_subtransport *) subtransport;
switch (action) {
case GIT_SERVICE_UPLOADPACK_LS:
return git_git_uploadpack_ls(t, url, stream);
return _git_uploadpack_ls(t, url, stream);
case GIT_SERVICE_UPLOADPACK:
return git_git_uploadpack(t, url, stream);
return _git_uploadpack(t, url, stream);
case GIT_SERVICE_RECEIVEPACK_LS:
return _git_receivepack_ls(t, url, stream);
case GIT_SERVICE_RECEIVEPACK:
return _git_receivepack(t, url, stream);
}
*stream = NULL;
return -1;
}
static void _git_free(git_smart_subtransport *smart_transport)
static int _git_close(git_smart_subtransport *subtransport)
{
git_subtransport *t = (git_subtransport *) 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);
......@@ -263,11 +335,12 @@ int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owne
if (!out)
return -1;
t = (git_subtransport *)git__calloc(sizeof(git_subtransport), 1);
t = git__calloc(sizeof(git_subtransport), 1);
GITERR_CHECK_ALLOC(t);
t->owner = owner;
t->parent.action = _git_action;
t->parent.close = _git_close;
t->parent.free = _git_free;
*out = (git_smart_subtransport *) t;
......
......@@ -26,6 +26,7 @@
typedef struct {
git_transport parent;
git_remote *owner;
char *url;
int direction;
int flags;
......@@ -42,7 +43,7 @@ static int add_ref(transport_local *t, const char *name)
git_object *obj = NULL, *target = NULL;
git_buf buf = GIT_BUF_INIT;
head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head));
head = git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
head->name = git__strdup(name);
......@@ -77,7 +78,7 @@ static int add_ref(transport_local *t, const char *name)
}
/* And if it's a tag, peel it, and add it to the list */
head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head));
head = git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
if (git_buf_join(&buf, 0, name, peeled) < 0)
return -1;
......@@ -339,13 +340,11 @@ cleanup:
return error;
}
static int local_is_connected(git_transport *transport, int *connected)
static int local_is_connected(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
*connected = t->connected;
return 0;
return t->connected;
}
static int local_read_flags(git_transport *transport, int *flags)
......@@ -398,7 +397,7 @@ static void local_free(git_transport *transport)
* Public API *
**************/
int git_transport_local(git_transport **out, void *param)
int git_transport_local(git_transport **out, git_remote *owner, void *param)
{
transport_local *t;
......@@ -419,6 +418,8 @@ int git_transport_local(git_transport **out, void *param)
t->parent.read_flags = local_read_flags;
t->parent.cancel = local_cancel;
t->owner = owner;
*out = (git_transport *) t;
return 0;
......
......@@ -29,12 +29,18 @@ static int git_smart__recv_cb(gitno_buffer *buf)
return (int)(buf->offset - old_len);
}
GIT_INLINE(void) git_smart__reset_stream(transport_smart *t)
GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
{
if (t->current_stream) {
t->current_stream->free(t->current_stream);
t->current_stream = NULL;
}
if (close_subtransport &&
t->wrapped->close(t->wrapped) < 0)
return -1;
return 0;
}
static int git_smart__set_callbacks(
......@@ -63,8 +69,11 @@ static int git_smart__connect(
git_smart_subtransport_stream *stream;
int error;
git_pkt *pkt;
git_pkt_ref *first;
git_smart_service_t service;
git_smart__reset_stream(t);
if (git_smart__reset_stream(t, true) < 0)
return -1;
t->url = git__strdup(url);
GITERR_CHECK_ALLOC(t->url);
......@@ -73,55 +82,64 @@ static int git_smart__connect(
t->flags = flags;
t->cred_acquire_cb = cred_acquire_cb;
if (GIT_DIRECTION_FETCH == direction)
{
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 0)
return error;
/* Save off the current stream (i.e. socket) that we are working with */
t->current_stream = stream;
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
/* 2 flushes for RPC; 1 for stateful */
if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
return error;
/* Strip the comment packet for RPC */
if (t->rpc) {
pkt = (git_pkt *)git_vector_get(&t->refs, 0);
if (!pkt || GIT_PKT_COMMENT != pkt->type) {
giterr_set(GITERR_NET, "Invalid response");
return -1;
} else {
/* Remove the comment pkt from the list */
git_vector_remove(&t->refs, 0);
git__free(pkt);
}
}
if (GIT_DIRECTION_FETCH == t->direction)
service = GIT_SERVICE_UPLOADPACK_LS;
else if (GIT_DIRECTION_PUSH == t->direction)
service = GIT_SERVICE_RECEIVEPACK_LS;
else {
giterr_set(GITERR_NET, "Invalid direction");
return -1;
}
/* We now have loaded the refs. */
t->have_refs = 1;
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
return error;
if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0)
return -1;
/* Save off the current stream (i.e. socket) that we are working with */
t->current_stream = stream;
if (t->rpc)
git_smart__reset_stream(t);
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
/* We're now logically connected. */
t->connected = 1;
/* 2 flushes for RPC; 1 for stateful */
if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
return error;
/* Strip the comment packet for RPC */
if (t->rpc) {
pkt = (git_pkt *)git_vector_get(&t->refs, 0);
return 0;
if (!pkt || GIT_PKT_COMMENT != pkt->type) {
giterr_set(GITERR_NET, "Invalid response");
return -1;
} else {
/* Remove the comment pkt from the list */
git_vector_remove(&t->refs, 0);
git__free(pkt);
}
}
else
{
giterr_set(GITERR_NET, "Push not implemented");
/* We now have loaded the refs. */
t->have_refs = 1;
first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
/* Detect capabilities */
if (git_smart__detect_caps(first, &t->caps) < 0)
return -1;
/* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
git_oid_iszero(&first->head.oid)) {
git_vector_clear(&t->refs);
git_pkt_free((git_pkt *)first);
}
return -1;
if (t->rpc && git_smart__reset_stream(t, false) < 0)
return -1;
/* We're now logically connected. */
t->connected = 1;
return 0;
}
static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
......@@ -156,29 +174,55 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len
git_smart_subtransport_stream *stream;
int error;
if (t->rpc)
git_smart__reset_stream(t);
if (t->rpc && git_smart__reset_stream(t, false) < 0)
return -1;
if (GIT_DIRECTION_FETCH != t->direction) {
giterr_set(GITERR_NET, "This operation is only valid for fetch");
return -1;
}
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
return error;
/* If this is a stateful implementation, the stream we get back should be the same */
assert(t->rpc || t->current_stream == stream);
if (GIT_DIRECTION_FETCH == t->direction) {
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
return error;
/* Save off the current stream (i.e. socket) that we are working with */
t->current_stream = stream;
/* If this is a stateful implementation, the stream we get back should be the same */
assert(t->rpc || t->current_stream == stream);
if ((error = stream->write(stream, (const char *)data, len)) < 0)
return error;
/* Save off the current stream (i.e. socket) that we are working with */
t->current_stream = stream;
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
if ((error = stream->write(stream, (const char *)data, len)) < 0)
return error;
return 0;
}
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
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 0;
if (GIT_DIRECTION_PUSH != t->direction) {
giterr_set(GITERR_NET, "This operation is only valid for push");
return -1;
}
giterr_set(GITERR_NET, "Push not implemented");
return -1;
if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
return error;
/* If this is a stateful implementation, the stream we get back should be the same */
assert(t->rpc || t->current_stream == *stream);
/* Save off the current stream (i.e. socket) that we are working with */
t->current_stream = *stream;
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
return 0;
}
static void git_smart__cancel(git_transport *transport)
......@@ -188,13 +232,11 @@ static void git_smart__cancel(git_transport *transport)
git_atomic_set(&t->cancelled, 1);
}
static int git_smart__is_connected(git_transport *transport, int *connected)
static int git_smart__is_connected(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
*connected = t->connected;
return 0;
return t->connected;
}
static int git_smart__read_flags(git_transport *transport, int *flags)
......@@ -209,21 +251,37 @@ static int git_smart__read_flags(git_transport *transport, int *flags)
static int git_smart__close(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
git_smart__reset_stream(t);
git_vector *refs = &t->refs;
git_vector *common = &t->common;
unsigned int i;
git_pkt *p;
int ret;
ret = git_smart__reset_stream(t, true);
git_vector_foreach(refs, i, p)
git_pkt_free(p);
git_vector_free(refs);
git_vector_foreach(common, i, p)
git_pkt_free(p);
git_vector_free(common);
if (t->url) {
git__free(t->url);
t->url = NULL;
}
t->connected = 0;
return 0;
return ret;
}
static void git_smart__free(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
git_vector *refs = &t->refs;
git_vector *common = &t->common;
unsigned int i;
git_pkt *p;
/* Make sure that the current stream is closed, if we have one. */
git_smart__close(transport);
......@@ -231,21 +289,10 @@ static void git_smart__free(git_transport *transport)
/* Free the subtransport */
t->wrapped->free(t->wrapped);
git_vector_foreach(refs, i, p) {
git_pkt_free(p);
}
git_vector_free(refs);
git_vector_foreach(common, i, p) {
git_pkt_free(p);
}
git_vector_free(common);
git__free(t->url);
git__free(t);
}
int git_transport_smart(git_transport **out, void *param)
int git_transport_smart(git_transport **out, git_remote *owner, void *param)
{
transport_smart *t;
git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
......@@ -253,7 +300,7 @@ int git_transport_smart(git_transport **out, void *param)
if (!param)
return -1;
t = (transport_smart *)git__calloc(sizeof(transport_smart), 1);
t = git__calloc(sizeof(transport_smart), 1);
GITERR_CHECK_ALLOC(t);
t->parent.set_callbacks = git_smart__set_callbacks;
......@@ -262,11 +309,13 @@ int git_transport_smart(git_transport **out, void *param)
t->parent.free = git_smart__free;
t->parent.negotiate_fetch = git_smart__negotiate_fetch;
t->parent.download_pack = git_smart__download_pack;
t->parent.push = git_smart__push;
t->parent.ls = git_smart__ls;
t->parent.is_connected = git_smart__is_connected;
t->parent.read_flags = git_smart__read_flags;
t->parent.cancel = git_smart__cancel;
t->owner = owner;
t->rpc = definition->rpc;
if (git_vector_init(&t->refs, 16, NULL) < 0) {
......
......@@ -8,6 +8,7 @@
#include "vector.h"
#include "netops.h"
#include "buffer.h"
#include "push.h"
#define GIT_SIDE_BAND_DATA 1
#define GIT_SIDE_BAND_PROGRESS 2
......@@ -18,6 +19,8 @@
#define GIT_CAP_SIDE_BAND "side-band"
#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
#define GIT_CAP_INCLUDE_TAG "include-tag"
#define GIT_CAP_DELETE_REFS "delete-refs"
#define GIT_CAP_REPORT_STATUS "report-status"
enum git_pkt_type {
GIT_PKT_CMD,
......@@ -31,6 +34,9 @@ enum git_pkt_type {
GIT_PKT_ERR,
GIT_PKT_DATA,
GIT_PKT_PROGRESS,
GIT_PKT_OK,
GIT_PKT_NG,
GIT_PKT_UNPACK,
};
/* Used for multi-ack */
......@@ -85,19 +91,38 @@ typedef struct {
char error[GIT_FLEX_ARRAY];
} git_pkt_err;
typedef struct {
enum git_pkt_type type;
char *ref;
} git_pkt_ok;
typedef struct {
enum git_pkt_type type;
char *ref;
char *msg;
} git_pkt_ng;
typedef struct {
enum git_pkt_type type;
int unpack_ok;
} git_pkt_unpack;
typedef struct transport_smart_caps {
int common:1,
ofs_delta:1,
multi_ack: 1,
side_band:1,
side_band_64k:1,
include_tag:1;
include_tag:1,
delete_refs:1,
report_status:1;
} transport_smart_caps;
typedef void (*packetsize_cb)(size_t received, void *payload);
typedef struct {
git_transport parent;
git_remote *owner;
char *url;
git_cred_acquire_cb cred_acquire_cb;
int direction;
......@@ -123,6 +148,7 @@ typedef struct {
/* smart_protocol.c */
int git_smart__store_refs(transport_smart *t, int flushes);
int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps);
int git_smart__push(git_transport *transport, git_push *push);
int git_smart__negotiate_fetch(
git_transport *transport,
......@@ -139,6 +165,7 @@ int git_smart__download_pack(
/* smart.c */
int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out);
/* smart_pkt.c */
int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
......
......@@ -214,6 +214,83 @@ error_out:
return error;
}
static int ok_pkt(git_pkt **out, const char *line, size_t len)
{
git_pkt_ok *pkt;
const char *ptr;
pkt = git__malloc(sizeof(*pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_OK;
line += 3; /* skip "ok " */
ptr = strchr(line, '\n');
len = ptr - line;
pkt->ref = git__malloc(len + 1);
GITERR_CHECK_ALLOC(pkt->ref);
memcpy(pkt->ref, line, len);
pkt->ref[len] = '\0';
*out = (git_pkt *)pkt;
return 0;
}
static int ng_pkt(git_pkt **out, const char *line, size_t len)
{
git_pkt_ng *pkt;
const char *ptr;
pkt = git__malloc(sizeof(*pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_NG;
line += 3; /* skip "ng " */
ptr = strchr(line, ' ');
len = ptr - line;
pkt->ref = git__malloc(len + 1);
GITERR_CHECK_ALLOC(pkt->ref);
memcpy(pkt->ref, line, len);
pkt->ref[len] = '\0';
line = ptr + 1;
ptr = strchr(line, '\n');
len = ptr - line;
pkt->msg = git__malloc(len + 1);
GITERR_CHECK_ALLOC(pkt->msg);
memcpy(pkt->msg, line, len);
pkt->msg[len] = '\0';
*out = (git_pkt *)pkt;
return 0;
}
static int unpack_pkt(git_pkt **out, const char *line, size_t len)
{
git_pkt_unpack *pkt;
GIT_UNUSED(len);
pkt = git__malloc(sizeof(*pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_UNPACK;
if (!git__prefixcmp(line, "unpack ok"))
pkt->unpack_ok = 1;
else
pkt->unpack_ok = 0;
*out = (git_pkt *)pkt;
return 0;
}
static int32_t parse_len(const char *line)
{
char num[PKT_LEN_SIZE + 1];
......@@ -311,6 +388,12 @@ int git_pkt_parse_line(
ret = err_pkt(head, line, len);
else if (*line == '#')
ret = comment_pkt(head, line, len);
else if (!git__prefixcmp(line, "ok"))
ret = ok_pkt(head, line, len);
else if (!git__prefixcmp(line, "ng"))
ret = ng_pkt(head, line, len);
else if (!git__prefixcmp(line, "unpack"))
ret = unpack_pkt(head, line, len);
else
ret = ref_pkt(head, line, len);
......@@ -326,6 +409,17 @@ void git_pkt_free(git_pkt *pkt)
git__free(p->head.name);
}
if (pkt->type == GIT_PKT_OK) {
git_pkt_ok *p = (git_pkt_ok *) pkt;
git__free(p->ref);
}
if (pkt->type == GIT_PKT_NG) {
git_pkt_ng *p = (git_pkt_ng *) pkt;
git__free(p->ref);
git__free(p->msg);
}
git__free(pkt);
}
......
......@@ -4,9 +4,14 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2.h"
#include "smart.h"
#include "refs.h"
#include "repository.h"
#include "push.h"
#include "pack-objects.h"
#include "remote.h"
#define NETWORK_XFER_THRESHOLD (100*1024)
......@@ -18,6 +23,11 @@ int git_smart__store_refs(transport_smart *t, int flushes)
const char *line_end;
git_pkt *pkt;
/* Clear existing refs in case git_remote_connect() is called again
* after git_remote_disconnect().
*/
git_vector_clear(refs);
do {
if (buf->offset > 0)
error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
......@@ -71,37 +81,43 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
if (*ptr == ' ')
ptr++;
if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
caps->common = caps->ofs_delta = 1;
ptr += strlen(GIT_CAP_OFS_DELTA);
continue;
}
if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
caps->common = caps->multi_ack = 1;
ptr += strlen(GIT_CAP_MULTI_ACK);
continue;
}
if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
caps->common = caps->include_tag = 1;
ptr += strlen(GIT_CAP_INCLUDE_TAG);
continue;
}
/* Keep side-band check after side-band-64k */
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
caps->common = caps->side_band_64k = 1;
ptr += strlen(GIT_CAP_SIDE_BAND_64K);
continue;
}
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
caps->common = caps->side_band = 1;
ptr += strlen(GIT_CAP_SIDE_BAND);
continue;
}
if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) {
caps->common = caps->delete_refs = 1;
ptr += strlen(GIT_CAP_DELETE_REFS);
continue;
}
/* We don't know this capability, so skip it */
ptr = strchr(ptr, ' ');
}
......@@ -471,7 +487,8 @@ on_success:
error = 0;
on_error:
writepack->free(writepack);
if (writepack)
writepack->free(writepack);
/* Trailing execution of progress_cb, if necessary */
if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes)
......@@ -479,3 +496,218 @@ on_error:
return error;
}
static int gen_pktline(git_buf *buf, git_push *push)
{
git_remote_head *head;
push_spec *spec;
unsigned int i, j, len;
char hex[41]; hex[40] = '\0';
git_vector_foreach(&push->specs, i, spec) {
len = 2*GIT_OID_HEXSZ + 7;
if (i == 0) {
len +=1; /* '\0' */
if (push->report_status)
len += strlen(GIT_CAP_REPORT_STATUS);
}
if (spec->lref) {
len += spec->rref ? strlen(spec->rref) : strlen(spec->lref);
if (git_oid_iszero(&spec->roid)) {
/*
* Create remote reference
*/
git_oid_fmt(hex, &spec->loid);
git_buf_printf(buf, "%04x%s %s %s", len,
GIT_OID_HEX_ZERO, hex,
spec->rref ? spec->rref : spec->lref);
} else {
/*
* Update remote reference
*/
git_oid_fmt(hex, &spec->roid);
git_buf_printf(buf, "%04x%s ", len, hex);
git_oid_fmt(hex, &spec->loid);
git_buf_printf(buf, "%s %s", hex,
spec->rref ? spec->rref : spec->lref);
}
} else {
/*
* Delete remote reference
*/
git_vector_foreach(&push->remote->refs, j, head) {
if (!strcmp(spec->rref, head->name)) {
len += strlen(spec->rref);
git_oid_fmt(hex, &head->oid);
git_buf_printf(buf, "%04x%s %s %s", len,
hex, GIT_OID_HEX_ZERO, head->name);
break;
}
}
}
if (i == 0) {
git_buf_putc(buf, '\0');
if (push->report_status)
git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
}
git_buf_putc(buf, '\n');
}
git_buf_puts(buf, "0000");
return git_buf_oom(buf) ? -1 : 0;
}
static int parse_report(gitno_buffer *buf, git_push *push)
{
git_pkt *pkt;
const char *line_end;
int error, recvd;
for (;;) {
if (buf->offset > 0)
error = git_pkt_parse_line(&pkt, buf->data,
&line_end, buf->offset);
else
error = GIT_EBUFS;
if (error < 0 && error != GIT_EBUFS)
return -1;
if (error == GIT_EBUFS) {
if ((recvd = gitno_recv(buf)) < 0)
return -1;
if (recvd == 0) {
giterr_set(GITERR_NET, "Early EOF");
return -1;
}
continue;
}
gitno_consume(buf, line_end);
if (pkt->type == GIT_PKT_OK) {
push_status *status = git__malloc(sizeof(push_status));
GITERR_CHECK_ALLOC(status);
status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
status->msg = NULL;
git_pkt_free(pkt);
if (git_vector_insert(&push->status, status) < 0) {
git__free(status);
return -1;
}
continue;
}
if (pkt->type == GIT_PKT_NG) {
push_status *status = git__malloc(sizeof(push_status));
GITERR_CHECK_ALLOC(status);
status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
git_pkt_free(pkt);
if (git_vector_insert(&push->status, status) < 0) {
git__free(status);
return -1;
}
continue;
}
if (pkt->type == GIT_PKT_UNPACK) {
push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
git_pkt_free(pkt);
continue;
}
if (pkt->type == GIT_PKT_FLUSH) {
git_pkt_free(pkt);
return 0;
}
git_pkt_free(pkt);
giterr_set(GITERR_NET, "report-status: protocol error");
return -1;
}
}
static int stream_thunk(void *buf, size_t size, void *data)
{
git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data;
return s->write(s, (const char *)buf, size);
}
int git_smart__push(git_transport *transport, git_push *push)
{
transport_smart *t = (transport_smart *)transport;
git_smart_subtransport_stream *s;
git_buf pktline = GIT_BUF_INIT;
char *url = NULL;
int error = -1;
#ifdef PUSH_DEBUG
{
git_remote_head *head;
push_spec *spec;
unsigned int i;
char hex[41]; hex[40] = '\0';
git_vector_foreach(&push->remote->refs, i, head) {
git_oid_fmt(hex, &head->oid);
fprintf(stderr, "%s (%s)\n", hex, head->name);
}
git_vector_foreach(&push->specs, i, spec) {
git_oid_fmt(hex, &spec->roid);
fprintf(stderr, "%s (%s) -> ", hex, spec->lref);
git_oid_fmt(hex, &spec->loid);
fprintf(stderr, "%s (%s)\n", hex, spec->rref ?
spec->rref : spec->lref);
}
}
#endif
if (git_smart__get_push_stream(t, &s) < 0 ||
gen_pktline(&pktline, push) < 0 ||
s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0 ||
git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0)
goto on_error;
/* If we sent nothing or the server doesn't support report-status, then
* we consider the pack to have been unpacked successfully */
if (!push->specs.length || !push->report_status)
push->unpack_ok = 1;
else if (parse_report(&t->buffer, push) < 0)
goto on_error;
/* If we updated at least one ref, then we need to re-acquire the list of
* refs so the caller can call git_remote_update_tips afterward. TODO: Use
* the data from the push report to do this without another network call */
if (push->specs.length) {
git_cred_acquire_cb cred_cb = t->cred_acquire_cb;
int flags = t->flags;
url = git__strdup(t->url);
if (!url || t->parent.close(&t->parent) < 0 ||
t->parent.connect(&t->parent, url, cred_cb, GIT_DIRECTION_PUSH, flags))
goto on_error;
}
error = 0;
on_error:
git__free(url);
git_buf_free(&pktline);
return error;
}
......@@ -48,8 +48,8 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n)
git_remote_set_autotag(remote, flag);
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
cl_git_pass(git_remote_download(remote, progress, &bytes_received));
git_remote_disconnect(remote);
cl_git_pass(git_remote_update_tips(remote));
git_remote_disconnect(remote);
cl_assert_equal_i(counter, n);
cl_assert(bytes_received > 0);
......
#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)
cl_assert_equal_i(
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
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