cmd_clone.c 4.72 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * 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 <stdio.h>
#include <git2.h>
10
#include "common.h"
11 12 13 14 15 16 17 18 19 20
#include "cmd.h"
#include "error.h"
#include "sighandler.h"
#include "progress.h"

#include "fs_path.h"
#include "futils.h"

#define COMMAND_NAME "clone"

21
static char *branch, *remote_path, *local_path, *depth;
22 23 24 25 26
static int show_help, quiet, checkout = 1, bare;
static bool local_path_exists;
static cli_progress progress = CLI_PROGRESS_INIT;

static const cli_opt_spec opts[] = {
Edward Thomson committed
27
	CLI_COMMON_OPT,
28 29 30 31 32 33 34 35 36

	{ CLI_OPT_TYPE_SWITCH,    "quiet",       'q', &quiet,       1,
	  CLI_OPT_USAGE_DEFAULT,   NULL,         "display the type of the object" },
	{ CLI_OPT_TYPE_SWITCH,    "no-checkout", 'n', &checkout,    0,
	  CLI_OPT_USAGE_DEFAULT,   NULL,         "don't checkout HEAD" },
	{ CLI_OPT_TYPE_SWITCH,    "bare",         0,  &bare,        1,
	  CLI_OPT_USAGE_DEFAULT,   NULL,         "don't create a working directory" },
	{ CLI_OPT_TYPE_VALUE,     "branch",      'b', &branch,      0,
	  CLI_OPT_USAGE_DEFAULT,  "name",        "branch to check out" },
37 38
	{ CLI_OPT_TYPE_VALUE,     "depth",       0,   &depth,       0,
	  CLI_OPT_USAGE_DEFAULT,  "depth",       "commit depth to check out " },
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
	{ CLI_OPT_TYPE_LITERAL },
	{ CLI_OPT_TYPE_ARG,       "repository",   0,  &remote_path, 0,
	  CLI_OPT_USAGE_REQUIRED, "repository",  "repository path" },
	{ CLI_OPT_TYPE_ARG,       "directory",    0,  &local_path,  0,
	  CLI_OPT_USAGE_DEFAULT,  "directory",    "directory to clone into" },
	{ 0 }
};

static void print_help(void)
{
	cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts);
	printf("\n");

	printf("Clone a repository into a new directory.\n");
	printf("\n");

	printf("Options:\n");

	cli_opt_help_fprint(stdout, opts);
}

static char *compute_local_path(const char *orig_path)
{
	const char *slash;
	char *local_path;

	if ((slash = strrchr(orig_path, '/')) == NULL &&
	    (slash = strrchr(orig_path, '\\')) == NULL)
		local_path = git__strdup(orig_path);
	else
		local_path = git__strdup(slash + 1);

	return local_path;
}

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
static int compute_depth(const char *depth)
{
	int64_t i;
	const char *endptr;

	if (!depth)
		return 0;

	if (git__strntol64(&i, depth, strlen(depth), &endptr, 10) < 0 || i < 0 || i > INT_MAX || *endptr) {
		fprintf(stderr, "fatal: depth '%s' is not valid.\n", depth);
		exit(128);
	}

	return (int)i;
}

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
static bool validate_local_path(const char *path)
{
	if (!git_fs_path_exists(path))
		return false;

	if (!git_fs_path_isdir(path) || !git_fs_path_is_empty_dir(path)) {
		fprintf(stderr, "fatal: destination path '%s' already exists and is not an empty directory.\n",
			path);
		exit(128);
	}

	return true;
}

static void cleanup(void)
{
	int rmdir_flags = GIT_RMDIR_REMOVE_FILES;

	cli_progress_abort(&progress);

	if (local_path_exists)
		rmdir_flags |= GIT_RMDIR_SKIP_ROOT;

	if (!git_fs_path_isdir(local_path))
		return;

	git_futils_rmdir_r(local_path, NULL, rmdir_flags);
}

static void interrupt_cleanup(void)
{
	cleanup();
	exit(130);
}

int cmd_clone(int argc, char **argv)
{
	git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
	git_repository *repo = NULL;
	cli_opt invalid_opt;
	char *computed_path = NULL;
	int ret = 0;

	if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU))
		return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt);

	if (show_help) {
		print_help();
		return 0;
	}

	if (!remote_path) {
		ret = cli_error_usage("you must specify a repository to clone");
		goto done;
	}

146 147 148
	clone_opts.bare = !!bare;
	clone_opts.checkout_branch = branch;
	clone_opts.fetch_opts.depth = compute_depth(depth);
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

	if (!checkout)
		clone_opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE;

	if (!local_path)
		local_path = computed_path = compute_local_path(remote_path);

	local_path_exists = validate_local_path(local_path);

	cli_sighandler_set_interrupt(interrupt_cleanup);

	if (!local_path_exists &&
	    git_futils_mkdir(local_path, 0777, 0) < 0) {
		ret = cli_error_git();
		goto done;
	}

	if (!quiet) {
		clone_opts.fetch_opts.callbacks.sideband_progress = cli_progress_fetch_sideband;
		clone_opts.fetch_opts.callbacks.transfer_progress = cli_progress_fetch_transfer;
		clone_opts.fetch_opts.callbacks.payload = &progress;

		clone_opts.checkout_opts.progress_cb = cli_progress_checkout;
		clone_opts.checkout_opts.progress_payload = &progress;

		printf("Cloning into '%s'...\n", local_path);
	}

	if (git_clone(&repo, remote_path, local_path, &clone_opts) < 0) {
		cleanup();
		ret = cli_error_git();
		goto done;
	}

	cli_progress_finish(&progress);

done:
	cli_progress_dispose(&progress);
	git__free(computed_path);
	git_repository_free(repo);
	return ret;
}