diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2020-04-04 18:31:00 +0100 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2022-04-14 09:59:07 -0400 |
commit | 8a757ae24c8c4fdb3e2112de541aee8e7812ebda (patch) | |
tree | f2a6e73e4eef01585f9e7795fe2c070709d499bd | |
parent | 7babe76f97822c701050c6ee8d3984462f73c27f (diff) | |
download | libgit2-8a757ae24c8c4fdb3e2112de541aee8e7812ebda.tar.gz |
cli: introduce a clone command
-rw-r--r-- | src/cli/cmd.h | 1 | ||||
-rw-r--r-- | src/cli/cmd_clone.c | 176 | ||||
-rw-r--r-- | src/cli/main.c | 1 | ||||
-rw-r--r-- | src/cli/progress.c | 38 | ||||
-rw-r--r-- | src/cli/progress.h | 8 |
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 } |