clone.c 9.74 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 30
	git_reference **branch,
	git_repository *repo,
	const git_oid *target,
	const char *name)
31
{
Vicent Marti committed
32
	git_commit *head_obj = NULL;
33
	git_reference *branch_ref = NULL;
34
	int error;
Ben Straub committed
35 36

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

	/* Create the new branch */
41
	error = git_branch_create(&branch_ref, repo, name, head_obj, 0);
Ben Straub committed
42

Vicent Marti committed
43
	git_commit_free(head_obj);
44

45
	if (!error)
46 47 48 49
		*branch = branch_ref;
	else
		git_reference_free(branch_ref);

50 51 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 92 93 94 95 96 97 98 99 100 101
	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,
	const char *branch_name)
{
	int error;

	if ((error = create_branch(branch, repo, target, branch_name)) < 0)
		return error;

	return setup_tracking_config(
		repo,
		branch_name,
		GIT_REMOTE_ORIGIN,
		git_reference_name(*branch));
102 103
}

104 105 106 107
struct head_info {
	git_repository *repo;
	git_oid remote_head_oid;
	git_buf branchname;
108
	const git_refspec *refspec;
109
	bool found;
110 111
};

112 113 114
static int reference_matches_remote_head(
	const char *reference_name,
	void *payload)
115
{
116
	struct head_info *head_info = (struct head_info *)payload;
Ben Straub committed
117
	git_oid oid;
118
	int error;
Ben Straub committed
119

120 121 122 123
	/* TODO: Should we guard against references
	 * which name doesn't start with refs/heads/ ?
	 */

124 125 126 127 128
	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();
129 130
		return 0;
	}
Ben Straub committed
131

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

137 138 139 140 141 142 143 144 145
		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;
146
		}
Ben Straub committed
147
	}
148

149
	return error;
150 151
}

152 153 154 155
static int update_head_to_new_branch(
	git_repository *repo,
	const git_oid *target,
	const char *name)
156
{
Michael Schubert committed
157
	git_reference *tracking_branch = NULL;
158
	int error = create_tracking_branch(&tracking_branch, repo, target, name);
Ben Straub committed
159

160 161 162
	if (!error)
		error = git_repository_set_head(
			repo, git_reference_name(tracking_branch));
163 164 165 166

	git_reference_free(tracking_branch);

	return error;
167 168
}

169 170
static int update_head_to_remote(git_repository *repo, git_remote *remote)
{
171
	int error = 0;
172
	size_t refs_len;
173
	git_refspec dummy_spec;
174
	const git_remote_head *remote_head, **refs;
175
	struct head_info head_info;
176
	git_buf remote_master_name = GIT_BUF_INIT;
Ben Straub committed
177

178 179
	if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0)
		return error;
180

181
	/* Did we just clone an empty repository? */
182
	if (refs_len == 0)
183
		return setup_tracking_config(
184
			repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE);
185

186 187
	/* Get the remote's HEAD. This is always the first ref in the list. */
	remote_head = refs[0];
188 189
	assert(remote_head);

190
	memset(&head_info, 0, sizeof(head_info));
Ben Straub committed
191 192
	git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);
	head_info.repo = repo;
193 194
	head_info.refspec =
		git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE);
195 196 197 198 199

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

201
	/* Determine the remote tracking reference name from the local master */
202
	if ((error = git_refspec_transform(
203 204
		&remote_master_name,
		head_info.refspec,
205 206
		GIT_REFS_HEADS_MASTER_FILE)) < 0)
		return error;
207 208

	/* Check to see if the remote HEAD points to the remote master */
209 210
	error = reference_matches_remote_head(
		git_buf_cstr(&remote_master_name), &head_info);
211 212
	if (error < 0 && error != GIT_ITEROVER)
		goto cleanup;
213

214
	if (head_info.found) {
215
		error = update_head_to_new_branch(
216 217 218 219
			repo,
			&head_info.remote_head_oid,
			git_buf_cstr(&head_info.branchname));
		goto cleanup;
Ben Straub committed
220
	}
221

Ben Straub committed
222
	/* Not master. Check all the other refs. */
223 224 225 226
	error = git_reference_foreach_name(
		repo, reference_matches_remote_head, &head_info);
	if (error < 0 && error != GIT_ITEROVER)
		goto cleanup;
227

