Blame src/transports/local.c

Packit ae9e2a
/*
Packit ae9e2a
 * Copyright (C) the libgit2 contributors. All rights reserved.
Packit ae9e2a
 *
Packit ae9e2a
 * This file is part of libgit2, distributed under the GNU GPL v2 with
Packit ae9e2a
 * a Linking Exception. For full terms see the included COPYING file.
Packit ae9e2a
 */
Packit ae9e2a
#include "common.h"
Packit ae9e2a
#include "git2/types.h"
Packit ae9e2a
#include "git2/net.h"
Packit ae9e2a
#include "git2/repository.h"
Packit ae9e2a
#include "git2/object.h"
Packit ae9e2a
#include "git2/tag.h"
Packit ae9e2a
#include "git2/transport.h"
Packit ae9e2a
#include "git2/revwalk.h"
Packit ae9e2a
#include "git2/odb_backend.h"
Packit ae9e2a
#include "git2/pack.h"
Packit ae9e2a
#include "git2/commit.h"
Packit ae9e2a
#include "git2/revparse.h"
Packit ae9e2a
#include "pack-objects.h"
Packit ae9e2a
#include "refs.h"
Packit ae9e2a
#include "posix.h"
Packit ae9e2a
#include "path.h"
Packit ae9e2a
#include "buffer.h"
Packit ae9e2a
#include "repository.h"
Packit ae9e2a
#include "odb.h"
Packit ae9e2a
#include "push.h"
Packit ae9e2a
#include "remote.h"
Packit ae9e2a
#include "proxy.h"
Packit ae9e2a
Packit ae9e2a
typedef struct {
Packit ae9e2a
	git_transport parent;
Packit ae9e2a
	git_remote *owner;
Packit ae9e2a
	char *url;
Packit ae9e2a
	int direction;
Packit ae9e2a
	int flags;
Packit ae9e2a
	git_atomic cancelled;
Packit ae9e2a
	git_repository *repo;
Packit ae9e2a
	git_transport_message_cb progress_cb;
Packit ae9e2a
	git_transport_message_cb error_cb;
Packit ae9e2a
	void *message_cb_payload;
Packit ae9e2a
	git_vector refs;
Packit ae9e2a
	unsigned connected : 1,
Packit ae9e2a
		have_refs : 1;
Packit ae9e2a
} transport_local;
Packit ae9e2a
Packit ae9e2a
static void free_head(git_remote_head *head)
Packit ae9e2a
{
Packit ae9e2a
	git__free(head->name);
Packit ae9e2a
	git__free(head->symref_target);
Packit ae9e2a
	git__free(head);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static void free_heads(git_vector *heads)
Packit ae9e2a
{
Packit ae9e2a
	git_remote_head *head;
Packit ae9e2a
	size_t i;
Packit ae9e2a
Packit ae9e2a
	git_vector_foreach(heads, i, head)
Packit ae9e2a
		free_head(head);
Packit ae9e2a
Packit ae9e2a
	git_vector_free(heads);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int add_ref(transport_local *t, const char *name)
Packit ae9e2a
{
Packit ae9e2a
	const char peeled[] = "^{}";
Packit ae9e2a
	git_reference *ref, *resolved;
Packit ae9e2a
	git_remote_head *head;
Packit ae9e2a
	git_oid obj_id;
Packit ae9e2a
	git_object *obj = NULL, *target = NULL;
Packit ae9e2a
	git_buf buf = GIT_BUF_INIT;
Packit ae9e2a
	int error;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_reference_lookup(&ref, t->repo, name)) < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	error = git_reference_resolve(&resolved, ref);
Packit ae9e2a
	if (error < 0) {
Packit ae9e2a
		git_reference_free(ref);
Packit ae9e2a
		if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
Packit ae9e2a
			/* This is actually okay.  Empty repos often have a HEAD that
Packit ae9e2a
			 * points to a nonexistent "refs/heads/master". */
Packit ae9e2a
			giterr_clear();
Packit ae9e2a
			return 0;
Packit ae9e2a
		}
Packit ae9e2a
		return error;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	git_oid_cpy(&obj_id, git_reference_target(resolved));
Packit ae9e2a
	git_reference_free(resolved);
Packit ae9e2a
Packit ae9e2a
	head = git__calloc(1, sizeof(git_remote_head));
Packit ae9e2a
	GITERR_CHECK_ALLOC(head);
Packit ae9e2a
Packit ae9e2a
	head->name = git__strdup(name);
Packit ae9e2a
	GITERR_CHECK_ALLOC(head->name);
Packit ae9e2a
Packit ae9e2a
	git_oid_cpy(&head->oid, &obj_id);
Packit ae9e2a
Packit ae9e2a
	if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
Packit ae9e2a
		head->symref_target = git__strdup(git_reference_symbolic_target(ref));
Packit ae9e2a
		GITERR_CHECK_ALLOC(head->symref_target);
Packit ae9e2a
	}
Packit ae9e2a
	git_reference_free(ref);
Packit ae9e2a
Packit ae9e2a
	if ((error = git_vector_insert(&t->refs, head)) < 0) {
Packit ae9e2a
		free_head(head);
Packit ae9e2a
		return error;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	/* If it's not a tag, we don't need to try to peel it */
Packit ae9e2a
	if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
Packit ae9e2a
		return 0;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY)) < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	head = NULL;
