summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2020-04-04 18:31:00 +0100
committerEdward Thomson <ethomson@edwardthomson.com>2022-04-14 09:59:07 -0400
commit8a757ae24c8c4fdb3e2112de541aee8e7812ebda (patch)
treef2a6e73e4eef01585f9e7795fe2c070709d499bd
parent7babe76f97822c701050c6ee8d3984462f73c27f (diff)
downloadlibgit2-8a757ae24c8c4fdb3e2112de541aee8e7812ebda.tar.gz
cli: introduce a clone command
-rw-r--r--src/cli/cmd.h1
-rw-r--r--src/cli/cmd_clone.c176
-rw-r--r--src/cli/main.c1
-rw-r--r--src/cli/progress.c38
-rw-r--r--src/cli/progress.h8
5 files changed, 201 insertions, 23 deletions
diff --git a/src/cli/cmd.h b/src/cli/cmd.h
index 664b5021a..8b1a1b38f 100644
--- a/src/cli/cmd.h
+++ b/src/cli/cmd.h
@@ -26,6 +26,7 @@ extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name);
/* Commands */
extern int cmd_cat_file(int argc, char **argv);
+extern int cmd_clone(int argc, char **argv);
extern int cmd_hash_object(int argc, char **argv);
extern int cmd_help(int argc, char **argv);
diff --git a/src/cli/cmd_clone.c b/src/cli/cmd_clone.c
new file mode 100644
index 000000000..a382b5875
--- /dev/null
+++ b/src/cli/cmd_clone.c
@@ -0,0 +1,176 @@
+/*
+ * 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>
+#include "cli.h"
+#include "cmd.h"
+#include "error.h"
+#include "sighandler.h"
+#include "progress.h"
+
+#include "fs_path.h"
+#include "futils.h"
+
+#define COMMAND_NAME "clone"
+
+static char *branch, *remote_path, *local_path;
+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[] = {
+ { 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_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" },
+ { 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;
+}
+
+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;
+ }
+
+ if (bare)
+ clone_opts.bare = 1;
+
+ if (branch)
+ clone_opts.checkout_branch = branch;
+
+ 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;
+}
diff --git a/src/cli/main.c b/src/cli/main.c
index 08abb324f..cbfc50eec 100644
--- a/src/cli/main.c
+++ b/src/cli/main.c
@@ -29,6 +29,7 @@ const cli_opt_spec cli_common_opts[] = {
const cli_cmd_spec cli_cmds[] = {
{ "cat-file", cmd_cat_file, "Display an object in the repository" },
+ { "clone", cmd_clone, "Clone a repository into a new directory" },
{ "hash-object", cmd_hash_object, "Hash a raw object and product its object ID" },
{ "help", cmd_help, "Display help information" },
{ NULL }
diff --git a/src/cli/progress.c b/src/cli/progress.c
index 99c46d8ea..39b59531d 100644
--- a/src/cli/progress.c
+++ b/src/cli/progress.c
@@ -43,7 +43,7 @@ GIT_INLINE(size_t) nl_len(bool *has_nl, const char *str, size_t len)
return i;
}
-static int progress_write(cli_progress *progress, bool force, git_buf *line)
+static int progress_write(cli_progress *progress, bool force, git_str *line)
{
bool has_nl;
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)
/* Avoid spamming the console with progress updates */
if (!force && line->ptr[line->size - 1] != '\n' && progress->last_update) {
if (now - progress->last_update < PROGRESS_UPDATE_TIME) {
- git_buf_clear(&progress->deferred);
- git_buf_put(&progress->deferred, line->ptr, line->size);
- return git_buf_oom(&progress->deferred) ? -1 : 0;
+ git_str_clear(&progress->deferred);
+ git_str_put(&progress->deferred, line->ptr, line->size);
+ return git_str_oom(&progress->deferred) ? -1 : 0;
}
}
@@ -79,17 +79,17 @@ static int progress_write(cli_progress *progress, bool force, git_buf *line)
fflush(stdout) != 0)
return_os_error("could not print status");
- git_buf_clear(&progress->onscreen);
+ git_str_clear(&progress->onscreen);
if (line->ptr[line->size - 1] == '\n') {
progress->last_update = 0;
} else {
- git_buf_put(&progress->onscreen, line->ptr, line->size);
+ git_str_put(&progress->onscreen, line->ptr, line->size);
progress->last_update = now;
}
- git_buf_clear(&progress->deferred);
- return git_buf_oom(&progress->onscreen) ? -1 : 0;
+ git_str_clear(&progress->deferred);
+ return git_str_oom(&progress->onscreen) ? -1 : 0;
}
static int progress_printf(cli_progress *progress, bool force, const char *fmt, ...)
@@ -97,12 +97,12 @@ static int progress_printf(cli_progress *progress, bool force, const char *fmt,
int progress_printf(cli_progress *progress, bool force, const char *fmt, ...)
{
- git_buf buf = GIT_BUF_INIT;
+ git_str buf = GIT_BUF_INIT;
va_list ap;
int error;
va_start(ap, fmt);
- error = git_buf_vprintf(&buf, fmt, ap);
+ error = git_str_vprintf(&buf, fmt, ap);
va_end(ap);
if (error < 0)
@@ -110,7 +110,7 @@ int progress_printf(cli_progress *progress, bool force, const char *fmt, ...)
error = progress_write(progress, force, &buf);
- git_buf_dispose(&buf);
+ git_str_dispose(&buf);
return error;
}
@@ -123,8 +123,8 @@ static int progress_complete(cli_progress *progress)
if (printf("\n") < 0)
return_os_error("could not print status");
- git_buf_clear(&progress->deferred);
- git_buf_clear(&progress->onscreen);
+ git_str_clear(&progress->deferred);
+ git_str_clear(&progress->onscreen);
progress->last_update = 0;
progress->action_start = 0;
progress->action_finish = 0;
@@ -149,7 +149,7 @@ int cli_progress_fetch_sideband(const char *str, int len, void *payload)
return 0;
/* Accumulate the sideband data, then print it line-at-a-time. */
- if (git_buf_put(&progress->sideband, str, len) < 0)
+ if (git_str_put(&progress->sideband, str, len) < 0)
return -1;
str = progress->sideband.ptr;
@@ -174,7 +174,7 @@ int cli_progress_fetch_sideband(const char *str, int len, void *payload)
remain -= line_len;
}
- git_buf_consume_bytes(&progress->sideband, (progress->sideband.size - remain));
+ git_str_consume_bytes(&progress->sideband, (progress->sideband.size - remain));
return 0;
}
@@ -272,7 +272,7 @@ int cli_progress_fetch_transfer(const git_indexer_progress *stats, void *payload
default:
/* should not be reached */
- cli_die("unexpected progress state");
+ GIT_ASSERT(!"unexpected progress state");
}
return error;
@@ -322,9 +322,9 @@ void cli_progress_dispose(cli_progress *progress)
if (progress == NULL)
return;
- git_buf_dispose(&progress->sideband);
- git_buf_dispose(&progress->onscreen);
- git_buf_dispose(&progress->deferred);
+ git_str_dispose(&progress->sideband);
+ git_str_dispose(&progress->onscreen);
+ git_str_dispose(&progress->deferred);
memset(progress, 0, sizeof(cli_progress));
}
diff --git a/src/cli/progress.h b/src/cli/progress.h
index 3eebcf8ea..fab2e97a2 100644
--- a/src/cli/progress.h
+++ b/src/cli/progress.h
@@ -9,7 +9,7 @@
#define CLI_progress_h__
#include <git2.h>
-#include "buffer.h"
+#include "str.h"
/*
* A general purpose set of progress printing functions. An individual
@@ -37,9 +37,9 @@ typedef struct {
double last_update;
/* Accumulators for partial output and deferred updates. */
- git_buf sideband;
- git_buf onscreen;
- git_buf deferred;
+ git_str sideband;
+ git_str onscreen;
+ git_str deferred;
} cli_progress;
#define CLI_PROGRESS_INIT { 0 }