summaryrefslogtreecommitdiff
path: root/src/cli/cmd_clone.c
blob: 4dd5db8588f6091c694bcc9023cbadcffe8d5da6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
 * 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 <git2.h>
#include "cli.h"
#include "cmd.h"
#include "fs_path.h"
#include "futils.h"

#define COMMAND_NAME "clone"

static int show_help;
static int quiet;
static bool local_path_exists;

static char *remote_path, *local_path;

static const cli_opt_spec opts[] = {
	{ CLI_OPT_TYPE_SWITCH,   "help",          0, &show_help,  1,
	  CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL,
	  "display help about the " COMMAND_NAME " command" },

	{ CLI_OPT_TYPE_BOOL,      "quiet",      'q', &quiet,      0,
	  CLI_OPT_USAGE_DEFAULT,  NULL,         "do not display progress output" },

	{ CLI_OPT_TYPE_LITERAL },
	{ CLI_OPT_TYPE_ARG,       "repository",  0, &remote_path, 0,
	  CLI_OPT_USAGE_REQUIRED, "repository", "path to repository to clone" },
	{ 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 an existing repository into a local 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;
}

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))
		cli_die("fatal: destination path '%s' already exists and is not an empty directory.\n", path);

	return true;
}

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

	if (local_path_exists)
		rmdir_flags |= GIT_RMDIR_SKIP_ROOT;

	if (!git_fs_path_isdir(local_path))
		return;

	if (git_futils_rmdir_r(local_path, NULL, rmdir_flags) < 0)
		cli_die_git();
}

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;

	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 (!local_path)
		local_path = computed_path = compute_local_path(remote_path);

	local_path_exists = validate_local_path(local_path);

	if (!quiet)
		printf("Cloning into '%s'...\n", local_path);

	if (git_clone(&repo, remote_path, local_path, &clone_opts) < 0) {
		cleanup();
		cli_die_git();
	}

	git__free(computed_path);
	git_repository_free(repo);

	return 0;
}