Commit 8a757ae2 by Edward Thomson

cli: introduce a clone command

parent 7babe76f
......@@ -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);
......
/*
* 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;
}
......@@ -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 }
......
......@@ -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));
}
......@@ -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 }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment