diff --git a/CMakeLists.txt b/CMakeLists.txt index 90e3246..d84f74b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/subcommand/branch_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/checkout_subcommand.cpp ${GIT2CPP_SOURCE_DIR}/subcommand/checkout_subcommand.hpp + ${GIT2CPP_SOURCE_DIR}/subcommand/clone_subcommand.cpp + ${GIT2CPP_SOURCE_DIR}/subcommand/clone_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.cpp ${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp diff --git a/src/main.cpp b/src/main.cpp index 2d7d57e..ca92d6d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include "subcommand/add_subcommand.hpp" #include "subcommand/branch_subcommand.hpp" #include "subcommand/checkout_subcommand.hpp" +#include "subcommand/clone_subcommand.hpp" #include "subcommand/init_subcommand.hpp" #include "subcommand/status_subcommand.hpp" @@ -25,8 +26,11 @@ int main(int argc, char** argv) init_subcommand init(lg2_obj, app); status_subcommand status(lg2_obj, app); add_subcommand add(lg2_obj, app); - branch_subcommand(lg2_obj, app); - checkout_subcommand(lg2_obj, app); + branch_subcommand branch(lg2_obj, app); + checkout_subcommand checkout(lg2_obj, app); + clone_subcommand clone(lg2_obj, app); + + app.require_subcommand(/* min */ 0, /* max */ 1); CLI11_PARSE(app, argc, argv); diff --git a/src/subcommand/clone_subcommand.cpp b/src/subcommand/clone_subcommand.cpp new file mode 100644 index 0000000..93b5f6a --- /dev/null +++ b/src/subcommand/clone_subcommand.cpp @@ -0,0 +1,31 @@ +#include + +#include "../subcommand/clone_subcommand.hpp" +#include "../wrapper/repository_wrapper.hpp" + +clone_subcommand::clone_subcommand(const libgit2_object&, CLI::App& app) +{ + auto* sub = app.add_subcommand("clone", "Clone a directory into a new repository"); + + sub->add_option("", m_repository, "The (possibly remote) repository to clone from.")->required(); + sub->add_option("", m_directory, "The name of a new directory to clone into."); + + sub->callback([this]() { this->run(); }); +} + +void clone_subcommand::run() +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + std::string short_name = m_directory; + if (m_directory.empty()) + { + auto size = m_repository.size(); + auto begin = m_repository.find_last_of('/') + 1; + auto end = m_repository.ends_with(".git") ? size - 4 : size; + auto count = end - begin; + short_name = m_repository.substr(begin, count); + m_directory = get_current_git_path() + '/' + short_name; + } + std::cout << "Cloning into '" + short_name + "'..." << std::endl; + repository_wrapper::clone(m_repository, m_directory, opts); +} diff --git a/src/subcommand/clone_subcommand.hpp b/src/subcommand/clone_subcommand.hpp new file mode 100644 index 0000000..bf2a0d7 --- /dev/null +++ b/src/subcommand/clone_subcommand.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "../utils/common.hpp" + +class clone_subcommand +{ +public: + + explicit clone_subcommand(const libgit2_object&, CLI::App& app); + void run(); + +private: + + std::string m_repository = {}; + std::string m_directory = {}; +}; diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 8029d05..6be951d 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -21,6 +21,13 @@ repository_wrapper repository_wrapper::init(std::string_view directory, bool bar return rw; } +repository_wrapper repository_wrapper::clone(std::string_view url, std::string_view path, const git_clone_options& opts) +{ + repository_wrapper rw; + throw_if_error(git_clone(&(rw.p_resource), url.data(), path.data(), &opts)); + return rw; +} + git_repository_state_t repository_wrapper::state() const { return git_repository_state_t(git_repository_state(*this)); diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index e2b9c60..8d44aed 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -26,6 +26,7 @@ class repository_wrapper : public wrapper_base static repository_wrapper init(std::string_view directory, bool bare); static repository_wrapper open(std::string_view directory); + static repository_wrapper clone(std::string_view url, std::string_view path, const git_clone_options& opts); git_repository_state_t state() const; diff --git a/test/test_clone.py b/test/test_clone.py new file mode 100644 index 0000000..21aec78 --- /dev/null +++ b/test/test_clone.py @@ -0,0 +1,18 @@ +import os +import subprocess + +import pytest + +def test_clone(git2cpp_path): + url = 'https://github.com/xtensor-stack/xtl.git' + working_dir = 'test/data' + + clone_cmd = [git2cpp_path, 'clone', url] + subprocess.run(clone_cmd, capture_output=True, cwd = working_dir, text=True) + + assert os.path.exists(working_dir + '/xtl') + assert os.path.exists(working_dir + '/xtl/include') + + cleanup_cmd = ['rm', '-rf', 'xtl'] + subprocess.run(cleanup_cmd, capture_output=True, cwd = working_dir, text=True) +