clone.c 10.9 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7 8
 *
 * 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>
9

10 11
#include "git2/clone.h"
#include "git2/remote.h"
12
#include "git2/revparse.h"
13 14
#include "git2/branch.h"
#include "git2/config.h"
15
#include "git2/checkout.h"
16 17
#include "git2/commit.h"
#include "git2/tree.h"
18 19 20 21

#include "common.h"
#include "remote.h"
#include "fileops.h"
22
#include "refs.h"
23
#include "path.h"
24
#include "repository.h"
25

26
static int create_branch(
27 28 29
	git_reference **branch,
	git_repository *repo,
	const git_oid *target,
30 31 32
	const char *name,
	const git_signature *signature,
	const char *log_message)
33
{
Vicent Marti committed
34
	git_commit *head_obj = NULL;
35
	git_reference *branch_ref = NULL;
36
	int error;
Ben Straub committed
37 38

	/* Find the target commit */
Vicent Marti committed
39
	if ((error = git_commit_lookup(&head_obj, repo, target)) < 0)
40
		return error;
Ben Straub committed
41 42

	/* Create the new branch */
43
	error = git_branch_create(&branch_ref, repo, name, head_obj, 0, signature, log_message);
Ben Straub committed
44

Vicent Marti committed
45
	git_commit_free(head_obj);
46

47
	if (!error)
48 49 50 51
		*branch = branch_ref;
	else
		git_reference_free(branch_ref);

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
	return error;
}

static int setup_tracking_config(
	git_repository *repo,
	const char *branch_name,
	const char *remote_name,
	const char *merge_target)
{
	git_config *cfg;
	git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT;
	int error = -1;

	if (git_repository_config__weakptr(&cfg, repo) < 0)
		return -1;

	if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0)
		goto cleanup;

	if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0)
		goto cleanup;

	if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0)
		goto cleanup;

	if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0)
		goto cleanup;

	error = 0;

cleanup:
	git_buf_free(&remote_key);
	git_buf_free(&merge_key);
	return error;
}

static int create_tracking_branch(
	git_reference **branch,
	git_repository *repo,
	const git_oid *target,
92 93 94
	const char *branch_name,
	const git_signature *signature,
	const char *log_message)
95 96 97
{
	int error;

98
	if ((error = create_branch(branch, repo, target, branch_name, signature, log_message)) < 0)
99 100 101 102 103 104 105
		return error;

	return setup_tracking_config(
		repo,
		branch_name,
		GIT_REMOTE_ORIGIN,
		git_reference_name(*branch));
106 107
}

108 109 110 111
struct head_info {
	git_repository *repo;
	git_oid remote_head_oid;
	git_buf branchname;
112
	const git_refspec *refspec;
113
	bool found;
114 115
};

116 117 118
static int reference_matches_remote_head(
	const char *reference_name,
	void *payload)
119
{
120
	struct head_info *head_info = (struct head_info *)payload;
Ben Straub committed
121
	git_oid oid;
122
	int error;
Ben Straub committed
123

124 125 126 127
	/* TODO: Should we guard against references
	 * which name doesn't start with refs/heads/ ?
	 */

128 129 130 131 132
	error = git_reference_name_to_id(&oid, head_info->repo, reference_name);
	if (error == GIT_ENOTFOUND) {
		/* If the reference doesn't exists, it obviously cannot match the
		 * expected oid. */
		giterr_clear();
133 134
		return 0;
	}
Ben Straub committed
135

136
	if (!error && !git_oid__cmp(&head_info->remote_head_oid, &oid)) {
137
		/* Determine the local reference name from the remote tracking one */
138
		error = git_refspec_rtransform(
139
			&head_info->branchname, head_info->refspec, reference_name);
140

141 142 143 144 145 146 147 148 149
		if (!error &&
			git_buf_len(&head_info->branchname) > 0 &&
			!(error = git_buf_sets(
				&head_info->branchname,
				git_buf_cstr(&head_info->branchname) +
				strlen(GIT_REFS_HEADS_DIR))))
		{
			head_info->found = true;
			error = GIT_ITEROVER;
150
		}
Ben Straub committed
151
	}
152

153
	return error;
154 155
}

156 157 158
static int update_head_to_new_branch(
	git_repository *repo,
	const git_oid *target,
159
	const char *name,
160
	const git_signature *signature,
161
	const char *reflog_message)
