local.c 9.69 KB
Newer Older
Vicent Marti committed
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
Vicent Marti committed
3 4 5 6
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
7 8 9 10
#include "common.h"
#include "git2/types.h"
#include "git2/net.h"
#include "git2/repository.h"
11 12
#include "git2/object.h"
#include "git2/tag.h"
13
#include "git2/transport.h"
14 15 16 17 18 19 20
#include "git2/revwalk.h"
#include "git2/odb_backend.h"
#include "git2/pack.h"
#include "git2/commit.h"
#include "git2/revparse.h"
#include "pack-objects.h"
#include "refs.h"
21
#include "posix.h"
22 23
#include "path.h"
#include "buffer.h"
24 25
#include "repository.h"
#include "odb.h"
26

27
typedef struct {
28
	git_transport parent;
29
	git_remote *owner;
30 31 32 33
	char *url;
	int direction;
	int flags;
	git_atomic cancelled;
34
	git_repository *repo;
35 36
	git_vector refs;
	unsigned connected : 1;
37
} transport_local;
38

39
static int add_ref(transport_local *t, const char *name)
40 41 42
{
	const char peeled[] = "^{}";
	git_remote_head *head;
43 44
	git_object *obj = NULL, *target = NULL;
	git_buf buf = GIT_BUF_INIT;
45
	int error;
46

47
	head = git__calloc(1, sizeof(git_remote_head));
48
	GITERR_CHECK_ALLOC(head);
49

50 51
	head->name = git__strdup(name);
	GITERR_CHECK_ALLOC(head->name);
52

53 54
	error = git_reference_name_to_id(&head->oid, t->repo, name);
	if (error < 0) {
55
		git__free(head->name);
56
		git__free(head);
57
		if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
58
			/* This is actually okay.  Empty repos often have a HEAD that points to
59
			 * a nonexistent "refs/heads/master". */
60 61 62 63
			giterr_clear();
			return 0;
		}
		return error;
64 65
	}

66
	if (git_vector_insert(&t->refs, head) < 0)
67
	{
68 69
		git__free(head->name);
		git__free(head);
70
		return -1;
71
	}
72 73 74

	/* If it's not a tag, we don't need to try to peel it */
	if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
75
		return 0;
76

77
	if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0)
78
		return -1;
79

80 81
	head = NULL;

82
	/* If it's not an annotated tag, just get out */
83 84 85 86
	if (git_object_type(obj) != GIT_OBJ_TAG) {
		git_object_free(obj);
		return 0;
	}
87 88

	/* And if it's a tag, peel it, and add it to the list */
89
	head = git__calloc(1, sizeof(git_remote_head));
90 91 92
	GITERR_CHECK_ALLOC(head);
	if (git_buf_join(&buf, 0, name, peeled) < 0)
		return -1;
93

94
	head->name = git_buf_detach(&buf);
95

96 97
	if (git_tag_peel(&target, (git_tag *) obj) < 0)
		goto on_error;
98

99 100 101
	git_oid_cpy(&head->oid, git_object_id(target));
	git_object_free(obj);
	git_object_free(target);
102

103
	if (git_vector_insert(&t->refs, head) < 0)
104
		return -1;
105

106
	return 0;
107

108
on_error:
109
	git_object_free(obj);
110 111
	git_object_free(target);
	return -1;
112 113
}

114
static int store_refs(transport_local *t)
115
{
116
	unsigned int i;
117
	git_strarray ref_names = {0};
118

119
	assert(t);
120

121
	if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
122
		git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
123
		goto on_error;
124

125
	/* Sort the references first */
126
	git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
127 128

	/* Add HEAD */
129 130
	if (add_ref(t, GIT_HEAD_FILE) < 0)
		goto on_error;
131

132
	for (i = 0; i < ref_names.count; ++i) {
133 134
		if (add_ref(t, ref_names.strings[i]) < 0)
			goto on_error;
135
	}
136

137
	git_strarray_free(&ref_names);
138 139 140
	return 0;

on_error:
141
	git_vector_free(&t->refs);
142
	git_strarray_free(&ref_names);
143
	return -1;
144
}
145

146 147 148 149
/*
 * Try to open the url as a git directory. The direction doesn't
 * matter in this case because we're calulating the heads ourselves.
 */
150 151 152 153
static int local_connect(
	git_transport *transport,
	const char *url,
	git_cred_acquire_cb cred_acquire_cb,
154
	void *cred_acquire_payload,
155
	int direction, int flags)
