checkout.c 18.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 * 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 <assert.h>

#include "git2/checkout.h"
#include "git2/repository.h"
#include "git2/refs.h"
#include "git2/tree.h"
14
#include "git2/blob.h"
15
#include "git2/config.h"
16
#include "git2/diff.h"
17 18 19

#include "common.h"
#include "refs.h"
20
#include "buffer.h"
21
#include "repository.h"
Ben Straub committed
22
#include "filter.h"
Ben Straub committed
23
#include "blob.h"
24
#include "diff.h"
25
#include "pathspec.h"
26

27 28 29 30
typedef struct {
	git_repository *repo;
	git_diff_list *diff;
	git_checkout_opts *opts;
31
	git_buf *path;
32
	size_t workdir_len;
33
	bool can_symlink;
34
	int error;
35 36
	size_t total_steps;
	size_t completed_steps;
37
} checkout_diff_data;
38 39 40 41

static int buffer_to_file(
	git_buf *buffer,
	const char *path,
42
	mode_t dir_mode,
43 44 45
	int file_open_flags,
	mode_t file_mode)
{
46
	int fd, error;
47

48 49
	if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
		return error;
50

51 52
	if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {
		giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
53
		return fd;
54
	}
55

56 57 58 59 60 61 62
	if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {
		giterr_set(GITERR_OS, "Could not write to '%s'", path);
		(void)p_close(fd);
	} else {
		if ((error = p_close(fd)) < 0)
			giterr_set(GITERR_OS, "Error while closing '%s'", path);
	}
63 64 65 66 67 68 69

	if (!error &&
		(file_mode & 0100) != 0 &&
		(error = p_chmod(path, file_mode)) < 0)
		giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);

	return error;
Ben Straub committed
70 71
}

72 73 74
static int blob_content_to_file(
	git_blob *blob,
	const char *path,
75
	mode_t entry_filemode,
76
	git_checkout_opts *opts)
77
{
78
	int error = -1, nb_filters = 0;
79
	mode_t file_mode = opts->file_mode;
80 81 82
	bool dont_free_filtered = false;
	git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
	git_vector filters = GIT_VECTOR_INIT;
83

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
	if (opts->disable_filters ||
		(nb_filters = git_filters_load(
			&filters,
			git_object_owner((git_object *)blob),
			path,
			GIT_FILTER_TO_WORKTREE)) == 0) {

		/* Create a fake git_buf from the blob raw data... */
		filtered.ptr = blob->odb_object->raw.data;
		filtered.size = blob->odb_object->raw.len;

		/* ... and make sure it doesn't get unexpectedly freed */
		dont_free_filtered = true;
	}

	if (nb_filters < 0)
		return nb_filters;
101

102
	if (nb_filters > 0)	 {
103
		if ((error = git_blob__getbuf(&unfiltered, blob)) < 0)
104 105 106 107 108
			goto cleanup;

		if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0)
			goto cleanup;
	}
109

110 111
	/* Allow overriding of file mode */
	if (!file_mode)
112
		file_mode = entry_filemode;
113

114 115
	error = buffer_to_file(
		&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
116

117
cleanup:
118 119 120 121 122 123
	git_filters_free(&filters);
	git_buf_free(&unfiltered);
	if (!dont_free_filtered)
		git_buf_free(&filtered);

	return error;
124 125
}

126 127
static int blob_content_to_link(
	git_blob *blob, const char *path, bool can_symlink)
128 129 130
{
	git_buf linktarget = GIT_BUF_INIT;
	int error;
131

132 133
	if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
		return error;
134 135 136

	if (can_symlink)
		error = p_symlink(git_buf_cstr(&linktarget), path);
137
	else
138
		error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
139

140 141 142
	git_buf_free(&linktarget);

	return error;
143 144
}

145
static int checkout_submodule(
146
	checkout_diff_data *data,
147 148
	const git_diff_file *file)
{
149 150 151 152
	/* Until submodules are supported, UPDATE_ONLY means do nothing here */
	if ((data->opts->checkout_strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
		return 0;

153
	if (git_futils_mkdir(
154 155
			file->path, git_repository_workdir(data->repo),
			data->opts->dir_mode, GIT_MKDIR_PATH) < 0)
156 157
		return -1;

158
	/* TODO: Support checkout_strategy options.  Two circumstances:
159 160 161 162
	 * 1 - submodule already checked out, but we need to move the HEAD
	 *     to the new OID, or
	 * 2 - submodule not checked out and we should recursively check it out
	 *
163 164
	 * Checkout will not execute a pull on the submodule, but a clone
	 * command should probably be able to.  Do we need a submodule callback?
165 166 167 168 169
	 */

	return 0;
}

170
static void report_progress(
171
	checkout_diff_data *data,
172
	const char *path)
173
{
174 175 176 177
	if (data->opts->progress_cb)
		data->opts->progress_cb(
			path, data->completed_steps, data->total_steps,
			data->opts->progress_payload);
178 179
}

180
static int checkout_blob(
181
	checkout_diff_data *data,
182
	const git_diff_file *file)
183
{
184
	int error = 0;
185
	git_blob *blob;
Ben Straub committed
186

187
	git_buf_truncate(data->path, data->workdir_len);
188
	if (git_buf_puts(data->path, file->path) < 0)
189
		return -1;
190

191
	if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
192 193 194 195 196
		return error;

	if (S_ISLNK(file->mode))
		error = blob_content_to_link(
			blob, git_buf_cstr(data->path), data->can_symlink);
197
	else
198
		error = blob_content_to_file(
199
			blob, git_buf_cstr(data->path), file->mode, data->opts);
200 201 202 203 204 205

	git_blob_free(blob);

	return error;
}

206
static int retrieve_symlink_caps(git_repository *repo, bool *can_symlink)
207
{
208
	git_config *cfg;
209
	int error;
210

211 212 213 214 215
	if (git_repository_config__weakptr(&cfg, repo) < 0)
		return -1;

	error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks");

216
	/* If "core.symlinks" is not found anywhere, default to true. */
217 218 219 220 221 222 223 224
	if (error == GIT_ENOTFOUND) {
		*can_symlink = true;
		error = 0;
	}

	return error;
}

225 226
static void normalize_options(
	git_checkout_opts *normalized, git_checkout_opts *proposed)
227 228 229 230 231 232 233
{
	assert(normalized);

	if (!proposed)
		memset(normalized, 0, sizeof(git_checkout_opts));
	else
		memmove(normalized, proposed, sizeof(git_checkout_opts));
234

235 236 237 238 239 240 241
	/* implied checkout strategies */
	if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_MODIFIED) != 0 ||
		(normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
		normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_UNMODIFIED;

	if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
		normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_MISSING;
242

243
	/* opts->disable_filters is false by default */
244

245 246 247 248 249 250 251
	if (!normalized->dir_mode)
		normalized->dir_mode = GIT_DIR_MODE;

	if (!normalized->file_open_flags)
		normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
}

252 253 254
enum {
	CHECKOUT_ACTION__NONE = 0,
	CHECKOUT_ACTION__REMOVE = 1,
255 256 257 258
	CHECKOUT_ACTION__UPDATE_BLOB = 2,
	CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
	CHECKOUT_ACTION__CONFLICT = 8,
	CHECKOUT_ACTION__MAX = 8
259 260
};

261 262 263 264
static int checkout_confirm_update_blob(
	checkout_diff_data *data,
	const git_diff_delta *delta,
	int action)
265
{
266 267 268 269 270 271 272 273 274 275 276
	int error;
	unsigned int strat = data->opts->checkout_strategy;
	struct stat st;
	bool update_only = ((strat & GIT_CHECKOUT_UPDATE_ONLY) != 0);

	/* for typechange, remove the old item first */
	if (delta->status == GIT_DELTA_TYPECHANGE) {
		if (update_only)
			action = CHECKOUT_ACTION__NONE;
		else
			action |= CHECKOUT_ACTION__REMOVE;
277

278 279 280 281 282 283 284
		return action;
	}

	git_buf_truncate(data->path, data->workdir_len);
	if (git_buf_puts(data->path, delta->new_file.path) < 0)
		return -1;

285
	if ((error = p_lstat_posixly(git_buf_cstr(data->path), &st)) < 0) {
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
		if (errno == ENOENT) {
			if (update_only)
				action = CHECKOUT_ACTION__NONE;
		} else if (errno == ENOTDIR) {
			/* File exists where a parent dir needs to go - i.e. untracked
			 * typechange.  Ignore if UPDATE_ONLY, remove if allowed.
			 */
			if (update_only)
				action = CHECKOUT_ACTION__NONE;
			else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
				action |= CHECKOUT_ACTION__REMOVE;
			else
				action = CHECKOUT_ACTION__CONFLICT;
		}
		/* otherwise let error happen when we attempt blob checkout later */
	}
	else if (S_ISDIR(st.st_mode)) {
		/* Directory exists where a blob needs to go - i.e. untracked
		 * typechange.  Ignore if UPDATE_ONLY, remove if allowed.
		 */
		if (update_only)
			action = CHECKOUT_ACTION__NONE;
		else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
309
			action |= CHECKOUT_ACTION__REMOVE;
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
		else
			action = CHECKOUT_ACTION__CONFLICT;
	}

	return action;
}

static int checkout_action_for_delta(
	checkout_diff_data *data,
	const git_diff_delta *delta,
	const git_index_entry *head_entry)
{
	int action = CHECKOUT_ACTION__NONE;
	unsigned int strat  = data->opts->checkout_strategy;

	switch (delta->status) {
	case GIT_DELTA_UNMODIFIED:
		if (!head_entry) {
			/* file independently created in wd, even though not in HEAD */
			if ((strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
				action = CHECKOUT_ACTION__CONFLICT;
		}
		else if (!git_oid_equal(&head_entry->oid, &delta->old_file.oid)) {
			/* working directory was independently updated to match index */
			if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
				action = CHECKOUT_ACTION__CONFLICT;
		}
337 338
		break;

339 340 341
	case GIT_DELTA_ADDED:
		/* Impossible.  New files should be UNTRACKED or TYPECHANGE */
		action = CHECKOUT_ACTION__CONFLICT;
342 343 344
		break;

	case GIT_DELTA_DELETED:
345 346 347 348 349
		if (head_entry && /* working dir missing, but exists in HEAD */
			(strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
			action = CHECKOUT_ACTION__CONFLICT;
		else
			action = CHECKOUT_ACTION__UPDATE_BLOB;
350 351 352
		break;

	case GIT_DELTA_MODIFIED:
353 354 355 356 357 358 359 360 361 362 363 364 365 366
	case GIT_DELTA_TYPECHANGE:
		if (!head_entry) {
			/* working dir was independently updated & does not match index */
			if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) == 0)
				action = CHECKOUT_ACTION__CONFLICT;
			else
				action = CHECKOUT_ACTION__UPDATE_BLOB;
		}
		else if (git_oid_equal(&head_entry->oid, &delta->new_file.oid))
			action = CHECKOUT_ACTION__UPDATE_BLOB;
		else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
			action = CHECKOUT_ACTION__CONFLICT;
		else
			action = CHECKOUT_ACTION__UPDATE_BLOB;
367 368
		break;

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
	case GIT_DELTA_UNTRACKED:
		if (!head_entry) {
			if ((strat & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
				action = CHECKOUT_ACTION__REMOVE;
		}
		else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) != 0) {
			action = CHECKOUT_ACTION__REMOVE;
		} else if ((strat & GIT_CHECKOUT_UPDATE_UNMODIFIED) != 0) {
			git_oid wd_oid;

			/* if HEAD matches workdir, then remove, else conflict */

			if (git_oid_iszero(&delta->new_file.oid) &&
				git_diff__oid_for_file(
					data->repo, delta->new_file.path, delta->new_file.mode,
					delta->new_file.size, &wd_oid) < 0)
				action = -1;
			else if (git_oid_equal(&head_entry->oid, &wd_oid))
				action = CHECKOUT_ACTION__REMOVE;
			else
				action = CHECKOUT_ACTION__CONFLICT;
		} else {
			/* present in HEAD and workdir, but absent in index */
			action = CHECKOUT_ACTION__CONFLICT;
		}
		break;

	case GIT_DELTA_IGNORED:
397
	default:
398
		/* just skip these files */
399 400 401
		break;
	}

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
	if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
		if (S_ISGITLINK(delta->old_file.mode))
			action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
				CHECKOUT_ACTION__UPDATE_SUBMODULE;

		action = checkout_confirm_update_blob(data, delta, action);
	}

	if (action == CHECKOUT_ACTION__CONFLICT &&
		data->opts->conflict_cb != NULL &&
		data->opts->conflict_cb(
			delta->old_file.path, &delta->old_file.oid,
			delta->old_file.mode, delta->new_file.mode,
			data->opts->conflict_payload) != 0)
	{
		giterr_clear();
		action = GIT_EUSER;
	}

	if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0)
		action = (action & ~CHECKOUT_ACTION__REMOVE);
423 424 425 426 427 428 429

	return action;
}

static int checkout_get_actions(
	uint32_t **actions_ptr,
	size_t **counts_ptr,
430
	checkout_diff_data *data)
431
{
432 433
	int error;
	git_diff_list *diff = data->diff;
434
	git_diff_delta *delta;
435 436
	size_t i, *counts = NULL;
	uint32_t *actions = NULL;
437 438 439 440 441 442
	git_tree *head = NULL;
	git_iterator *hiter = NULL;
	char *pfx = git_pathspec_prefix(&data->opts->paths);
	const git_index_entry *he;

	/* if there is no HEAD, that's okay - we'll make an empty iterator */
443 444 445
	if (((error = git_repository_head_tree(&head, data->repo)) < 0) &&
		!(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD))
			return -1;
446 447 448 449 450 451 452 453

	if ((error = git_iterator_for_tree_range(
			 &hiter, data->repo, head, pfx, pfx)) < 0)
		goto fail;

	if ((diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
		!hiter->ignore_case &&
		(error = git_iterator_spoolandsort(
454
			&hiter, hiter, diff->entrycomp, true)) < 0)
455 456 457 458 459 460
		goto fail;

	if ((error = git_iterator_current(hiter, &he)) < 0)
		goto fail;

	git__free(pfx);
461
	pfx = NULL;
462 463 464 465 466 467 468

	*counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
	*actions_ptr = actions = git__calloc(diff->deltas.length, sizeof(uint32_t));
	if (!counts || !actions) {
		error = -1;
		goto fail;
	}
469

470 471 472 473 474 475
	git_vector_foreach(&diff->deltas, i, delta) {
		int cmp = -1, act;

		/* try to track HEAD entries parallel to deltas */
		while (he) {
			cmp = S_ISDIR(delta->new_file.mode) ?
476 477
				diff->pfxcomp(he->path, delta->new_file.path) :
				diff->strcomp(he->path, delta->old_file.path);
478 479 480 481 482
			if (cmp >= 0)
				break;
			if (git_iterator_advance(hiter, &he) < 0)
				he = NULL;
		}
483

484
		act = checkout_action_for_delta(data, delta, !cmp ? he : NULL);
485

486 487 488 489
		if (act < 0) {
			error = act;
			goto fail;
		}
490

491 492
		if (!cmp && git_iterator_advance(hiter, &he) < 0)
			he = NULL;
493

494
		actions[i] = act;
495

496 497 498 499 500 501 502 503 504
		if (act & CHECKOUT_ACTION__REMOVE)
			counts[CHECKOUT_ACTION__REMOVE]++;
		if (act & CHECKOUT_ACTION__UPDATE_BLOB)
			counts[CHECKOUT_ACTION__UPDATE_BLOB]++;
		if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
			counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;
		if (act & CHECKOUT_ACTION__CONFLICT)
			counts[CHECKOUT_ACTION__CONFLICT]++;
	}
505

506 507 508 509 510 511
	if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
		(data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
	{
		giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
				   (int)counts[CHECKOUT_ACTION__CONFLICT]);
		goto fail;
512 513
	}

514
	git_iterator_free(hiter);
515 516
	git_tree_free(head);

517
	return 0;
518 519 520 521 522 523 524 525

fail:
	*counts_ptr = NULL;
	git__free(counts);
	*actions_ptr = NULL;
	git__free(actions);

	git_iterator_free(hiter);
526
	git_tree_free(head);
527 528 529
	git__free(pfx);

	return -1;
530 531 532 533 534
}

static int checkout_remove_the_old(
	git_diff_list *diff,
	unsigned int *actions,
535
	checkout_diff_data *data)
536 537 538 539
{
	git_diff_delta *delta;
	size_t i;

540 541
	git_buf_truncate(data->path, data->workdir_len);

542 543 544
	git_vector_foreach(&diff->deltas, i, delta) {
		if (actions[i] & CHECKOUT_ACTION__REMOVE) {
			int error = git_futils_rmdir_r(
545 546 547 548
				delta->new_file.path,
				git_buf_cstr(data->path), /* here set to work dir root */
				GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS |
				GIT_RMDIR_REMOVE_BLOCKERS);
549 550 551 552 553 554 555 556 557 558 559 560 561 562
			if (error < 0)
				return error;

			data->completed_steps++;
			report_progress(data, delta->new_file.path);
		}
	}

	return 0;
}

static int checkout_create_the_new(
	git_diff_list *diff,
	unsigned int *actions,
563
	checkout_diff_data *data)
564 565 566 567 568
{
	git_diff_delta *delta;
	size_t i;

	git_vector_foreach(&diff->deltas, i, delta) {
569
		if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
570
			int error = checkout_blob(data, &delta->old_file);
571
			if (error < 0)
572 573 574 575 576 577 578 579 580 581 582 583 584
				return error;

			data->completed_steps++;
			report_progress(data, delta->old_file.path);
		}
	}

	return 0;
}

static int checkout_create_submodules(
	git_diff_list *diff,
	unsigned int *actions,
585
	checkout_diff_data *data)
586 587 588 589 590
{
	git_diff_delta *delta;
	size_t i;

	git_vector_foreach(&diff->deltas, i, delta) {
591
		if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
592 593 594 595 596 597 598 599 600 601 602 603
			int error = checkout_submodule(data, &delta->old_file);
			if (error < 0)
				return error;

			data->completed_steps++;
			report_progress(data, delta->old_file.path);
		}
	}

	return 0;
}

604
int git_checkout_index(
605
	git_repository *repo,
606
	git_index *index,
607
	git_checkout_opts *opts)
608 609 610 611
{
	git_diff_list *diff = NULL;
	git_diff_options diff_opts = {0};
	git_checkout_opts checkout_opts;
612
	checkout_diff_data data;
613
	git_buf workdir = GIT_BUF_INIT;
614 615
	uint32_t *actions = NULL;
	size_t *counts = NULL;
616 617
	int error;

618
	assert(repo);
619

620 621
	if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
		return error;
622

623
	diff_opts.flags =
624 625
		GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED |
		GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK;
626

627 628
	if (opts && opts->paths.count > 0)
		diff_opts.pathspec = opts->paths;
629

630
	if ((error = git_diff_workdir_to_index(&diff, repo, index, &diff_opts)) < 0)
631 632 633 634 635 636 637
		goto cleanup;

	if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0)
		goto cleanup;

	normalize_options(&checkout_opts, opts);

638 639 640 641 642 643 644 645 646 647
	/* Checkout is best performed with up to four passes through the diff.
	 *
	 * 0. Figure out what actions should be taken and record for later.
	 * 1. Next do removes, because we iterate in alphabetical order, thus
	 *    a new untracked directory will end up sorted *after* a blob that
	 *    should be checked out with the same name.
	 * 2. Then checkout all blobs.
	 * 3. Then checkout all submodules in case a new .gitmodules blob was
	 *    checked out during pass #2.
	 */
648

649
	memset(&data, 0, sizeof(data));
650 651
	data.path = &workdir;
	data.workdir_len = git_buf_len(&workdir);
652 653 654 655 656 657 658
	data.repo = repo;
	data.diff = diff;
	data.opts = &checkout_opts;

	if ((error = checkout_get_actions(&actions, &counts, &data)) < 0)
		goto cleanup;

659
	data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
660 661
		counts[CHECKOUT_ACTION__UPDATE_BLOB] +
		counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
662

663
	if ((error = retrieve_symlink_caps(repo, &data.can_symlink)) < 0)
664 665
		goto cleanup;

666
	report_progress(&data, NULL); /* establish 0 baseline */
667

668 669 670 671
	if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
		(error = checkout_remove_the_old(diff, actions, &data)) < 0)
		goto cleanup;