162
{
Michael Schubert committed
163
	git_reference *tracking_branch = NULL;
164 165
	int error = create_tracking_branch(&tracking_branch, repo, target, name,
			signature, reflog_message);
Ben Straub committed
166

167 168
	if (!error)
		error = git_repository_set_head(
169
			repo, git_reference_name(tracking_branch),
170
			signature, reflog_message);
171 172 173 174

	git_reference_free(tracking_branch);

	return error;
175 176
}

177 178 179
static int update_head_to_remote(
		git_repository *repo,
		git_remote *remote,
180
		const git_signature *signature,
181
		const char *reflog_message)
182
{
183
	int error = 0;
184
	size_t refs_len;
185
	git_refspec dummy_spec;
186
	const git_remote_head *remote_head, **refs;
187
	struct head_info head_info;
188
	git_buf remote_master_name = GIT_BUF_INIT;
Ben Straub committed
189

190 191
	if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0)
		return error;
192

193
	/* Did we just clone an empty repository? */
194
	if (refs_len == 0)
195
		return setup_tracking_config(
196
			repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE);
197

198 199
	/* Get the remote's HEAD. This is always the first ref in the list. */
	remote_head = refs[0];
200 201
	assert(remote_head);

202
	memset(&head_info, 0, sizeof(head_info));
Ben Straub committed
203 204
	git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);
	head_info.repo = repo;
205 206
	head_info.refspec =
		git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE);
207 208 209 210 211

	if (head_info.refspec == NULL) {
		memset(&dummy_spec, 0, sizeof(git_refspec));
		head_info.refspec = &dummy_spec;
	}
212

213
	/* Determine the remote tracking reference name from the local master */
214
	if ((error = git_refspec_transform(
215 216
		&remote_master_name,
		head_info.refspec,
217 218
		GIT_REFS_HEADS_MASTER_FILE)) < 0)
		return error;
219 220

	/* Check to see if the remote HEAD points to the remote master */
221 222
	error = reference_matches_remote_head(
		git_buf_cstr(&remote_master_name), &head_info);
223 224
	if (error < 0 && error != GIT_ITEROVER)
		goto cleanup;
225

226
	if (head_info.found) {
227
		error = update_head_to_new_branch(
228 229
			repo,
			&head_info.remote_head_oid,
230
			git_buf_cstr(&head_info.branchname),
231
			signature, reflog_message);
232
		goto cleanup;
Ben Straub committed
233
	}
234

Ben Straub committed
235
	/* Not master. Check all the other refs. */
236 237 238 239
	error = git_reference_foreach_name(
		repo, reference_matches_remote_head, &head_info);
	if (error < 0 && error != GIT_ITEROVER)
		goto cleanup;
240

241
	if (head_info.found) {
242
		error = update_head_to_new_branch(
243 244
			repo,
			&head_info.remote_head_oid,
245
			git_buf_cstr(&head_info.branchname),
246
			signature, reflog_message);
247
	} else {
248
		error = git_repository_set_head_detached(
249
			repo, &head_info.remote_head_oid, signature, reflog_message);
Ben Straub committed
250 251
	}

252 253
cleanup:
	git_buf_free(&remote_master_name);
Ben Straub committed
254
	git_buf_free(&head_info.branchname);
255
	return error;
256 257
}

258 259
static int update_head_to_branch(
		git_repository *repo,
260
		const char *remote_name,
261
		const char *branch,
262
		const git_signature *signature,
263
		const char *reflog_message)
264 265 266 267
{
	int retcode;
	git_buf remote_branch_name = GIT_BUF_INIT;
	git_reference* remote_ref = NULL;
nulltoken committed
268

269
	assert(remote_name && branch);
270 271

	if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
272
		remote_name, branch)) < 0 )
273 274 275 276 277
		goto cleanup;

	if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)
		goto cleanup;

278 279
	retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch,
			signature, reflog_message);
280 281 282

cleanup:
	git_reference_free(remote_ref);
Sascha Cunz committed
283
	git_buf_free(&remote_branch_name);
284 285 286
	return retcode;
}

287 288 289 290
/*
 * submodules?
 */