156 157 158 159 160
{
	git_repository *repo;
	int error;
	transport_local *t = (transport_local *) transport;
	const char *path;
161 162
	git_buf buf = GIT_BUF_INIT;

163
	GIT_UNUSED(cred_acquire_cb);
164
	GIT_UNUSED(cred_acquire_payload);
165

166 167 168 169
	t->url = git__strdup(url);
	GITERR_CHECK_ALLOC(t->url);
	t->direction = direction;
	t->flags = flags;
170 171

	/* The repo layer doesn't want the prefix */
172 173
	if (!git__prefixcmp(t->url, "file://")) {
		if (git_path_fromurl(&buf, t->url) < 0) {
174
			git_buf_free(&buf);
175
			return -1;
176 177 178
		}
		path = git_buf_cstr(&buf);

179
	} else { /* We assume transport->url is already a path */
180
		path = t->url;
181
	}
182 183

	error = git_repository_open(&repo, path);
184 185 186

	git_buf_free(&buf);

187 188
	if (error < 0)
		return -1;
189

190 191
	t->repo = repo;

192 193
	if (store_refs(t) < 0)
		return -1;
194

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
	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->connected) {
		giterr_set(GITERR_NET, "The transport is not connected");
		return -1;
	}

	git_vector_foreach(&t->refs, i, head) {
		if (list_cb(head, payload))
			return GIT_EUSER;
	}
215

216
	return 0;
217 218
}

219 220 221
static int local_negotiate_fetch(
	git_transport *transport,
	git_repository *repo,
222 223
	const git_remote_head * const *refs,
	size_t count)
224
{
225 226 227 228
	transport_local *t = (transport_local*)transport;
	git_remote_head *rhead;
	unsigned int i;

229 230
	GIT_UNUSED(refs);
	GIT_UNUSED(count);
231

232 233 234 235 236 237 238 239 240
	/* 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;
nulltoken committed
241
		git_object_free(obj);
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
		giterr_clear();
	}

	return 0;
}

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;
278
	git_odb *odb = NULL;
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309

	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 */
310 311
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
		goto cleanup;
312 313 314 315

	while ((error = git_revwalk_next(&oid, walk)) == 0) {
		git_commit *commit;

316 317 318
		/* Skip commits we already have */
		if (git_odb_exists(odb, &oid)) continue;

319
		if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
Vicent Marti committed
320
			const git_oid *tree_oid = git_commit_tree_id(commit);
321 322 323

			/* Add the commit and its tree */
			if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
324 325
				 (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) {
				git_commit_free(commit);
326
				goto cleanup;
327 328 329
			}

			git_commit_free(commit);
330 331 332
		}
	}

333 334
	if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)
		goto cleanup;
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353

	/* 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;
354 355
}

356
static int local_is_connected(git_transport *transport)
357 358 359
{
	transport_local *t = (transport_local *)transport;

360
	return t->connected;
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
}

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);
}

379
static int local_close(git_transport *transport)
380
{
381 382
	transport_local *t = (transport_local *)transport;

383
	t->connected = 0;
384 385 386
	git_repository_free(t->repo);
	t->repo = NULL;

387
	return 0;
388 389
}

390 391
static void local_free(git_transport *transport)
{
392
	unsigned int i;
393
	transport_local *t = (transport_local *) transport;
394 395
	git_vector *vec = &t->refs;
	git_remote_head *head;
396

397 398
	assert(transport);

399 400 401
	git_vector_foreach (vec, i, head) {
		git__free(head->name);
		git__free(head);
402
	}
403
	git_vector_free(vec);
404

405
	git__free(t->url);
406
	git__free(t);
407 408
}

409 410 411 412
/**************
 * Public API *
 **************/

413
int git_transport_local(git_transport **out, git_remote *owner, void *param)
414
{
415 416
	transport_local *t;

417 418
	GIT_UNUSED(param);

419
	t = git__calloc(1, sizeof(transport_local));
420
	GITERR_CHECK_ALLOC(t);
421

422
	t->parent.version = GIT_TRANSPORT_VERSION;
423
	t->parent.connect = local_connect;
424
	t->parent.negotiate_fetch = local_negotiate_fetch;
425
	t->parent.download_pack = local_download_pack;
426 427
	t->parent.close = local_close;
	t->parent.free = local_free;
428 429 430 431
	t->parent.ls = local_ls;
	t->parent.is_connected = local_is_connected;
	t->parent.read_flags = local_read_flags;
	t->parent.cancel = local_cancel;
432

433 434
	t->owner = owner;

435
	*out = (git_transport *) t;
436

437
	return 0;
438
}