From c2328b69f01841cf73162ebf4870b47727c7eabf Mon Sep 17 00:00:00 2001 From: Hoffmann Gerhard Date: Wed, 13 Mar 2024 13:17:02 +0100 Subject: [PATCH] added CheckoutBranch() --- Calculator/main.cpp | 1 + CalculatorCInterface/CalculatorCInterface.pro | 5 +- .../calculator_c_interface_lib.cpp | 312 +++++++++++++++++- .../calculator_c_interface_lib.h | 7 +- 4 files changed, 319 insertions(+), 6 deletions(-) diff --git a/Calculator/main.cpp b/Calculator/main.cpp index f9a85a6..820677f 100644 --- a/Calculator/main.cpp +++ b/Calculator/main.cpp @@ -33,6 +33,7 @@ int main(int argc, char *argv[]) if (InitGitLibrary() > 0) { qCritical() << CloneRepository("https://git.mimbach49.de/GerhardHoffmann/customer_999.git", "C:\\tmp\\customer_999"); + qCritical() << CheckoutLocalBranch("C:\\tmp\\customer_999", "master"); ShutdownGitLibrary(); } #else diff --git a/CalculatorCInterface/CalculatorCInterface.pro b/CalculatorCInterface/CalculatorCInterface.pro index 2f60b75..e0a6722 100644 --- a/CalculatorCInterface/CalculatorCInterface.pro +++ b/CalculatorCInterface/CalculatorCInterface.pro @@ -2,7 +2,9 @@ QT+=core TEMPLATE = lib DEFINES += CALCULATOR_C_INTERFACE_LIBRARY -QMAKE_CXXFLAGS += -fPIC + + +QMAKE_CXXFLAGS += -fPIC -std=c++20 unix { LIBS += -L/usr/lib64 -lgit2 @@ -10,6 +12,7 @@ unix { win32 { INCLUDEPATH += C:\Users\G.Hoffmann\Downloads\libgit2-1.7.2\libgit2-1.7.2\include + LIBS += -LC:\Users\G.Hoffmann\Downloads\libgit2-1.7.2\libgit2-1.7.2\build\Debug -lgit2 } diff --git a/CalculatorCInterface/calculator_c_interface_lib.cpp b/CalculatorCInterface/calculator_c_interface_lib.cpp index 98f1bea..717a637 100644 --- a/CalculatorCInterface/calculator_c_interface_lib.cpp +++ b/CalculatorCInterface/calculator_c_interface_lib.cpp @@ -1,13 +1,215 @@ #include "calculator_c_interface_lib.h" +//#include + +#include +#include +#include + + #include #include +#include +static QMap customerRepoMap; + +#ifndef _WIN32 +# include +#endif +#include #ifdef __cplusplus extern "C" { #endif +// see https://libgit2.org/libgit2/ex/HEAD/checkout.html + +/* Define t/he printf format specifier to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) +# define PRIuZ "Illu" +# define PRIxZ "Illx" +# define PRIdZ "Illd" +#else +# define PRIuZ "zu" +# define PRIxZ "zx" +# define PRIdZ "zd" +#endif + +typedef struct { + unsigned int force : 1; + unsigned int progress : 1; + unsigned int perf : 1; +} checkout_options; + +static void print_checkout_progress(const char *path, size_t completed_steps, size_t total_steps, void *payload) { + (void)payload; + if (path == NULL) { + printf("checkout started: %" PRIuZ " steps\n", total_steps); + } else { + printf("checkout: %s %" PRIuZ "/%" PRIuZ "\n", path, completed_steps, total_steps); + } +} + +static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload) { + (void)payload; + printf("perf: stat: %" PRIuZ " mkdir: %" PRIuZ " chmod: %" PRIuZ "\n", + perfdata->stat_calls, perfdata->mkdir_calls, perfdata->chmod_calls); +} + +static int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish) +{ + git_reference *ref; + git_object *obj; + int err = 0; + + assert(commit != NULL); + + err = git_reference_dwim(&ref, repo, refish); + if (err == GIT_OK) { + git_annotated_commit_from_ref(commit, repo, ref); + git_reference_free(ref); + return 0; + } + + err = git_revparse_single(&obj, repo, refish); + if (err == GIT_OK) { + err = git_annotated_commit_lookup(commit, repo, git_object_id(obj)); + git_object_free(obj); + } + + return err; +} + +static int perform_checkout_ref(git_repository *repo, + git_annotated_commit *target, + const char *target_ref, checkout_options *opts) { + git_checkout_options checkout_opts; + git_reference *ref = NULL, *branch = NULL; + git_commit *target_commit = NULL; + int err; + + if ((err = git_checkout_options_init(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION)) < 0) { + fprintf(stderr, "error checkout options init %s\n", git_error_last()->message); + return err; + } + + // Setup our checkout options from the parsed options + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + // if (opts->force) + // checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + // if (opts->progress) + // checkout_opts.progress_cb = print_checkout_progress; + + // if (opts->perf) + // checkout_opts.perfdata_cb = print_perf_data; + + // Grab the commit we're interested to move to + + err = git_commit_lookup(&target_commit, repo, git_annotated_commit_id(target)); + if (err != 0) { + fprintf(stderr, "failed to lookup commit: %s\n", git_error_last()->message); + goto cleanup; + } + + // Perform the checkout so the workdir corresponds to what target_commit contains. + // Note that it's okay to pass a git_commit here, because it will be peeled to a tree. + + err = git_checkout_tree(repo, (const git_object *)target_commit, &checkout_opts); + if (err != 0) { + fprintf(stderr, "failed to checkout tree: %s\n", git_error_last()->message); + goto cleanup; + } + + // Now that the checkout has completed, we have to update HEAD. + // Depending on the "origin" of target (ie. it's an OID or a branch name), we might need to detach HEAD. + + if (git_annotated_commit_ref(target)) { + const char *target_head; + + if ((err = git_reference_lookup(&ref, repo, git_annotated_commit_ref(target))) < 0) + goto error; + + if (git_reference_is_remote(ref)) { + if ((err = git_branch_create_from_annotated(&branch, repo, target_ref, target, 0)) < 0) + goto error; + target_head = git_reference_name(branch); + } else { + target_head = git_annotated_commit_ref(target); + } + + err = git_repository_set_head(repo, target_head); + } else { + err = git_repository_set_head_detached_from_annotated(repo, target); + } + +error: + if (err != 0) { + fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message); + goto cleanup; + } + +cleanup: + git_commit_free(target_commit); + git_reference_free(branch); + git_reference_free(ref); + + return err; +} + +static int guess_refish(git_annotated_commit **out, git_repository *repo, const char *ref) { + git_strarray remotes = { NULL, 0 }; + git_reference *remote_ref = NULL; + int error; + size_t i; + + if ((error = git_remote_list(&remotes, repo)) < 0) { + fprintf(stderr, "error git_remote_list %s\n", git_error_last()->message); + goto out; + } + + for (i = 0; i < remotes.count; i++) { + char *refname = NULL; + size_t reflen; + + reflen = snprintf(refname, 0, "refs/remotes/%s/%s", remotes.strings[i], ref); + if ((refname = (char *)malloc(reflen + 1)) == NULL) { + error = -1; + fprintf(stderr, "%s:%d malloc failed\n", __func__, __LINE__); + goto next; + } + snprintf(refname, reflen + 1, "refs/remotes/%s/%s", remotes.strings[i], ref); + + if ((error = git_reference_lookup(&remote_ref, repo, refname)) < 0) { + fprintf(stderr, "git reference lookup %s\n", git_error_last()->message); + goto next; + } + + break; + next: + free(refname); + refname = NULL; + if (error < 0 && error != GIT_ENOTFOUND) { + break; + } + } + + if (!remote_ref) { + error = GIT_ENOTFOUND; + goto out; + } + + if ((error = git_annotated_commit_from_ref(out, repo, remote_ref)) < 0) + goto out; + +out: + git_reference_free(remote_ref); + git_strarray_dispose(&remotes); + return error; +} + TariffCalculatorHandle NewTariffCalculator(void) { return new TariffCalculator(); } @@ -21,14 +223,29 @@ int InitGitLibrary(void) { } int ShutdownGitLibrary(void) { + + for (auto key : customerRepoMap.keys()) { + git_repository_free(customerRepoMap.value(key)); + } + customerRepoMap.clear(); return git_libgit2_shutdown(); } int CloneRepository(char const *url, char const *local_path) { + + QString localRepoPath(QDir::cleanPath(QString(local_path) + QDir::separator() + ".git")); + QDir localRepoDir(localRepoPath); + + if (localRepoDir.exists()) { + fprintf(stderr, "local repository path %s already exists\n", + localRepoPath.toUtf8().constData()); + return -1; + } + git_repository *out; git_clone_options opts; - int res = 0; + if ((res = git_clone_options_init(&opts, GIT_CLONE_OPTIONS_VERSION)) == 0) { opts.checkout_branch = "master"; @@ -37,6 +254,9 @@ int CloneRepository(char const *url, char const *local_path) { if ((res = git_clone(&out, url, local_path, &opts)) < 0) { git_error const *error = git_error_last(); fprintf(stderr, "%s:%d error: %s\n", __func__, __LINE__, error->message); + } else { + QString localRepoPath = QDir::cleanPath(QString(local_path) + QDir::separator() + ".git"); + customerRepoMap.insert(localRepoPath, out); } } else { git_error const *error = git_error_last(); @@ -45,6 +265,96 @@ int CloneRepository(char const *url, char const *local_path) { return res; } +int CheckoutLocalBranch(char const *local_path, char const *branch_name) { + int err = 0; + checkout_options opts; + git_repository_state_t state = GIT_REPOSITORY_STATE_NONE; + git_annotated_commit *checkout_target = NULL; + git_repository *repo = NULL; + git_reference *branch_ref = NULL; + + QString localRepoPath(QDir::cleanPath(QString(local_path) + QDir::separator() + ".git")); + QDir localRepoDir(localRepoPath); + + if (!localRepoDir.exists()) { + fprintf(stderr, "local repository path %s does not exist\n", + localRepoPath.toUtf8().constData()); + return -1; + } + + if (!customerRepoMap.contains(localRepoPath)) { + if ((err = git_repository_open(&repo, localRepoPath.toUtf8().constData())) != 0) { + fprintf(stderr, "repository %s cannot be opened: %s\n", + localRepoPath.toUtf8().constData(), git_error_last()->message); + return -1; + } + customerRepoMap.insert(localRepoPath, repo); + } + + if (customerRepoMap.contains(localRepoPath)) { + repo = customerRepoMap[localRepoPath]; + + // Make sure we're not about to checkout while something else is going on + if ((state = (git_repository_state_t)git_repository_state(repo)) != GIT_REPOSITORY_STATE_NONE) { + fprintf(stderr, "repository %s is in unexpected state %d\n", + localRepoPath.toUtf8().constData(), state); + err = -1; + } else { + fprintf(stderr, "repository %s is in state %d\n", + localRepoPath.toUtf8().constData(), state); + + if ((err = resolve_refish(&checkout_target, repo, branch_name)) < 0 && + (err = guess_refish(&checkout_target, repo, branch_name)) < 0) { + fprintf(stderr, "failed to resolve %s: %s\n", branch_name, git_error_last()->message); + } else { + if ((err = perform_checkout_ref(repo, checkout_target, branch_name, &opts)) == 0) { + fprintf(stderr, "%s:%d checkout ok\n", __func__, __LINE__); + if ((err = git_branch_lookup(&branch_ref, repo, branch_name, GIT_BRANCH_LOCAL)) != 0) { + if (err == GIT_ENOTFOUND) { + fprintf(stderr, "local branch %s not found\n", branch_name); + } else { + fprintf(stderr, "local branch %s not found: %s\n", + branch_name, git_error_last()->message); + } + } + } + } + } + } + + if (branch_ref) { + git_reference_free(branch_ref); + } + + if (checkout_target) { + git_annotated_commit_free(checkout_target); + } + + return err; +} + +int CommitFile(char const *local_path, char const *file_name) { + assert(local_path != NULL); + assert(file_name != NULL); + + QString const currentWD = QDir::currentPath(); + QString localPath(path); + + if (!localPath.exists()) { + fprintf(stderr, "local path %s for file %s does not exist\n", + localRepoPath.toUtf8().constData(), file_name); + return -1; + } + + if (QDir::setCurrent(localPath)) { + + } + + QDir::setCurrent(currentWD); + + return 0; +} + #ifdef __cplusplus } #endif diff --git a/CalculatorCInterface/calculator_c_interface_lib.h b/CalculatorCInterface/calculator_c_interface_lib.h index e7ed9ec..8f56c73 100644 --- a/CalculatorCInterface/calculator_c_interface_lib.h +++ b/CalculatorCInterface/calculator_c_interface_lib.h @@ -13,13 +13,12 @@ extern "C" { TariffCalculatorHandle NewTariffCalculator(void) CALCULATOR_C_INTERFACE_LIB_EXPORT; void DeleteTariffCalculator(TariffCalculatorHandle handle) CALCULATOR_C_INTERFACE_LIB_EXPORT; +// libgit2 int InitGitLibrary(void) CALCULATOR_C_INTERFACE_LIB_EXPORT; int ShutdownGitLibrary(void) CALCULATOR_C_INTERFACE_LIB_EXPORT; - int CloneRepository(char const *url, char const *local_path) CALCULATOR_C_INTERFACE_LIB_EXPORT; - - -void DeleteTariffCalculator(TariffCalculatorHandle handle) CALCULATOR_C_INTERFACE_LIB_EXPORT; +int CheckoutLocalBranch(char const *local_path, char const *branch) CALCULATOR_C_INTERFACE_LIB_EXPORT; +int CommitFile(char const *local_path, char const *branch_name, char const *file_name) CALCULATOR_C_INTERFACE_LIB_EXPORT; #ifdef __cplusplus }