291 292 293 294 295 296 297
static int create_and_configure_origin(
		git_remote **out,
		git_repository *repo,
		const char *url,
		const git_clone_options *options)
{
	int error;
Ben Straub committed
298
	git_remote *origin = NULL;
299
	const char *name;
300

301 302
	name = options->remote_name ? options->remote_name : "origin";
	if ((error = git_remote_create(&origin, repo, name, url)) < 0)
303 304
		goto on_error;

305 306 307
	if (options->ignore_cert_errors)
		git_remote_check_cert(origin, 0);

308
	if ((error = git_remote_set_callbacks(origin, &options->remote_callbacks)) < 0)
309 310
		goto on_error;

Ben Straub committed
311 312 313
	if ((error = git_remote_save(origin)) < 0)
		goto on_error;

314 315 316 317
	*out = origin;
	return 0;

on_error:
Ben Straub committed
318
	git_remote_free(origin);
319 320
	return error;
}
321

322 323 324
static bool should_checkout(
	git_repository *repo,
	bool is_bare,
325
	const git_checkout_options *opts)
326 327 328 329 330 331 332
{
	if (is_bare)
		return false;

	if (!opts)
		return false;

333
	if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
334 335
		return false;

336
	return !git_repository_head_unborn(repo);
337
}
Ben Straub committed
338

339
int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature)
340 341
{
	int error = 0, old_fetchhead;
342
	git_strarray refspecs;
343
	git_buf reflog_message = GIT_BUF_INIT;
344 345 346 347 348 349 350 351

	assert(repo && remote);

	if (!git_repository_is_empty(repo)) {
		giterr_set(GITERR_INVALID, "the repository is not empty");
		return -1;
	}

352 353 354 355

	if ((error = git_remote_get_fetch_refspecs(&refspecs, remote)) < 0)
		return error;

356 357 358 359 360
	if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0)
		return error;

	old_fetchhead = git_remote_update_fetchhead(remote);
	git_remote_set_update_fetchhead(remote, 0);
361
	git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
362

363
	if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0)
364 365 366
		goto cleanup;

	if (branch)
367 368
		error = update_head_to_branch(repo, git_remote_name(remote), branch,
				signature, git_buf_cstr(&reflog_message));
369 370
	/* Point HEAD to the same ref as the remote's head */
	else
371
		error = update_head_to_remote(repo, remote, signature, git_buf_cstr(&reflog_message));
372 373 374 375 376 377

	if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
		error = git_checkout_head(repo, co_opts);

cleanup:
	git_remote_set_update_fetchhead(remote, old_fetchhead);
378

379
	/* Go back to the original refspecs */
380 381 382 383
	{
		int error_alt = git_remote_set_fetch_refspecs(remote, &refspecs);
		if (!error)
			error = error_alt;
384 385 386
	}

	git_strarray_free(&refspecs);
387
	git_buf_free(&reflog_message);
388 389 390 391

	return error;
}

392
int git_clone(
393
	git_repository **out,
394 395
	const char *url,
	const char *local_path,
396
	const git_clone_options *_options)
397
{
398
	int error = 0;
Ben Straub committed
399
	git_repository *repo = NULL;
400
	git_remote *origin;
401
	git_clone_options options = GIT_CLONE_OPTIONS_INIT;
402
	uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES;
403 404

	assert(out && url && local_path);
Ben Straub committed
405

406 407 408 409
	if (_options)
		memcpy(&options, _options, sizeof(git_clone_options));

	GITERR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
410

411 412 413
	/* Only clone to a new directory or an empty directory */
	if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) {
		giterr_set(GITERR_INVALID,
Russell Belfer committed
414
			"'%s' exists and is not an empty directory", local_path);
415
		return GIT_EEXISTS;
Ben Straub committed
416 417
	}

418 419 420
	/* Only remove the root directory on failure if we create it */
	if (git_path_exists(local_path))
		rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
421

422 423
	if ((error = git_repository_init(&repo, local_path, options.bare)) < 0)
		return error;
424

425 426
	if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
		error = git_clone_into(
427
			repo, origin, &options.checkout_opts, options.checkout_branch, options.signature);
428

429 430
		git_remote_free(origin);
	}
431

432
	if (error != 0) {
433 434 435
		git_error_state last_error = {0};
		giterr_capture(&last_error, error);

436 437
		git_repository_free(repo);
		repo = NULL;
438

439
		(void)git_futils_rmdir_r(local_path, NULL, rmdir_flags);
440 441

		giterr_restore(&last_error);
442
	}
Ben Straub committed
443

444
	*out = repo;
445
	return error;
446
}
447

448
int git_clone_init_options(git_clone_options *opts, unsigned int version)
449
{
450 451 452
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT);
	return 0;
453
}