Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/transports/local.c')
-rw-r--r--src/transports/local.c514
1 files changed, 457 insertions, 57 deletions
diff --git a/src/transports/local.c b/src/transports/local.c
index 000993e69..8af970eac 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
@@ -10,16 +10,34 @@
#include "git2/repository.h"
#include "git2/object.h"
#include "git2/tag.h"
+#include "git2/transport.h"
+#include "git2/revwalk.h"
+#include "git2/odb_backend.h"
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/revparse.h"
+#include "git2/push.h"
+#include "pack-objects.h"
#include "refs.h"
-#include "transport.h"
#include "posix.h"
#include "path.h"
#include "buffer.h"
+#include "repository.h"
+#include "odb.h"
+#include "push.h"
+#include "remote.h"
typedef struct {
git_transport parent;
+ git_remote *owner;
+ char *url;
+ int direction;
+ int flags;
+ git_atomic cancelled;
git_repository *repo;
git_vector refs;
+ unsigned connected : 1,
+ have_refs : 1;
} transport_local;
static int add_ref(transport_local *t, const char *name)
@@ -28,15 +46,28 @@ static int add_ref(transport_local *t, const char *name)
git_remote_head *head;
git_object *obj = NULL, *target = NULL;
git_buf buf = GIT_BUF_INIT;
+ int error;
- head = git__malloc(sizeof(git_remote_head));
+ head = git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
head->name = git__strdup(name);
GITERR_CHECK_ALLOC(head->name);
- if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0 ||
- git_vector_insert(&t->refs, head) < 0)
+ error = git_reference_name_to_id(&head->oid, t->repo, name);
+ if (error < 0) {
+ git__free(head->name);
+ git__free(head);
+ if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
+ /* This is actually okay. Empty repos often have a HEAD that points to
+ * a nonexistent "refs/heads/master". */
+ giterr_clear();
+ return 0;
+ }
+ return error;
+ }
+
+ if (git_vector_insert(&t->refs, head) < 0)
{
git__free(head->name);
git__free(head);
@@ -52,14 +83,16 @@ static int add_ref(transport_local *t, const char *name)
head = NULL;
- /* If it's not an annotated tag, just get out */
- if (git_object_type(obj) != GIT_OBJ_TAG) {
+ /* If it's not an annotated tag, or if we're mocking
+ * git-receive-pack, just get out */
+ if (git_object_type(obj) != GIT_OBJ_TAG ||
+ t->direction != GIT_DIRECTION_FETCH) {
git_object_free(obj);
return 0;
}
/* And if it's a tag, peel it, and add it to the list */
- head = git__malloc(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;
@@ -92,14 +125,14 @@ static int store_refs(transport_local *t)
assert(t);
if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
- git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
+ git_vector_init(&t->refs, ref_names.count, NULL) < 0)
goto on_error;
/* Sort the references first */
git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
- /* Add HEAD */
- if (add_ref(t, GIT_HEAD_FILE) < 0)
+ /* Add HEAD iff direction is fetch */
+ if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
goto on_error;
for (i = 0; i < ref_names.count; ++i) {
@@ -107,6 +140,7 @@ static int store_refs(transport_local *t)
goto on_error;
}
+ t->have_refs = 1;
git_strarray_free(&ref_names);
return 0;
@@ -116,28 +150,16 @@ on_error:
return -1;
}
-static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
-{
- transport_local *t = (transport_local *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
- git_remote_head *h;
-
- assert(transport && transport->connected);
-
- git_vector_foreach(refs, i, h) {
- if (list_cb(h, payload) < 0)
- return -1;
- }
-
- return 0;
-}
-
/*
* Try to open the url as a git directory. The direction doesn't
* matter in this case because we're calulating the heads ourselves.
*/
-static int local_connect(git_transport *transport, int direction)
+static int local_connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ int direction, int flags)
{
git_repository *repo;
int error;
@@ -145,18 +167,24 @@ static int local_connect(git_transport *transport, int direction)
const char *path;
git_buf buf = GIT_BUF_INIT;
- GIT_UNUSED(direction);
+ GIT_UNUSED(cred_acquire_cb);
+ GIT_UNUSED(cred_acquire_payload);
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+ t->direction = direction;
+ t->flags = flags;
/* The repo layer doesn't want the prefix */
- if (!git__prefixcmp(transport->url, "file://")) {
- if (git_path_fromurl(&buf, transport->url) < 0) {
+ if (!git__prefixcmp(t->url, "file://")) {
+ if (git_path_fromurl(&buf, t->url) < 0) {
git_buf_free(&buf);
return -1;
}
path = git_buf_cstr(&buf);
} else { /* We assume transport->url is already a path */
- path = transport->url;
+ path = t->url;
}
error = git_repository_open(&repo, path);
@@ -171,47 +199,411 @@ static int local_connect(git_transport *transport, int direction)
if (store_refs(t) < 0)
return -1;
- t->parent.connected = 1;
+ t->connected = 1;
+
+ return 0;
+}
+
+static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
+{
+ transport_local *t = (transport_local *)transport;
+ unsigned int i;
+ git_remote_head *head = NULL;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ git_vector_foreach(&t->refs, i, head) {
+ if (list_cb(head, payload))
+ return GIT_EUSER;
+ }
return 0;
}
-static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
+static int local_negotiate_fetch(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count)
{
- GIT_UNUSED(transport);
- GIT_UNUSED(repo);
- GIT_UNUSED(wants);
+ transport_local *t = (transport_local*)transport;
+ git_remote_head *rhead;
+ unsigned int i;
- giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry");
- return -1;
+ GIT_UNUSED(refs);
+ GIT_UNUSED(count);
+
+ /* Fill in the loids */
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+
+ int error = git_revparse_single(&obj, repo, rhead->name);
+ if (!error)
+ git_oid_cpy(&rhead->loid, git_object_id(obj));
+ else if (error != GIT_ENOTFOUND)
+ return error;
+ git_object_free(obj);
+ giterr_clear();
+ }
+
+ return 0;
+}
+
+static int local_push_copy_object(
+ git_odb *local_odb,
+ git_odb *remote_odb,
+ git_pobject *obj)
+{
+ int error = 0;
+ git_odb_object *odb_obj = NULL;
+ git_odb_stream *odb_stream;
+ size_t odb_obj_size;
+ git_otype odb_obj_type;
+ git_oid remote_odb_obj_oid;
+
+ /* Object already exists in the remote ODB; do nothing and return 0*/
+ if (git_odb_exists(remote_odb, &obj->id))
+ return 0;
+
+ if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0)
+ return error;
+
+ odb_obj_size = git_odb_object_size(odb_obj);
+ odb_obj_type = git_odb_object_type(odb_obj);
+
+ if ((error = git_odb_open_wstream(&odb_stream, remote_odb,
+ odb_obj_size, odb_obj_type)) < 0)
+ goto on_error;
+
+ if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj),
+ odb_obj_size) < 0 ||
+ odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) {
+ error = -1;
+ } else if (git_oid_cmp(&obj->id, &remote_odb_obj_oid) != 0) {
+ giterr_set(GITERR_ODB, "Error when writing object to remote odb "
+ "during local push operation. Remote odb object oid does not "
+ "match local oid.");
+ error = -1;
+ }
+
+ odb_stream->free(odb_stream);
+
+on_error:
+ git_odb_object_free(odb_obj);
+ return error;
+}
+
+static int local_push_update_remote_ref(
+ git_repository *remote_repo,
+ const char *lref,
+ const char *rref,
+ git_oid *loid,
+ git_oid *roid)
+{
+ int error;
+ git_reference *remote_ref = NULL;
+
+ /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */
+ rref = rref ? rref : lref;
+
+ if (lref) {
+ /* Create or update a ref */
+ if ((error = git_reference_create(NULL, remote_repo, rref, loid,
+ !git_oid_iszero(roid))) < 0)
+ return error;
+ } else {
+ /* Delete a ref */
+ if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ return error;
+ }
+
+ if ((error = git_reference_delete(remote_ref)) < 0)
+ return error;
+
+ git_reference_free(remote_ref);
+ }
+
+ return 0;
+}
+
+static int local_push(
+ git_transport *transport,
+ git_push *push)
+{
+ transport_local *t = (transport_local *)transport;
+ git_odb *remote_odb = NULL;
+ git_odb *local_odb = NULL;
+ git_repository *remote_repo = NULL;
+ push_spec *spec;
+ char *url = NULL;
+ int error;
+ unsigned int i;
+ size_t j;
+
+ if ((error = git_repository_open(&remote_repo, push->remote->url)) < 0)
+ return error;
+
+ /* We don't currently support pushing locally to non-bare repos. Proper
+ non-bare repo push support would require checking configs to see if
+ we should override the default 'don't let this happen' behavior */
+ if (!remote_repo->is_bare) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 ||
+ (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0)
+ goto on_error;
+
+ for (i = 0; i < push->pb->nr_objects; i++) {
+ if ((error = local_push_copy_object(local_odb, remote_odb,
+ &push->pb->object_list[i])) < 0)
+ goto on_error;
+ }
+
+ push->unpack_ok = 1;
+
+ git_vector_foreach(&push->specs, j, spec) {
+ push_status *status;
+ const git_error *last;
+ char *ref = spec->rref ? spec->rref : spec->lref;
+
+ status = git__calloc(sizeof(push_status), 1);
+ if (!status)
+ goto on_error;
+
+ status->ref = git__strdup(ref);
+ if (!status->ref) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref,
+ &spec->loid, &spec->roid);
+
+ switch (error) {
+ case GIT_OK:
+ break;
+ case GIT_EINVALIDSPEC:
+ status->msg = git__strdup("funny refname");
+ break;
+ case GIT_ENOTFOUND:
+ status->msg = git__strdup("Remote branch not found to delete");
+ break;
+ default:
+ last = giterr_last();
+
+ if (last && last->message)
+ status->msg = git__strdup(last->message);
+ else
+ status->msg = git__strdup("Unspecified error encountered");
+ break;
+ }
+
+ /* failed to allocate memory for a status message */
+ if (error < 0 && !status->msg) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ /* failed to insert the ref update status */
+ if ((error = git_vector_insert(&push->status, status)) < 0) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+ }
+
+ if (push->specs.length) {
+ int flags = t->flags;
+ url = git__strdup(t->url);
+
+ if (!url || t->parent.close(&t->parent) < 0 ||
+ t->parent.connect(&t->parent, url,
+ push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags))
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_repository_free(remote_repo);
+ git__free(url);
+
+ return error;
+}
+
+typedef struct foreach_data {
+ git_transfer_progress *stats;
+ git_transfer_progress_callback progress_cb;
+ void *progress_payload;
+ git_odb_writepack *writepack;
+} foreach_data;
+
+static int foreach_cb(void *buf, size_t len, void *payload)
+{
+ foreach_data *data = (foreach_data*)payload;
+
+ data->stats->received_bytes += len;
+ return data->writepack->add(data->writepack, buf, len, data->stats);
+}
+
+static int local_download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ transport_local *t = (transport_local*)transport;
+ git_revwalk *walk = NULL;
+ git_remote_head *rhead;
+ unsigned int i;
+ int error = -1;
+ git_oid oid;
+ git_packbuilder *pack = NULL;
+ git_odb_writepack *writepack = NULL;
+ git_odb *odb = NULL;
+
+ if ((error = git_revwalk_new(&walk, t->repo)) < 0)
+ goto cleanup;
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
+ goto cleanup;
+
+ stats->total_objects = 0;
+ stats->indexed_objects = 0;
+ stats->received_objects = 0;
+ stats->received_bytes = 0;
+
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+ if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
+ goto cleanup;
+
+ if (git_object_type(obj) == GIT_OBJ_COMMIT) {
+ /* Revwalker includes only wanted commits */
+ error = git_revwalk_push(walk, &rhead->oid);
+ if (!git_oid_iszero(&rhead->loid))
+ error = git_revwalk_hide(walk, &rhead->loid);
+ } else {
+ /* Tag or some other wanted object. Add it on its own */
+ error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
+ }
+ git_object_free(obj);
+ }
+
+ /* Walk the objects, building a packfile */
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ goto cleanup;
+
+ while ((error = git_revwalk_next(&oid, walk)) == 0) {
+ git_commit *commit;
+
+ /* Skip commits we already have */
+ if (git_odb_exists(odb, &oid)) continue;
+
+ if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
+ const git_oid *tree_oid = git_commit_tree_id(commit);
+
+ /* Add the commit and its tree */
+ if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
+ (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) {
+ git_commit_free(commit);
+ goto cleanup;
+ }
+
+ git_commit_free(commit);
+ }
+ }
+
+ if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)
+ goto cleanup;
+
+ /* Write the data to the ODB */
+ {
+ foreach_data data = {0};
+ data.stats = stats;
+ data.progress_cb = progress_cb;
+ data.progress_payload = progress_payload;
+ data.writepack = writepack;
+
+ if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) < 0)
+ goto cleanup;
+ }
+ error = writepack->commit(writepack, stats);
+
+cleanup:
+ if (writepack) writepack->free(writepack);
+ git_packbuilder_free(pack);
+ git_revwalk_free(walk);
+ return error;
+}
+
+static int local_is_connected(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ return t->connected;
+}
+
+static int local_read_flags(git_transport *transport, int *flags)
+{
+ transport_local *t = (transport_local *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static void local_cancel(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
}
static int local_close(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
- git_repository_free(t->repo);
- t->repo = NULL;
+ t->connected = 0;
+
+ if (t->repo) {
+ git_repository_free(t->repo);
+ t->repo = NULL;
+ }
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
return 0;
}
static void local_free(git_transport *transport)
{
- unsigned int i;
- transport_local *t = (transport_local *) transport;
- git_vector *vec = &t->refs;
- git_remote_head *h;
+ transport_local *t = (transport_local *)transport;
+ size_t i;
+ git_remote_head *head;
- assert(transport);
+ /* Close the transport, if it's still open. */
+ local_close(transport);
- git_vector_foreach (vec, i, h) {
- git__free(h->name);
- git__free(h);
+ git_vector_foreach(&t->refs, i, head) {
+ git__free(head->name);
+ git__free(head);
}
- git_vector_free(vec);
- git__free(t->parent.url);
+ git_vector_free(&t->refs);
+
+ /* Free the transport */
git__free(t);
}
@@ -219,20 +611,28 @@ static void local_free(git_transport *transport)
* Public API *
**************/
-int git_transport_local(git_transport **out)
+int git_transport_local(git_transport **out, git_remote *owner, void *param)
{
transport_local *t;
- t = git__malloc(sizeof(transport_local));
- GITERR_CHECK_ALLOC(t);
+ GIT_UNUSED(param);
- memset(t, 0x0, sizeof(transport_local));
+ t = git__calloc(1, sizeof(transport_local));
+ GITERR_CHECK_ALLOC(t);
+ t->parent.version = GIT_TRANSPORT_VERSION;
t->parent.connect = local_connect;
- t->parent.ls = local_ls;
t->parent.negotiate_fetch = local_negotiate_fetch;
+ t->parent.download_pack = local_download_pack;
+ t->parent.push = local_push;
t->parent.close = local_close;
t->parent.free = local_free;
+ t->parent.ls = local_ls;
+ t->parent.is_connected = local_is_connected;
+ t->parent.read_flags = local_read_flags;
+ t->parent.cancel = local_cancel;
+
+ t->owner = owner;
*out = (git_transport *) t;