]> git.proxmox.com Git - libgit2.git/blame - src/cli/cmd_clone.c
Merge https://salsa.debian.org/debian/libgit2 into proxmox/bullseye
[libgit2.git] / src / cli / cmd_clone.c
CommitLineData
ad5611d8
TR
1/*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8#include <stdio.h>
9#include <git2.h>
10#include "cli.h"
11#include "cmd.h"
12#include "error.h"
13#include "sighandler.h"
14#include "progress.h"
15
16#include "fs_path.h"
17#include "futils.h"
18
19#define COMMAND_NAME "clone"
20
21static char *branch, *remote_path, *local_path;
22static int show_help, quiet, checkout = 1, bare;
23static bool local_path_exists;
24static cli_progress progress = CLI_PROGRESS_INIT;
25
26static const cli_opt_spec opts[] = {
27 { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
28 CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL,
29 "display help about the " COMMAND_NAME " command" },
30
31 { CLI_OPT_TYPE_SWITCH, "quiet", 'q', &quiet, 1,
32 CLI_OPT_USAGE_DEFAULT, NULL, "display the type of the object" },
33 { CLI_OPT_TYPE_SWITCH, "no-checkout", 'n', &checkout, 0,
34 CLI_OPT_USAGE_DEFAULT, NULL, "don't checkout HEAD" },
35 { CLI_OPT_TYPE_SWITCH, "bare", 0, &bare, 1,
36 CLI_OPT_USAGE_DEFAULT, NULL, "don't create a working directory" },
37 { CLI_OPT_TYPE_VALUE, "branch", 'b', &branch, 0,
38 CLI_OPT_USAGE_DEFAULT, "name", "branch to check out" },
39 { CLI_OPT_TYPE_LITERAL },
40 { CLI_OPT_TYPE_ARG, "repository", 0, &remote_path, 0,
41 CLI_OPT_USAGE_REQUIRED, "repository", "repository path" },
42 { CLI_OPT_TYPE_ARG, "directory", 0, &local_path, 0,
43 CLI_OPT_USAGE_DEFAULT, "directory", "directory to clone into" },
44 { 0 }
45};
46
47static void print_help(void)
48{
49 cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts);
50 printf("\n");
51
52 printf("Clone a repository into a new directory.\n");
53 printf("\n");
54
55 printf("Options:\n");
56
57 cli_opt_help_fprint(stdout, opts);
58}
59
60static char *compute_local_path(const char *orig_path)
61{
62 const char *slash;
63 char *local_path;
64
65 if ((slash = strrchr(orig_path, '/')) == NULL &&
66 (slash = strrchr(orig_path, '\\')) == NULL)
67 local_path = git__strdup(orig_path);
68 else
69 local_path = git__strdup(slash + 1);
70
71 return local_path;
72}
73
74static bool validate_local_path(const char *path)
75{
76 if (!git_fs_path_exists(path))
77 return false;
78
79 if (!git_fs_path_isdir(path) || !git_fs_path_is_empty_dir(path)) {
80 fprintf(stderr, "fatal: destination path '%s' already exists and is not an empty directory.\n",
81 path);
82 exit(128);
83 }
84
85 return true;
86}
87
88static void cleanup(void)
89{
90 int rmdir_flags = GIT_RMDIR_REMOVE_FILES;
91
92 cli_progress_abort(&progress);
93
94 if (local_path_exists)
95 rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
96
97 if (!git_fs_path_isdir(local_path))
98 return;
99
100 git_futils_rmdir_r(local_path, NULL, rmdir_flags);
101}
102
103static void interrupt_cleanup(void)
104{
105 cleanup();
106 exit(130);
107}
108
109int cmd_clone(int argc, char **argv)
110{
111 git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
112 git_repository *repo = NULL;
113 cli_opt invalid_opt;
114 char *computed_path = NULL;
115 int ret = 0;
116
117 if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU))
118 return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt);
119
120 if (show_help) {
121 print_help();
122 return 0;
123 }
124
125 if (!remote_path) {
126 ret = cli_error_usage("you must specify a repository to clone");
127 goto done;
128 }
129
130 if (bare)
131 clone_opts.bare = 1;
132
133 if (branch)
134 clone_opts.checkout_branch = branch;
135
136 if (!checkout)
137 clone_opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE;
138
139 if (!local_path)
140 local_path = computed_path = compute_local_path(remote_path);
141
142 local_path_exists = validate_local_path(local_path);
143
144 cli_sighandler_set_interrupt(interrupt_cleanup);
145
146 if (!local_path_exists &&
147 git_futils_mkdir(local_path, 0777, 0) < 0) {
148 ret = cli_error_git();
149 goto done;
150 }
151
152 if (!quiet) {
153 clone_opts.fetch_opts.callbacks.sideband_progress = cli_progress_fetch_sideband;
154 clone_opts.fetch_opts.callbacks.transfer_progress = cli_progress_fetch_transfer;
155 clone_opts.fetch_opts.callbacks.payload = &progress;
156
157 clone_opts.checkout_opts.progress_cb = cli_progress_checkout;
158 clone_opts.checkout_opts.progress_payload = &progress;
159
160 printf("Cloning into '%s'...\n", local_path);
161 }
162
163 if (git_clone(&repo, remote_path, local_path, &clone_opts) < 0) {
164 cleanup();
165 ret = cli_error_git();
166 goto done;
167 }
168
169 cli_progress_finish(&progress);
170
171done:
172 cli_progress_dispose(&progress);
173 git__free(computed_path);
174 git_repository_free(repo);
175 return ret;
176}