672
	if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
673 674 675
		(error = checkout_create_the_new(diff, actions, &data)) < 0)
		goto cleanup;

676
	if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
677 678 679 680
		(error = checkout_create_submodules(diff, actions, &data)) < 0)
		goto cleanup;

	assert(data.completed_steps == data.total_steps);
681

682
cleanup:
683
	if (error == GIT_EUSER)
684
		giterr_clear();
685

686 687
	git__free(actions);
	git__free(counts);
688
	git_diff_list_free(diff);
689
	git_buf_free(&workdir);
690

691 692 693 694 695
	return error;
}

int git_checkout_tree(
	git_repository *repo,
Vicent Marti committed
696
	const git_object *treeish,
697
	git_checkout_opts *opts)
698
{
699
	int error = 0;
700 701 702 703 704 705
	git_index *index = NULL;
	git_tree *tree = NULL;

	assert(repo && treeish);

	if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
706 707 708
		giterr_set(
			GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
		return -1;
709 710
	}

711 712
	/* TODO: create a temp index, load tree there and check it out */

713 714 715 716 717
	/* load paths in tree that match pathspec into index */
	if (!(error = git_repository_index(&index, repo)) &&
		!(error = git_index_read_tree_match(
			index, tree, opts ? &opts->paths : NULL)) &&
		!(error = git_index_write(index)))
718
		error = git_checkout_index(repo, NULL, opts);
719

720 721
	git_index_free(index);
	git_tree_free(tree);
722

723
	return error;
724 725
}

726 727
int git_checkout_head(
	git_repository *repo,
728
	git_checkout_opts *opts)
729 730
{
	int error;
731
	git_reference *head = NULL;
732
	git_object *tree = NULL;
733 734 735

	assert(repo);

736 737 738
	if (!(error = git_repository_head(&head, repo)) &&
		!(error = git_reference_peel(&tree, head, GIT_OBJ_TREE)))
		error = git_checkout_tree(repo, tree, opts);
739

740 741
	git_reference_free(head);
	git_object_free(tree);
742

743 744
	return error;
}