Skip to content

Commit 8a757ae

Browse files
committed
cli: introduce a clone command
1 parent 7babe76 commit 8a757ae

File tree

5 files changed

+201
-23
lines changed

5 files changed

+201
-23
lines changed

src/cli/cmd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name);
2626

2727
/* Commands */
2828
extern int cmd_cat_file(int argc, char **argv);
29+
extern int cmd_clone(int argc, char **argv);
2930
extern int cmd_hash_object(int argc, char **argv);
3031
extern int cmd_help(int argc, char **argv);
3132

src/cli/cmd_clone.c

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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+
21+
static char *branch, *remote_path, *local_path;
22+
static int show_help, quiet, checkout = 1, bare;
23+
static bool local_path_exists;
24+
static cli_progress progress = CLI_PROGRESS_INIT;
25+
26+
static 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+
47+
static 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+
60+
static 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+
74+
static 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+
88+
static 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+
103+
static void interrupt_cleanup(void)
104+
{
105+
cleanup();
106+
exit(130);
107+
}
108+
109+
int 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+
171+
done:
172+
cli_progress_dispose(&progress);
173+
git__free(computed_path);
174+
git_repository_free(repo);
175+
return ret;
176+
}

src/cli/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const cli_opt_spec cli_common_opts[] = {
2929

3030
const cli_cmd_spec cli_cmds[] = {
3131
{ "cat-file", cmd_cat_file, "Display an object in the repository" },
32+
{ "clone", cmd_clone, "Clone a repository into a new directory" },
3233
{ "hash-object", cmd_hash_object, "Hash a raw object and product its object ID" },
3334
{ "help", cmd_help, "Display help information" },
3435
{ NULL }

src/cli/progress.c

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ GIT_INLINE(size_t) nl_len(bool *has_nl, const char *str, size_t len)
4343
return i;
4444
}
4545

46-
static int progress_write(cli_progress *progress, bool force, git_buf *line)
46+
static int progress_write(cli_progress *progress, bool force, git_str *line)
4747
{
4848
bool has_nl;
4949
size_t no_nl = no_nl_len(line->ptr, line->size);
@@ -54,9 +54,9 @@ static int progress_write(cli_progress *progress, bool force, git_buf *line)
5454
/* Avoid spamming the console with progress updates */
5555
if (!force && line->ptr[line->size - 1] != '\n' && progress->last_update) {
5656
if (now - progress->last_update < PROGRESS_UPDATE_TIME) {
57-
git_buf_clear(&progress->deferred);
58-
git_buf_put(&progress->deferred, line->ptr, line->size);
59-
return git_buf_oom(&progress->deferred) ? -1 : 0;
57+
git_str_clear(&progress->deferred);
58+
git_str_put(&progress->deferred, line->ptr, line->size);
59+
return git_str_oom(&progress->deferred) ? -1 : 0;
6060
}
6161
}
6262

@@ -79,38 +79,38 @@ static int progress_write(cli_progress *progress, bool force, git_buf *line)
7979
fflush(stdout) != 0)
8080
return_os_error("could not print status");
8181

82-
git_buf_clear(&progress->onscreen);
82+
git_str_clear(&progress->onscreen);
8383

8484
if (line->ptr[line->size - 1] == '\n') {
8585
progress->last_update = 0;
8686
} else {
87-
git_buf_put(&progress->onscreen, line->ptr, line->size);
87+
git_str_put(&progress->onscreen, line->ptr, line->size);
8888
progress->last_update = now;
8989
}
9090

91-
git_buf_clear(&progress->deferred);
92-
return git_buf_oom(&progress->onscreen) ? -1 : 0;
91+
git_str_clear(&progress->deferred);
92+
return git_str_oom(&progress->onscreen) ? -1 : 0;
9393
}
9494

9595
static int progress_printf(cli_progress *progress, bool force, const char *fmt, ...)
9696
GIT_FORMAT_PRINTF(3, 4);
9797

9898
int progress_printf(cli_progress *progress, bool force, const char *fmt, ...)
9999
{
100-
git_buf buf = GIT_BUF_INIT;
100+
git_str buf = GIT_BUF_INIT;
101101
va_list ap;
102102
int error;
103103

104104
va_start(ap, fmt);
105-
error = git_buf_vprintf(&buf, fmt, ap);
105+
error = git_str_vprintf(&buf, fmt, ap);
106106
va_end(ap);
107107

108108
if (error < 0)
109109
return error;
110110

111111
error = progress_write(progress, force, &buf);
112112

113-
git_buf_dispose(&buf);
113+
git_str_dispose(&buf);
114114
return error;
115115
}
116116

@@ -123,8 +123,8 @@ static int progress_complete(cli_progress *progress)
123123
if (printf("\n") < 0)
124124
return_os_error("could not print status");
125125

126-
git_buf_clear(&progress->deferred);
127-
git_buf_clear(&progress->onscreen);
126+
git_str_clear(&progress->deferred);
127+
git_str_clear(&progress->onscreen);
128128
progress->last_update = 0;
129129
progress->action_start = 0;
130130
progress->action_finish = 0;
@@ -149,7 +149,7 @@ int cli_progress_fetch_sideband(const char *str, int len, void *payload)
149149
return 0;
150150

151151
/* Accumulate the sideband data, then print it line-at-a-time. */
152-
if (git_buf_put(&progress->sideband, str, len) < 0)
152+
if (git_str_put(&progress->sideband, str, len) < 0)
153153
return -1;
154154

155155
str = progress->sideband.ptr;
@@ -174,7 +174,7 @@ int cli_progress_fetch_sideband(const char *str, int len, void *payload)
174174
remain -= line_len;
175175
}
176176

177-
git_buf_consume_bytes(&progress->sideband, (progress->sideband.size - remain));
177+
git_str_consume_bytes(&progress->sideband, (progress->sideband.size - remain));
178178

179179
return 0;
180180
}
@@ -272,7 +272,7 @@ int cli_progress_fetch_transfer(const git_indexer_progress *stats, void *payload
272272

273273
default:
274274
/* should not be reached */
275-
cli_die("unexpected progress state");
275+
GIT_ASSERT(!"unexpected progress state");
276276
}
277277

278278
return error;
@@ -322,9 +322,9 @@ void cli_progress_dispose(cli_progress *progress)
322322
if (progress == NULL)
323323
return;
324324

325-
git_buf_dispose(&progress->sideband);
326-
git_buf_dispose(&progress->onscreen);
327-
git_buf_dispose(&progress->deferred);
325+
git_str_dispose(&progress->sideband);
326+
git_str_dispose(&progress->onscreen);
327+
git_str_dispose(&progress->deferred);
328328

329329
memset(progress, 0, sizeof(cli_progress));
330330
}

src/cli/progress.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#define CLI_progress_h__
1010

1111
#include <git2.h>
12-
#include "buffer.h"
12+
#include "str.h"
1313

1414
/*
1515
* A general purpose set of progress printing functions. An individual
@@ -37,9 +37,9 @@ typedef struct {
3737
double last_update;
3838

3939
/* Accumulators for partial output and deferred updates. */
40-
git_buf sideband;
41-
git_buf onscreen;
42-
git_buf deferred;
40+
git_str sideband;
41+
git_str onscreen;
42+
git_str deferred;
4343
} cli_progress;
4444

4545
#define CLI_PROGRESS_INIT { 0 }

0 commit comments

Comments
 (0)