Packit ae9e2a
Packit ae9e2a
	/* If it's not an annotated tag, or if we're mocking
Packit ae9e2a
	 * git-receive-pack, just get out */
Packit ae9e2a
	if (git_object_type(obj) != GIT_OBJ_TAG ||
Packit ae9e2a
		t->direction != GIT_DIRECTION_FETCH) {
Packit ae9e2a
		git_object_free(obj);
Packit ae9e2a
		return 0;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	/* And if it's a tag, peel it, and add it to the list */
Packit ae9e2a
	head = git__calloc(1, sizeof(git_remote_head));
Packit ae9e2a
	GITERR_CHECK_ALLOC(head);
Packit ae9e2a
Packit ae9e2a
	if (git_buf_join(&buf, 0, name, peeled) < 0) {
Packit ae9e2a
		free_head(head);
Packit ae9e2a
		return -1;
Packit ae9e2a
	}
Packit ae9e2a
	head->name = git_buf_detach(&buf;;
Packit ae9e2a
Packit ae9e2a
	if (!(error = git_tag_peel(&target, (git_tag *)obj))) {
Packit ae9e2a
		git_oid_cpy(&head->oid, git_object_id(target));
Packit ae9e2a
Packit ae9e2a
		if ((error = git_vector_insert(&t->refs, head)) < 0) {
Packit ae9e2a
			free_head(head);
Packit ae9e2a
		}
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	git_object_free(obj);
Packit ae9e2a
	git_object_free(target);
Packit ae9e2a
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int store_refs(transport_local *t)
Packit ae9e2a
{
Packit ae9e2a
	size_t i;
Packit ae9e2a
	git_remote_head *head;
Packit ae9e2a
	git_strarray ref_names = {0};
Packit ae9e2a
Packit ae9e2a
	assert(t);
Packit ae9e2a
Packit ae9e2a
	if (git_reference_list(&ref_names, t->repo) < 0)
Packit ae9e2a
		goto on_error;
Packit ae9e2a
Packit ae9e2a
	/* Clear all heads we might have fetched in a previous connect */
Packit ae9e2a
	git_vector_foreach(&t->refs, i, head) {
Packit ae9e2a
		git__free(head->name);
Packit ae9e2a
		git__free(head);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	/* Clear the vector so we can reuse it */
Packit ae9e2a
	git_vector_clear(&t->refs);
Packit ae9e2a
Packit ae9e2a
	/* Sort the references first */
Packit ae9e2a
	git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
Packit ae9e2a
Packit ae9e2a
	/* Add HEAD iff direction is fetch */
Packit ae9e2a
	if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
Packit ae9e2a
		goto on_error;
Packit ae9e2a
Packit ae9e2a
	for (i = 0; i < ref_names.count; ++i) {
Packit ae9e2a
		if (add_ref(t, ref_names.strings[i]) < 0)
Packit ae9e2a
			goto on_error;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	t->have_refs = 1;
Packit ae9e2a
	git_strarray_free(&ref_names);
Packit ae9e2a
	return 0;
Packit ae9e2a
Packit ae9e2a
on_error:
Packit ae9e2a
	git_vector_free(&t->refs);
Packit ae9e2a
	git_strarray_free(&ref_names);
Packit ae9e2a
	return -1;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/*
Packit ae9e2a
 * Try to open the url as a git directory. The direction doesn't
Packit ae9e2a
 * matter in this case because we're calculating the heads ourselves.
Packit ae9e2a
 */
Packit ae9e2a
static int local_connect(
Packit ae9e2a
	git_transport *transport,
Packit ae9e2a
	const char *url,
Packit ae9e2a
	git_cred_acquire_cb cred_acquire_cb,
Packit ae9e2a
	void *cred_acquire_payload,
Packit ae9e2a
	const git_proxy_options *proxy,
Packit ae9e2a
	int direction, int flags)
Packit ae9e2a
{
Packit ae9e2a
	git_repository *repo;
Packit ae9e2a
	int error;
Packit ae9e2a
	transport_local *t = (transport_local *) transport;
Packit ae9e2a
	const char *path;
Packit ae9e2a
	git_buf buf = GIT_BUF_INIT;
Packit ae9e2a
Packit ae9e2a
	GIT_UNUSED(cred_acquire_cb);
Packit ae9e2a
	GIT_UNUSED(cred_acquire_payload);
Packit ae9e2a
	GIT_UNUSED(proxy);
Packit ae9e2a
Packit ae9e2a
	if (t->connected)
Packit ae9e2a
		return 0;
Packit ae9e2a
Packit ae9e2a
	free_heads(&t->refs);
Packit ae9e2a
Packit ae9e2a
	t->url = git__strdup(url);
Packit ae9e2a
	GITERR_CHECK_ALLOC(t->url);
Packit ae9e2a
	t->direction = direction;
Packit ae9e2a
	t->flags = flags;
Packit ae9e2a
Packit ae9e2a
	/* 'url' may be a url or path; convert to a path */
Packit ae9e2a
	if ((error = git_path_from_url_or_path(&buf, url)) < 0) {
Packit ae9e2a
		git_buf_free(&buf;;
Packit ae9e2a
		return error;
Packit ae9e2a
	}
Packit ae9e2a
	path = git_buf_cstr(&buf;;
Packit ae9e2a
Packit ae9e2a
	error = git_repository_open(&repo, path);
Packit ae9e2a
Packit ae9e2a
	git_buf_free(&buf;;
Packit ae9e2a
Packit ae9e2a
	if (error < 0)
Packit ae9e2a
		return -1;
Packit ae9e2a
Packit ae9e2a
	t->repo = repo;
Packit ae9e2a
Packit ae9e2a
	if (store_refs(t) < 0)
Packit ae9e2a
		return -1;
Packit ae9e2a
Packit ae9e2a
	t->connected = 1;
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
Packit ae9e2a
	if (!t->have_refs) {
Packit ae9e2a
		giterr_set(GITERR_NET, "the transport has not yet loaded the refs");
Packit ae9e2a
		return -1;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	*out = (const git_remote_head **)t->refs.contents;
Packit ae9e2a
	*size = t->refs.length;
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_negotiate_fetch(
Packit ae9e2a
	git_transport *transport,
Packit ae9e2a
	git_repository *repo,
Packit ae9e2a
	const git_remote_head * const *refs,
Packit ae9e2a
	size_t count)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local*)transport;
Packit ae9e2a
	git_remote_head *rhead;
Packit ae9e2a
	unsigned int i;
Packit ae9e2a
Packit ae9e2a
	GIT_UNUSED(refs);
Packit ae9e2a
	GIT_UNUSED(count);
Packit ae9e2a
Packit ae9e2a
	/* Fill in the loids */
Packit ae9e2a
	git_vector_foreach(&t->refs, i, rhead) {
Packit ae9e2a
		git_object *obj;
Packit ae9e2a
Packit ae9e2a
		int error = git_revparse_single(&obj, repo, rhead->name);
Packit ae9e2a
		if (!error)
Packit ae9e2a
			git_oid_cpy(&rhead->loid, git_object_id(obj));
Packit ae9e2a
		else if (error != GIT_ENOTFOUND)
Packit ae9e2a
			return error;
Packit ae9e2a
		else
Packit ae9e2a
			giterr_clear();
Packit ae9e2a
		git_object_free(obj);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_push_update_remote_ref(
Packit ae9e2a
	git_repository *remote_repo,
Packit ae9e2a
	const char *lref,
Packit ae9e2a
	const char *rref,
Packit ae9e2a
	git_oid *loid,
Packit ae9e2a
	git_oid *roid)
Packit ae9e2a
{
Packit ae9e2a
	int error;
Packit ae9e2a
	git_reference *remote_ref = NULL;
Packit ae9e2a
Packit ae9e2a
	/* check for lhs, if it's empty it means to delete */
Packit ae9e2a
	if (lref[0] != '\0') {
Packit ae9e2a
		/* Create or update a ref */
Packit ae9e2a
		error = git_reference_create(NULL, remote_repo, rref, loid,
Packit ae9e2a
					     !git_oid_iszero(roid), NULL);
Packit ae9e2a
	} else {
Packit ae9e2a
		/* Delete a ref */
Packit ae9e2a
		if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
Packit ae9e2a
			if (error == GIT_ENOTFOUND)
Packit ae9e2a
				error = 0;
Packit ae9e2a
			return error;
Packit ae9e2a
		}
Packit ae9e2a
Packit ae9e2a
		error = git_reference_delete(remote_ref);
Packit ae9e2a
		git_reference_free(remote_ref);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int transfer_to_push_transfer(const git_transfer_progress *stats, void *payload)
Packit ae9e2a
{
Packit ae9e2a
	const git_remote_callbacks *cbs = payload;
Packit ae9e2a
Packit ae9e2a
	if (!cbs || !cbs->push_transfer_progress)
Packit ae9e2a
		return 0;
Packit ae9e2a
Packit ae9e2a
	return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes,
Packit ae9e2a
					   cbs->payload);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_push(
Packit ae9e2a
	git_transport *transport,
Packit ae9e2a
	git_push *push,
Packit ae9e2a
	const git_remote_callbacks *cbs)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
	git_repository *remote_repo = NULL;
Packit ae9e2a
	push_spec *spec;
Packit ae9e2a
	char *url = NULL;
Packit ae9e2a
	const char *path;
Packit ae9e2a
	git_buf buf = GIT_BUF_INIT, odb_path = GIT_BUF_INIT;
Packit ae9e2a
	int error;
Packit ae9e2a
	size_t j;
Packit ae9e2a
Packit ae9e2a
	GIT_UNUSED(cbs);
Packit ae9e2a
Packit ae9e2a
	/* 'push->remote->url' may be a url or path; convert to a path */
Packit ae9e2a
	if ((error = git_path_from_url_or_path(&buf, push->remote->url)) < 0) {
Packit ae9e2a
		git_buf_free(&buf;;
Packit ae9e2a
		return error;
Packit ae9e2a
	}
Packit ae9e2a
	path = git_buf_cstr(&buf;;
Packit ae9e2a
Packit ae9e2a
	error = git_repository_open(&remote_repo, path);
Packit ae9e2a
Packit ae9e2a
	git_buf_free(&buf;;
Packit ae9e2a
Packit ae9e2a
	if (error < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	/* We don't currently support pushing locally to non-bare repos. Proper
Packit ae9e2a
	   non-bare repo push support would require checking configs to see if
Packit ae9e2a
	   we should override the default 'don't let this happen' behavior.
Packit ae9e2a
Packit ae9e2a
	   Note that this is only an issue when pushing to the current branch,
Packit ae9e2a
	   but we forbid all pushes just in case */
Packit ae9e2a
	if (!remote_repo->is_bare) {
Packit ae9e2a
		error = GIT_EBAREREPO;
Packit ae9e2a
		giterr_set(GITERR_INVALID, "local push doesn't (yet) support pushing to non-bare repos.");
Packit ae9e2a
		goto on_error;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if ((error = git_repository_item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
Packit ae9e2a
		|| (error = git_buf_joinpath(&odb_path, odb_path.ptr, "pack")) < 0)
Packit ae9e2a
		goto on_error;
Packit ae9e2a
Packit ae9e2a
	error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs);
Packit ae9e2a
	git_buf_free(&odb_path);
Packit ae9e2a
Packit ae9e2a
	if (error < 0)
Packit ae9e2a
		goto on_error;
Packit ae9e2a
Packit ae9e2a
	push->unpack_ok = 1;
Packit ae9e2a
Packit ae9e2a
	git_vector_foreach(&push->specs, j, spec) {
Packit ae9e2a
		push_status *status;
Packit ae9e2a
		const git_error *last;
Packit ae9e2a
		char *ref = spec->refspec.dst;
Packit ae9e2a
Packit ae9e2a
		status = git__calloc(1, sizeof(push_status));
Packit ae9e2a
		if (!status)
Packit ae9e2a
			goto on_error;
Packit ae9e2a
Packit ae9e2a
		status->ref = git__strdup(ref);
Packit ae9e2a
		if (!status->ref) {
Packit ae9e2a
			git_push_status_free(status);
Packit ae9e2a
			goto on_error;
Packit ae9e2a
		}
Packit ae9e2a
Packit ae9e2a
		error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst,
Packit ae9e2a
			&spec->loid, &spec->roid);
Packit ae9e2a
Packit ae9e2a
		switch (error) {
Packit ae9e2a
			case GIT_OK:
Packit ae9e2a
				break;
Packit ae9e2a
			case GIT_EINVALIDSPEC:
Packit ae9e2a
				status->msg = git__strdup("funny refname");
Packit ae9e2a
				break;
Packit ae9e2a
			case GIT_ENOTFOUND:
Packit ae9e2a
				status->msg = git__strdup("Remote branch not found to delete");
Packit ae9e2a
				break;
Packit ae9e2a
			default:
Packit ae9e2a
				last = giterr_last();
Packit ae9e2a
Packit ae9e2a
				if (last && last->message)
Packit ae9e2a
					status->msg = git__strdup(last->message);
Packit ae9e2a
				else
Packit ae9e2a
					status->msg = git__strdup("Unspecified error encountered");
Packit ae9e2a
				break;
Packit ae9e2a
		}
Packit ae9e2a
Packit ae9e2a
		/* failed to allocate memory for a status message */
Packit ae9e2a
		if (error < 0 && !status->msg) {
Packit ae9e2a
			git_push_status_free(status);
Packit ae9e2a
			goto on_error;
Packit ae9e2a
		}
Packit ae9e2a
Packit ae9e2a
		/* failed to insert the ref update status */
Packit ae9e2a
		if ((error = git_vector_insert(&push->status, status)) < 0) {
Packit ae9e2a
			git_push_status_free(status);
Packit ae9e2a
			goto on_error;
Packit ae9e2a
		}
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (push->specs.length) {
Packit ae9e2a
		int flags = t->flags;
Packit ae9e2a
		url = git__strdup(t->url);
Packit ae9e2a
Packit ae9e2a
		if (!url || t->parent.close(&t->parent) < 0 ||
Packit ae9e2a
			t->parent.connect(&t->parent, url,
Packit ae9e2a
			NULL, NULL, NULL, GIT_DIRECTION_PUSH, flags))
Packit ae9e2a
			goto on_error;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	error = 0;
Packit ae9e2a
Packit ae9e2a
on_error:
Packit ae9e2a
	git_repository_free(remote_repo);
Packit ae9e2a
	git__free(url);
Packit ae9e2a
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
typedef struct foreach_data {
Packit ae9e2a
	git_transfer_progress *stats;
Packit ae9e2a
	git_transfer_progress_cb progress_cb;
Packit ae9e2a
	void *progress_payload;
Packit ae9e2a
	git_odb_writepack *writepack;
Packit ae9e2a
} foreach_data;
Packit ae9e2a
Packit ae9e2a
static int foreach_cb(void *buf, size_t len, void *payload)
Packit ae9e2a
{
Packit ae9e2a
	foreach_data *data = (foreach_data*)payload;
Packit ae9e2a
Packit ae9e2a
	data->stats->received_bytes += len;
Packit ae9e2a
	return data->writepack->append(data->writepack, buf, len, data->stats);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static const char *counting_objects_fmt = "Counting objects %d\r";
Packit ae9e2a
static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)";
Packit ae9e2a
Packit ae9e2a
static int local_counting(int stage, unsigned int current, unsigned int total, void *payload)
Packit ae9e2a
{
Packit ae9e2a
	git_buf progress_info = GIT_BUF_INIT;
Packit ae9e2a
	transport_local *t = payload;
Packit ae9e2a
	int error;
Packit ae9e2a
Packit ae9e2a
	if (!t->progress_cb)
Packit ae9e2a
		return 0;
Packit ae9e2a
Packit ae9e2a
	if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) {
Packit ae9e2a
		git_buf_printf(&progress_info, counting_objects_fmt, current);
Packit ae9e2a
	} else if (stage == GIT_PACKBUILDER_DELTAFICATION) {
Packit ae9e2a
		float perc = (((float) current) / total) * 100;
Packit ae9e2a
		git_buf_printf(&progress_info, compressing_objects_fmt, perc, current, total);
Packit ae9e2a
		if (current == total)
Packit ae9e2a
			git_buf_printf(&progress_info, ", done\n");
Packit ae9e2a
		else
Packit ae9e2a
			git_buf_putc(&progress_info, '\r');
Packit ae9e2a
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (git_buf_oom(&progress_info))
Packit ae9e2a
		return -1;
Packit ae9e2a
Packit ae9e2a
	error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload);
Packit ae9e2a
	git_buf_free(&progress_info);
Packit ae9e2a
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_download_pack(
Packit ae9e2a
		git_transport *transport,
Packit ae9e2a
		git_repository *repo,
Packit ae9e2a
		git_transfer_progress *stats,
Packit ae9e2a
		git_transfer_progress_cb progress_cb,
Packit ae9e2a
		void *progress_payload)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local*)transport;
Packit ae9e2a
	git_revwalk *walk = NULL;
Packit ae9e2a
	git_remote_head *rhead;
Packit ae9e2a
	unsigned int i;
Packit ae9e2a
	int error = -1;
Packit ae9e2a
	git_packbuilder *pack = NULL;
Packit ae9e2a
	git_odb_writepack *writepack = NULL;
Packit ae9e2a
	git_odb *odb = NULL;
Packit ae9e2a
	git_buf progress_info = GIT_BUF_INIT;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_revwalk_new(&walk, t->repo)) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
	git_revwalk_sorting(walk, GIT_SORT_TIME);
Packit ae9e2a
Packit ae9e2a
	if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	git_packbuilder_set_callbacks(pack, local_counting, t);
Packit ae9e2a
Packit ae9e2a
	stats->total_objects = 0;
Packit ae9e2a
	stats->indexed_objects = 0;
Packit ae9e2a
	stats->received_objects = 0;
Packit ae9e2a
	stats->received_bytes = 0;
Packit ae9e2a
Packit ae9e2a
	git_vector_foreach(&t->refs, i, rhead) {
Packit ae9e2a
		git_object *obj;
Packit ae9e2a
		if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
Packit ae9e2a
			goto cleanup;
Packit ae9e2a
Packit ae9e2a
		if (git_object_type(obj) == GIT_OBJ_COMMIT) {
Packit ae9e2a
			/* Revwalker includes only wanted commits */
Packit ae9e2a
			error = git_revwalk_push(walk, &rhead->oid);
Packit ae9e2a
			if (!error && !git_oid_iszero(&rhead->loid)) {
Packit ae9e2a
				error = git_revwalk_hide(walk, &rhead->loid);
Packit ae9e2a
				if (error == GIT_ENOTFOUND)
Packit ae9e2a
					error = 0;
Packit ae9e2a
			}
Packit ae9e2a
		} else {
Packit ae9e2a
			/* Tag or some other wanted object. Add it on its own */
Packit ae9e2a
			error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name);
Packit ae9e2a
		}
Packit ae9e2a
		git_object_free(obj);
Packit ae9e2a
		if (error < 0)
Packit ae9e2a
			goto cleanup;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if ((error = git_packbuilder_insert_walk(pack, walk)))
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_buf_printf(&progress_info, counting_objects_fmt, git_packbuilder_object_count(pack))) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	if (t->progress_cb &&
Packit ae9e2a
	    (error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload)) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	/* Walk the objects, building a packfile */
Packit ae9e2a
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	/* One last one with the newline */
Packit ae9e2a
	git_buf_clear(&progress_info);
Packit ae9e2a
	git_buf_printf(&progress_info, counting_objects_fmt, git_packbuilder_object_count(pack));
Packit ae9e2a
	if ((error = git_buf_putc(&progress_info, '\n')) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	if (t->progress_cb &&
Packit ae9e2a
	    (error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload)) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	/* Write the data to the ODB */
Packit ae9e2a
	{
Packit ae9e2a
		foreach_data data = {0};
Packit ae9e2a
		data.stats = stats;
Packit ae9e2a
		data.progress_cb = progress_cb;
Packit ae9e2a
		data.progress_payload = progress_payload;
Packit ae9e2a
		data.writepack = writepack;
Packit ae9e2a
Packit ae9e2a
		/* autodetect */
Packit ae9e2a
		git_packbuilder_set_threads(pack, 0);
Packit ae9e2a
Packit ae9e2a
		if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0)
Packit ae9e2a
			goto cleanup;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	error = writepack->commit(writepack, stats);
Packit ae9e2a
Packit ae9e2a
cleanup:
Packit ae9e2a
	if (writepack) writepack->free(writepack);
Packit ae9e2a
	git_buf_free(&progress_info);
Packit ae9e2a
	git_packbuilder_free(pack);
Packit ae9e2a
	git_revwalk_free(walk);
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_set_callbacks(
Packit ae9e2a
	git_transport *transport,
Packit ae9e2a
	git_transport_message_cb progress_cb,
Packit ae9e2a
	git_transport_message_cb error_cb,
Packit ae9e2a
	git_transport_certificate_check_cb certificate_check_cb,
Packit ae9e2a
	void *message_cb_payload)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
Packit ae9e2a
	GIT_UNUSED(certificate_check_cb);
Packit ae9e2a
Packit ae9e2a
	t->progress_cb = progress_cb;
Packit ae9e2a
	t->error_cb = error_cb;
Packit ae9e2a
	t->message_cb_payload = message_cb_payload;
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_is_connected(git_transport *transport)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
Packit ae9e2a
	return t->connected;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_read_flags(git_transport *transport, int *flags)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
Packit ae9e2a
	*flags = t->flags;
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static void local_cancel(git_transport *transport)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
Packit ae9e2a
	git_atomic_set(&t->cancelled, 1);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int local_close(git_transport *transport)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
Packit ae9e2a
	t->connected = 0;
Packit ae9e2a
Packit ae9e2a
	if (t->repo) {
Packit ae9e2a
		git_repository_free(t->repo);
Packit ae9e2a
		t->repo = NULL;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (t->url) {
Packit ae9e2a
		git__free(t->url);
Packit ae9e2a
		t->url = NULL;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static void local_free(git_transport *transport)
Packit ae9e2a
{
Packit ae9e2a
	transport_local *t = (transport_local *)transport;
Packit ae9e2a
Packit ae9e2a
	free_heads(&t->refs);
Packit ae9e2a
Packit ae9e2a
	/* Close the transport, if it's still open. */
Packit ae9e2a
	local_close(transport);
Packit ae9e2a
Packit ae9e2a
	/* Free the transport */
Packit ae9e2a
	git__free(t);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/**************
Packit ae9e2a
 * Public API *
Packit ae9e2a
 **************/
Packit ae9e2a
Packit ae9e2a
int git_transport_local(git_transport **out, git_remote *owner, void *param)
Packit ae9e2a
{
Packit ae9e2a
	int error;
Packit ae9e2a
	transport_local *t;
Packit ae9e2a
Packit ae9e2a
	GIT_UNUSED(param);
Packit ae9e2a
Packit ae9e2a
	t = git__calloc(1, sizeof(transport_local));
Packit ae9e2a
	GITERR_CHECK_ALLOC(t);
Packit ae9e2a
Packit ae9e2a
	t->parent.version = GIT_TRANSPORT_VERSION;
Packit ae9e2a
	t->parent.set_callbacks = local_set_callbacks;
Packit ae9e2a
	t->parent.connect = local_connect;
Packit ae9e2a
	t->parent.negotiate_fetch = local_negotiate_fetch;
Packit ae9e2a
	t->parent.download_pack = local_download_pack;
Packit ae9e2a
	t->parent.push = local_push;
Packit ae9e2a
	t->parent.close = local_close;
Packit ae9e2a
	t->parent.free = local_free;
Packit ae9e2a
	t->parent.ls = local_ls;
Packit ae9e2a
	t->parent.is_connected = local_is_connected;
Packit ae9e2a
	t->parent.read_flags = local_read_flags;
Packit ae9e2a
	t->parent.cancel = local_cancel;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) {
Packit ae9e2a
		git__free(t);
Packit ae9e2a
		return error;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	t->owner = owner;
Packit ae9e2a
Packit ae9e2a
	*out = (git_transport *) t;
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}