228
	if (head_info.found) {
229
		error = update_head_to_new_branch(
230 231 232 233
			repo,
			&head_info.remote_head_oid,
			git_buf_cstr(&head_info.branchname));
	} else {
234 235
		error = git_repository_set_head_detached(
			repo, &head_info.remote_head_oid);
Ben Straub committed
236 237
	}

238 239
cleanup:
	git_buf_free(&remote_master_name);
Ben Straub committed
240
	git_buf_free(&head_info.branchname);
241
	return error;
242 243
}

244 245
static int update_head_to_branch(
		git_repository *repo,
246 247
		const char *remote_name,
		const char *branch)
248 249 250 251
{
	int retcode;
	git_buf remote_branch_name = GIT_BUF_INIT;
	git_reference* remote_ref = NULL;
nulltoken committed
252

253
	assert(remote_name && branch);
254 255

	if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
256
		remote_name, branch)) < 0 )
257 258 259 260 261
		goto cleanup;

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

262
	retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch);
263 264 265

cleanup:
	git_reference_free(remote_ref);
Sascha Cunz committed
266
	git_buf_free(&remote_branch_name);
267 268 269
	return retcode;
}

270 271 272 273
/*
 * submodules?
 */

274 275 276 277 278 279 280
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
281
	git_remote *origin = NULL;
282
	const char *name;
283

284 285
	name = options->remote_name ? options->remote_name : "origin";
	if ((error = git_remote_create(&origin, repo, name, url)) < 0)
286 287
		goto on_error;

288 289 290
	if (options->ignore_cert_errors)
		git_remote_check_cert(origin, 0);

291
	if ((error = git_remote_set_callbacks(origin, &options->remote_callbacks)) < 0)
292 293
		goto on_error;

Ben Straub committed
294 295 296
	if ((error = git_remote_save(origin)) < 0)
		goto on_error;

297 298 299 300
	*out = origin;
	return 0;

on_error:
Ben Straub committed
301
	git_remote_free(origin);
302 303
	return error;
}
304

305 306 307
static bool should_checkout(
	git_repository *repo,
	bool is_bare,
308
	const git_checkout_opts *opts)
309 310 311 312 313 314 315
{
	if (is_bare)
		return false;

	if (!opts)
		return false;

316
	if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
317 318
		return false;

319
	return !git_repository_head_unborn(repo);
320
}
Ben Straub committed
321

322
int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch)
323 324
{
	int error = 0, old_fetchhead;
325
	git_strarray refspecs;
326 327 328 329 330 331 332 333

	assert(repo && remote);

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

334 335 336 337

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

338 339 340 341 342 343
	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);

344
	if ((error = git_remote_fetch(remote)) != 0)
345 346 347 348 349 350 351 352 353 354 355 356 357
		goto cleanup;

	if (branch)
		error = update_head_to_branch(repo, git_remote_name(remote), branch);
	/* Point HEAD to the same ref as the remote's head */
	else
		error = update_head_to_remote(repo, remote);

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

359
	/* Go back to the original refspecs */
360 361 362 363
	{
		int error_alt = git_remote_set_fetch_refspecs(remote, &refspecs);
		if (!error)
			error = error_alt;
364 365 366
	}

	git_strarray_free(&refspecs);
367 368 369 370

	return error;
}

371
int git_clone(
372
	git_repository **out,
373 374
	const char *url,
	const char *local_path,
375
	const git_clone_options *_options)
376
{
377
	int error = 0;
Ben Straub committed
378
	git_repository *repo = NULL;
379
	git_remote *origin;
380
	git_clone_options options = GIT_CLONE_OPTIONS_INIT;
381
	uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES;
382 383

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

385 386 387 388
	if (_options)
		memcpy(&options, _options, sizeof(git_clone_options));

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

390 391 392
	/* 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
393
			"'%s' exists and is not an empty directory", local_path);
394
		return GIT_EEXISTS;
Ben Straub committed
395 396
	}

397 398 399
	/* Only remove the root directory on failure if we create it */
	if (git_path_exists(local_path))
		rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
400

401 402
	if ((error = git_repository_init(&repo, local_path, options.bare)) < 0)
		return error;
403

404 405 406
	if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
		error = git_clone_into(
			repo, origin, &options.checkout_opts, options.checkout_branch);
407

408 409
		git_remote_free(origin);
	}
410

411
	if (error != 0) {
412 413
		git_repository_free(repo);
		repo = NULL;
414

415 416
		(void)git_futils_rmdir_r(local_path, NULL, rmdir_flags);
	}
Ben Straub committed
417

418
	*out = repo;
419
	return error;
420
}