From 4ab36ec64fe2679561d8fac39d072b742bd5f1d4 Mon Sep 17 00:00:00 2001 From: Hoffmann Gerhard Date: Wed, 3 Apr 2024 16:29:58 +0200 Subject: [PATCH] just safe --- CalculatorCInterface/CalculatorCInterface.pro | 3 +- .../calculator_c_interface_lib.cpp | 519 +------------- .../calculator_c_interface_lib.h | 7 +- CalculatorCInterface/git_library.cpp | 673 ++++++++++++++++-- CalculatorCInterface/git_library.h | 29 +- 5 files changed, 665 insertions(+), 566 deletions(-) diff --git a/CalculatorCInterface/CalculatorCInterface.pro b/CalculatorCInterface/CalculatorCInterface.pro index e5ffaec..3782dec 100644 --- a/CalculatorCInterface/CalculatorCInterface.pro +++ b/CalculatorCInterface/CalculatorCInterface.pro @@ -37,7 +37,8 @@ HEADERS += \ calculator_c_interface_lib_global.h \ git_library.h \ tariff_calculator.h \ - local_git_repository.h + local_git_repository.h \ + global_defines.h # Default rules for deployment. unix { diff --git a/CalculatorCInterface/calculator_c_interface_lib.cpp b/CalculatorCInterface/calculator_c_interface_lib.cpp index f79f7b6..4e19095 100644 --- a/CalculatorCInterface/calculator_c_interface_lib.cpp +++ b/CalculatorCInterface/calculator_c_interface_lib.cpp @@ -19,10 +19,8 @@ #include #include -static QMap customerRepoMap; -static std::mutex m; -static char const *user = NULL; -static char const *pass = NULL; +//static QMap customerRepoMap; +//static std::mutex m; #ifndef _WIN32 # include @@ -33,241 +31,6 @@ static char const *pass = NULL; 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; - -int cred_acquire_cb(git_credential **out, - const char *url, - const char *username_from_url, - unsigned int allowed_types, - void *payload) -{ - char const *username = user, *password = pass; - - fprintf(stderr, "%s:%d: opened repository %s %s\n", __func__, __LINE__, username, password); - - Q_UNUSED(url); - Q_UNUSED(username_from_url); - Q_UNUSED(payload); - int error = -1; - - if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) { - error = git_credential_userpass_plaintext_new(out, username, password); - } else { - fprintf(stderr, "GIT_CREDENTIAL_USERPASS_PLAINTEXT not supported by protocol (allowed_types=%08x)\n", allowed_types); - } - - return error; -} - -static bool check(int error, const char *message, const char *extra) { - const git_error *lg2err; - const char *lg2msg = "", *lg2spacer = ""; - - if (!error) { - return true; - } - - if ((lg2err = git_error_last()) != NULL && lg2err->message != NULL) { - lg2msg = lg2err->message; - lg2spacer = " - "; - } - - if (extra) - fprintf(stderr, "%s '%s' [%d]%s%s\n", - message, extra, error, lg2spacer, lg2msg); - else - fprintf(stderr, "%s [%d]%s%s\n", - message, error, lg2spacer, lg2msg); - - // exit(1); - return false; -} - -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(); @@ -290,276 +53,24 @@ int CloneRepositoryInternal(char const *url, char const *local_path) { return GitLibrary::CloneRepository(url, local_path); } -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 CheckoutRepositoryInternal(char const *url, char const *localRepoName, char const *branchName) { + return GitLibrary::CheckoutRepository(url, localRepoName, branchName); } -int CommitFile(char const *local_path, char const *branch_name, - char const *file_name, char const *comment) { - assert(local_path != NULL); - assert(branch_name != NULL); - assert(file_name != NULL); - assert(comment != NULL); - - fprintf(stderr, "%s %s %s %s\n", - local_path, branch_name, file_name, comment); - - QString const currentWD = QDir::currentPath(); - - QString localPath(local_path); - if (!QDir(localPath).exists()) { - fprintf(stderr, "local path %s for file %s does not exist\n", - localPath.toUtf8().constData(), file_name); - QDir::setCurrent(currentWD); - return -1; - } - - if (!QDir::setCurrent(localPath)) { - fprintf(stderr, "can not set cwd path %s\n", localPath.toUtf8().constData()); - QDir::setCurrent(currentWD); - return -1; - } - - fprintf(stderr, "cwd %s\n", QDir::current().absolutePath().toUtf8().constData()); - - git_repository *repo = NULL; - int error = 0; - - 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()); - QDir::setCurrent(currentWD); - return -1; - } - - if (!customerRepoMap.contains(localRepoPath)) { - if ((error = git_repository_open(&repo, localRepoPath.toUtf8().constData())) != 0) { - fprintf(stderr, "repository %s cannot be opened: %s\n", - localRepoPath.toUtf8().constData(), git_error_last()->message); - QDir::setCurrent(currentWD); - return -1; - } - customerRepoMap.insert(localRepoPath, repo); - } - - git_oid commit_oid,tree_oid; - git_tree *tree = NULL; - git_index *index = NULL; - git_object *parent = NULL; - git_reference *ref = NULL; - git_signature *signature = NULL; - git_repository_state_t state = GIT_REPOSITORY_STATE_NONE; - - if (customerRepoMap.contains(localRepoPath)) { - repo = customerRepoMap[localRepoPath]; - - if (git_repository_head_unborn(repo) == 1) { - fprintf(stderr, "HEAD unborn. Create first commit\n"); - return -1; - } - - // 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); - error = -1; - } else { - error = git_revparse_ext(&parent, &ref, repo, "HEAD"); - if (error == GIT_ENOTFOUND) { - fprintf(stderr, "HEAD not found. Creating first commit\n"); - error = 0; - } else if (error != 0) { - const git_error *err = git_error_last(); - if (err) { - fprintf(stderr, "ERROR %d: %s\n", err->klass, err->message); - } else { - fprintf(stderr, "ERROR %d: no detailed info\n", error); - } - } - - if (error == 0) { - if (check(git_repository_index(&index, repo), "Could not open repository index", NULL) && - check(git_index_write_tree(&tree_oid, index), "Could not write tree", NULL) && - check(git_index_write(index), "Could not write index", NULL) && - check(git_tree_lookup(&tree, repo, &tree_oid), "Error looking up tree", NULL) && - check(git_signature_default(&signature, repo), "Error creating signature", NULL) && - check(git_commit_create_v(&commit_oid, repo, "HEAD", signature, signature, - NULL, comment, tree, parent ? 1 : 0, parent), - "Error creating commit", NULL)) { - fprintf(stderr, "%s %s %s %s\n", - local_path, branch_name, file_name, comment); - fprintf(stderr, "git commit ok\n"); - } - - if (index) { - git_index_free(index); - } - if (signature) { - git_signature_free(signature); - } - if (tree) { - git_tree_free(tree); - } - if (parent) { - git_object_free(parent); - } - if (ref) { - git_reference_free(ref); - } - } - } - } - - QDir::setCurrent(currentWD); - return error; +int CommitFileInternal(char const *localRepoName, char const *branchName, + char const *fName, char const *commitMessage) { + return GitLibrary::CommitFile(localRepoName, branchName, fName, commitMessage); } -int PushLocalRepository(char const *local_path, char const *branch_name, char const *username, char const *password) { - git_repository *repo = NULL; - int error = -1; +int PushRepositoryInternal(char const *localRepoName, char const *branchName, + char const *userName, char const *userPassword) { + return GitLibrary::PushRepository(localRepoName, branchName, userName, userPassword); +} - fprintf(stderr, "%s:%d: %s %s\n", __func__, __LINE__, local_path, branch_name); - - QString const currentWD = QDir::currentPath(); - - 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()); - QDir::setCurrent(currentWD); - return -1; - } - - if (!customerRepoMap.contains(localRepoPath)) { - if ((error = git_repository_open(&repo, localRepoPath.toUtf8().constData())) != 0) { - fprintf(stderr, "repository %s cannot be opened: %s\n", - localRepoPath.toUtf8().constData(), git_error_last()->message); - QDir::setCurrent(currentWD); - return -1; - } - customerRepoMap.insert(localRepoPath, repo); - } - - fprintf(stderr, "%s:%d: opened repository\n", __func__, __LINE__); - - git_repository_state_t state = GIT_REPOSITORY_STATE_NONE; - git_push_options options; - git_remote_callbacks callbacks; - git_remote* remote = NULL; - char refspec_[] = "refs/heads/master"; - char *refspec = refspec_; - const git_strarray refspecs = { - &refspec, - 1 - }; - - std::lock_guard lock(m); - - user = username; - pass = password; - - fprintf(stderr, "%s:%d: opened repository %s %s\n", __func__, __LINE__, user, pass); - - 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); - } else { - if (check(git_remote_lookup(&remote, repo, "origin" ), "Unable to lookup remote", NULL)) { - if (check(git_remote_init_callbacks(&callbacks, GIT_REMOTE_CALLBACKS_VERSION), - "Error initializing remote callbacks", NULL)) { - callbacks.credentials = cred_acquire_cb; - if (check(git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION ), - "Error initializing push", NULL)) { - options.callbacks = callbacks; - if (check(git_remote_push(remote, &refspecs, &options), "Error pushing", NULL)) { - fprintf(stderr, "%s:%d: pushed %s (branch: %s)\n", __func__, __LINE__, local_path, branch_name); - error = 0; - } - } - } - } - } - } - - if (remote) { - git_remote_free(remote); - } - - QDir::setCurrent(currentWD); - return error; +int PullRepositoryInternal(char const *localRepoName, + char const *branchName, char const *user, + char const *password) { + return GitLibrary::PullRepository(localRepoName, branchName, user, password); } #include diff --git a/CalculatorCInterface/calculator_c_interface_lib.h b/CalculatorCInterface/calculator_c_interface_lib.h index 2c58df6..3126ad9 100644 --- a/CalculatorCInterface/calculator_c_interface_lib.h +++ b/CalculatorCInterface/calculator_c_interface_lib.h @@ -30,9 +30,10 @@ void DeleteTariffCalculator(TariffCalculatorHandle handle) CALCULATOR_C_INTERFAC int InitGitLibraryInternal(void) CALCULATOR_C_INTERFACE_LIB_EXPORT; int ShutdownGitLibraryInternal(void) CALCULATOR_C_INTERFACE_LIB_EXPORT; int CloneRepositoryInternal(char const *url, char const *local_path) 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, char const *commit_message) CALCULATOR_C_INTERFACE_LIB_EXPORT; -int PushLocalRepository(char const *local_path, char const *branch_name, char const *user, char const *password) CALCULATOR_C_INTERFACE_LIB_EXPORT; +int CheckoutRepositoryInternal(char const *url, char const *localRepoName, char const *branchName) CALCULATOR_C_INTERFACE_LIB_EXPORT; +int CommitFileInternal(char const *local_path, char const *branch_name, char const *file_name, char const *commit_message) CALCULATOR_C_INTERFACE_LIB_EXPORT; +int PushRepositoryInternal(char const *local_path, char const *branch_name, char const *user, char const *password) CALCULATOR_C_INTERFACE_LIB_EXPORT; +int PullRepositoryInternal(char const *localRepoName, char const *branchName, char const *user, char const *password) CALCULATOR_C_INTERFACE_LIB_EXPORT; #ifdef __cplusplus } diff --git a/CalculatorCInterface/git_library.cpp b/CalculatorCInterface/git_library.cpp index f5bba66..fd6f032 100644 --- a/CalculatorCInterface/git_library.cpp +++ b/CalculatorCInterface/git_library.cpp @@ -1,4 +1,5 @@ #include "git_library.h" +#include "global_defines.h" #include "local_git_repository.h" #include @@ -6,6 +7,352 @@ #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; + +int cred_acquire_cb(git_credential **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload) +{ + Q_UNUSED(url); + Q_UNUSED(username_from_url); + Q_UNUSED(payload); + int error = -1; + + if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) { + error = git_credential_userpass_plaintext_new(out, + GitLibrary::userName().toStdString().c_str(), + GitLibrary::userPassword().toStdString().c_str()); + } else { + fprintf(stderr, "GIT_CREDENTIAL_USERPASS_PLAINTEXT not supported by protocol (allowed_types=%08x)\n", allowed_types); + } + + return error; +} + +static bool check(int error, const char *message, const char *extra) { + const git_error *lg2err; + const char *lg2msg = "", *lg2spacer = ""; + + if (!error) { + return true; + } + + if ((lg2err = git_error_last()) != NULL && lg2err->message != NULL) { + lg2msg = lg2err->message; + lg2spacer = " - "; + } + + if (extra) + fprintf(stderr, "%s '%s' [%d]%s%s\n", + message, extra, error, lg2spacer, lg2msg); + else + fprintf(stderr, "%s [%d]%s%s\n", + message, error, lg2spacer, lg2msg); + + // exit(1); + return false; +} + +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; +} + +// +// git fetch +// + +static int progress_cb(const char *str, int len, void *data) { + (void)data; + printf("remote: %.*s", len, str); + fflush(stdout); /* We don't have the \n to force the flush */ + return 0; +} + +/** + * This function gets called for each remote-tracking branch that gets + * updated. The message we output depends on whether it's a new one or + * an update. + */ +static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) { + char a_str[GIT_OID_SHA1_HEXSIZE+1], b_str[GIT_OID_SHA1_HEXSIZE+1]; + (void)data; + + git_oid_fmt(b_str, b); + b_str[GIT_OID_SHA1_HEXSIZE] = '\0'; + + if (git_oid_is_zero(a)) { + printf("[new] %.20s %s\n", b_str, refname); + } else { + git_oid_fmt(a_str, a); + a_str[GIT_OID_SHA1_HEXSIZE] = '\0'; + printf("[updated] %.10s..%.10s %s\n", a_str, b_str, refname); + } + + return 0; +} + +/** + * This gets called during the download and indexing. Here we show + * processed and total objects in the pack and the amount of received + * data. Most frontends will probably want to show a percentage and + * the download rate. + */ +static int transfer_progress_cb(const git_indexer_progress *stats, void *payload) { + (void)payload; + + if (stats->received_objects == stats->total_objects) { + printf("Resolving deltas %u/%u\r", + stats->indexed_deltas, stats->total_deltas); + } else if (stats->total_objects > 0) { + printf("Received %u/%u objects (%u) in %" PRIuZ " bytes\r", + stats->received_objects, stats->total_objects, + stats->indexed_objects, stats->received_bytes); + } + return 0; +} + +static int fetch(git_repository *repo, char const *repoName) { + git_remote *remote = NULL; + const git_indexer_progress *stats; + git_fetch_options fetch_opts; + //git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + + git_fetch_options_init(&fetch_opts, GIT_FETCH_OPTIONS_VERSION); + + /* Figure out whether it's a named remote or a URL */ + printf("Fetching %s for repo %p\n", repoName, repo); + if (git_remote_lookup(&remote, repo, repoName) < 0) + if (git_remote_create_anonymous(&remote, repo, repoName) < 0) + goto on_error; + + /* Set up the callbacks (only update_tips for now) */ + fetch_opts.callbacks.update_tips = &update_cb; + fetch_opts.callbacks.sideband_progress = &progress_cb; + fetch_opts.callbacks.transfer_progress = transfer_progress_cb; + fetch_opts.callbacks.credentials = cred_acquire_cb; + + /** + * Perform the fetch with the configured refspecs from the + * config. Update the reflog for the updated references with + * "fetch". + */ + if (git_remote_fetch(remote, NULL, &fetch_opts, "fetch") < 0) + goto on_error; + + /** + * If there are local objects (we got a thin pack), then tell + * the user how many objects we saved from having to cross the + * network. + */ + stats = git_remote_stats(remote); + if (stats->local_objects > 0) { + printf("\rReceived %u/%u objects in %" PRIuZ " bytes (used %u local objects)\n", + stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects); + } else{ + printf("\rReceived %u/%u objects in %" PRIuZ "bytes\n", + stats->indexed_objects, stats->total_objects, stats->received_bytes); + } + + git_remote_free(remote); + + return 0; + +on_error: + git_remote_free(remote); + return -1; +} + +// +// git merge +// + +static int merge(git_repository *repo) { + return 0; +} + +#ifdef __cplusplus +} +#endif + + +QString GitLibrary::m_userName; +QString GitLibrary::m_userPassword; + int GitLibrary::Init() { return git_libgit2_init(); } @@ -14,80 +361,296 @@ int GitLibrary::Shutdown() { return git_libgit2_shutdown(); } -int GitLibrary::CheckoutRepository(char const *localRepoName, char const *branchName) { - QString localRepo(localRepoName); - QDir localRepoDir; +CWD::CWD(QString const &localRepoName) { + m_prev = QDir::current(); + m_valid = false; - if (strstr(localRepoName, "customer_") == localRepoName) { // localRepoPath starts with 'customer_' - QString const &p = QDir::cleanPath(LocalGitRepository::GetReposRootDirectory() - + QDir::separator() + localRepoName); - localRepoDir.setPath(p); - } else { - qCritical() << __func__ << ":" << __LINE__ - << "localRepoPath" << localRepoDir.path() << "either not absolute" - << "or not starting with customer_*"; - return -1; - } -} - -int GitLibrary::CloneRepository(char const *url, char const *localRepoPath /* absolute path or repo-name */) { - QString localRepoName; - QDir localRepoDir; - - if (strstr(localRepoPath, "customer_") == localRepoPath) { // localRepoPath starts with 'customer_' - QString const &p = QDir::cleanPath(LocalGitRepository::GetReposRootDirectory() - + QDir::separator() + localRepoPath); - localRepoDir.setPath(p); - localRepoName = localRepoPath; - } else if (QDir::isAbsolutePath(localRepoPath) && - strstr(localRepoPath, "/customer_") != localRepoPath) { - localRepoDir.setPath(localRepoPath); - localRepoName = localRepoDir.dirName(); // extract e.g. customer_999 - } else { - qCritical() << __func__ << ":" << __LINE__ - << "localRepoPath" << localRepoPath << "either not absolute" - << "or not starting with customer_*"; - return -1; + if (!localRepoName.startsWith("customer_")) { + qCritical() << HEADER + << "localRepoName not starting with customer_*"; + return; } - if (!QDir(localRepoDir).exists()) { - if (!QDir().mkpath(localRepoDir.absolutePath())) { - qCritical() << __func__ << ":" << __LINE__ - << "can not create git-repositories-root-dir" << localRepoDir; - return -1; + QString const &p = QDir::cleanPath(LocalGitRepository::GetReposRootDirectory() + + QDir::separator() + localRepoName); + + if (!QDir(p).exists()) { + if (!QDir().mkpath(p)) { + qCritical() << HEADER << "ERROR cannot mkdir" << p; } } - QDir gitDir(QDir::cleanPath(localRepoDir.absolutePath() + QDir::separator() + ".git")); + m_valid = QDir(p).exists(); - if (gitDir.exists()) { - qCritical() << __func__ << ":" << __LINE__ - << "local repository" << gitDir.path() << "already exists," - << "no clone necessary"; - return -1; + if (m_valid) { + QDir::setCurrent(p); + qCritical() << HEADER << "setCurrent working directory" << p; + m_localRepoDir.setPath(p); + m_localRepoGitDir.setPath(p + QDir::separator() + ".git"); + } else { + qCritical() << HEADER << "ERROR p" << p << "still not exists"; + } +} + +CWD::~CWD() { + QDir::setCurrent(m_prev.absolutePath()); + qCritical() << HEADER << "setCurrent working directory" << m_prev.absolutePath(); +} + +int GitLibrary::CheckoutRepository(char const *url, + char const *localRepoName, + char const *branchName) { + int err = 0; + checkout_options opts; + git_repository_state_t state = GIT_REPOSITORY_STATE_NONE; + git_annotated_commit *checkout_target = NULL; + git_reference *branch_ref = NULL; + + CWD cwd(localRepoName); + QString const &localRepoPath = cwd.localRepoDir().absolutePath(); + + if (!cwd.localRepoGitDir().exists()) { + + if (!url) { + qCritical() << HEADER << "url" << url << "not given"; + return -1; + } + + qCritical() << HEADER + << "localRepoGirDir" << cwd.localRepoGitDir().absolutePath() + << "does not exist. cloning..."; + + if ((err = CloneRepository(url, localRepoName)) < 0) { // a "clone" checkouts "master" + return err; + } } + qCritical() << HEADER + << "checkout repository:" << localRepoPath + << " branch:" << branchName << "..."; + + git_repository *repo = LocalGitRepository::GetInstance(localRepoName)->GetGitRepository(); + if (!repo) { // no clone + std::string const &s = localRepoPath.toStdString(); + char const *r = s.c_str(); + if ((err = git_repository_open(&repo, r)) != 0) { + qCritical() << HEADER << "repository" << localRepoPath + << "cannot be opened:" << QString(git_error_last()->message); + return err; + } + + qCritical() << HEADER << "repository" << localRepoPath << "opened"; + LocalGitRepository::GetInstance(localRepoName)->SetGitRepository(repo); + } + + // 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) { + qCritical() << __func__ << ":" << __LINE__ << "repository" << localRepoPath + << "is in unexpected state" << (int)state; + err = -1; + } else { + if ((err = resolve_refish(&checkout_target, repo, branchName)) < 0 && + (err = guess_refish(&checkout_target, repo, branchName)) < 0) { + qCritical() << __func__ << ":" << __LINE__ << "ERROR failed to resolve" + << QString(branchName) << QString(git_error_last()->message); + } else { + if ((err = perform_checkout_ref(repo, checkout_target, branchName, &opts)) == 0) { + if ((err = git_branch_lookup(&branch_ref, repo, branchName, GIT_BRANCH_LOCAL)) != 0) { + if (err == GIT_ENOTFOUND) { + qCritical() << __func__ << ":" << __LINE__ + << "ERROR local branch" << branchName << "not found"; + } else { + qCritical() << __func__ << ":" << __LINE__ + << "ERROR local branch" << branchName << "not found" + << QString(git_error_last()->message); + } + } + } + } + } + + if (branch_ref) { + git_reference_free(branch_ref); + } + + if (checkout_target) { + git_annotated_commit_free(checkout_target); + } + + if (err == 0) { + qCritical() << HEADER + << "checkout repository" << localRepoPath + << "and branch" << branchName << "done"; + } + + return err; +} + +int GitLibrary::CloneRepository(char const *url, char const *localRepoName) { + CWD cwd(localRepoName); int res = 0; - QDir const ¤t = QDir::current(); + + qCritical() << HEADER; + qCritical() << " url:" << url; + qCritical() << "repo:" << localRepoName; git_clone_options opts; if ((res = git_clone_options_init(&opts, GIT_CLONE_OPTIONS_VERSION)) == 0) { opts.checkout_branch = "master"; - if (QDir::setCurrent(localRepoDir.absolutePath())) { - git_repository *out = LocalGitRepository::GetInstance(localRepoName)->GetGitRepository(); - if ((res = git_clone(&out, url, ".", &opts)) < 0) { + git_repository *repo = LocalGitRepository::GetInstance(localRepoName)->GetGitRepository(); + if (!repo) { + if ((res = git_clone(&repo, url, ".", &opts)) < 0) { git_error const *error = git_error_last(); - qCritical() << __func__ << ":" << __LINE__ << error->message << localRepoDir.absolutePath(); + qCritical() << HEADER; + qCritical() << " error:" << error->message; + qCritical() << "localRepoDir:" << cwd.localRepoDir().absolutePath(); } - QDir::setCurrent(current.absolutePath()); - } else { - qCritical() << __func__ << ":" << __LINE__ - << "ERROR setCurrent" << localRepoDir.absolutePath(); - res = -1; + LocalGitRepository::GetInstance(localRepoName)->SetGitRepository(repo); } } else { git_error const *error = git_error_last(); - qCritical() << __func__ << ":" << __LINE__ << error->message; + qCritical() << HEADER << error->message; } return res; } + +int GitLibrary::CommitFile(char const *localRepoName, char const *branchName, + char const *fName, char const *commitMessage) { + CWD cwd(localRepoName); + int error = CheckoutRepository(nullptr, localRepoName, branchName); + if (error == 0) { + git_repository *repo = LocalGitRepository::GetInstance(localRepoName)->GetGitRepository(); + + git_oid commit_oid,tree_oid; + git_tree *tree = NULL; + git_index *index = NULL; + git_object *parent = NULL; + git_reference *ref = NULL; + git_signature *signature = NULL; + git_repository_state_t state = GIT_REPOSITORY_STATE_NONE; + + if (git_repository_head_unborn(repo) == 1) { + qCritical() << HEADER << "HEAD unborn. Create first commit"; + return -1; + } + + // 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) { + qCritical() << HEADER; + qCritical() << "ERROR repository" << cwd.localRepoDir().absolutePath() + << "is in unexpected state" << (int)state; + error = -1; + } else { + error = git_revparse_ext(&parent, &ref, repo, "HEAD"); + if (error == GIT_ENOTFOUND) { + qCritical() << HEADER << "HEAD not found. Creating first commit"; + error = 0; + } else if (error != 0) { + const git_error *err = git_error_last(); + if (err) { + qCritical() << HEADER << QString("ERROR %d: %s").arg(err->klass).arg(err->message); + } else { + qCritical() << HEADER << QString("ERROR no detailed error %d").arg(error); + } + } + + if (error == 0) { + if (check(git_repository_index(&index, repo), "Could not open repository index", NULL) && + check(git_index_write_tree(&tree_oid, index), "Could not write tree", NULL) && + check(git_index_write(index), "Could not write index", NULL) && + check(git_tree_lookup(&tree, repo, &tree_oid), "Error looking up tree", NULL) && + check(git_signature_default(&signature, repo), "Error creating signature", NULL) && + check(git_commit_create_v(&commit_oid, repo, "HEAD", signature, signature, + NULL, commitMessage, tree, parent ? 1 : 0, parent), + "Error creating commit", NULL)) { + qCritical() << HEADER; + qCritical() << "localRepoName" << QString::fromUtf8(localRepoName, strlen(localRepoName)); + qCritical() << " branchName" << QString::fromUtf8(branchName, strlen(branchName)); + qCritical() << " fName" << QString::fromUtf8(fName, strlen(fName)); + qCritical() << "commitMessage" << QString::fromUtf8(commitMessage, strlen(commitMessage)); + qCritical() << " git commit" << "OK"; + } + + if (index) { + git_index_free(index); + } + if (signature) { + git_signature_free(signature); + } + if (tree) { + git_tree_free(tree); + } + if (parent) { + git_object_free(parent); + } + if (ref) { + git_reference_free(ref); + } + } + } + } + + return error; +} + +int GitLibrary::PushRepository(char const *localRepoName, char const *branchName, + char const *userName, char const *userPassword) { + CWD cwd(localRepoName); + int error = CheckoutRepository(nullptr, localRepoName, branchName); + if (error == 0) { + + git_repository_state_t state = GIT_REPOSITORY_STATE_NONE; + git_push_options options; + git_remote_callbacks callbacks; + git_remote* remote = NULL; + char refspec_[] = "refs/heads/master"; + char *refspec = refspec_; + const git_strarray refspecs = { + &refspec, + 1 + }; + + // std::lock_guard lock(m); + + m_userName = QString::fromUtf8(userName, strlen(userName)); + m_userPassword = QString::fromUtf8(userPassword, strlen(userPassword)); + + git_repository *repo = LocalGitRepository::GetInstance(localRepoName)->GetGitRepository(); + + // 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) { + qCritical() << HEADER; + qCritical() << "ERROR repository" << cwd.localRepoDir().absolutePath() + << "is in unexpected state" << (int)state; + } else { + if (check(git_remote_lookup(&remote, repo, "origin" ), "Unable to lookup remote", NULL)) { + if (check(git_remote_init_callbacks(&callbacks, GIT_REMOTE_CALLBACKS_VERSION), + "Error initializing remote callbacks", NULL)) { + callbacks.credentials = cred_acquire_cb; + if (check(git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION ), + "Error initializing push", NULL)) { + options.callbacks = callbacks; + if (check(git_remote_push(remote, &refspecs, &options), "Error pushing", NULL)) { + qCritical() << HEADER; + qCritical() << QString("pushed %s (branch: %s").arg(localRepoName).arg(branchName); + error = 0; + } + } + } + } + } + + if (remote) { + git_remote_free(remote); + } + } + + return error; +} + +int GitLibrary::PullRepository(char const *localRepoName, char const *branchName, + char const *userName, char const *userPassword) { + // "git pull" is a basiclly a "git fetch" followed by a "git merge" + + return 0; +} diff --git a/CalculatorCInterface/git_library.h b/CalculatorCInterface/git_library.h index 003ef8a..fe0f30b 100644 --- a/CalculatorCInterface/git_library.h +++ b/CalculatorCInterface/git_library.h @@ -9,13 +9,36 @@ #include #include -class GitLibrary { +class CWD { + QDir m_prev; + QDir m_localRepoDir; + QDir m_localRepoGitDir; + bool m_valid; public: + CWD(QString const &localRepoName); + ~CWD(); + + QDir const &localRepoDir() const { return m_localRepoDir; } + QDir &localRepoDir() { return m_localRepoDir; } + QDir const &localRepoGitDir() const { return m_localRepoGitDir; } + QDir &localRepoGitDir() { return m_localRepoGitDir; } +}; + +class GitLibrary { + static QString m_userName; + static QString m_userPassword; +public: + static QString &userName() { return m_userName; } + static QString &userPassword() { return m_userPassword; } + static int Init(); static int Shutdown(); static int CloneRepository(char const *url, char const *localRepoName); - static int CheckoutRepository(char const *localRepoName, char const *branchName); - + static int CheckoutRepository(char const *url, char const *localRepoName, char const *branchName); + static int CommitRepository(char const *localRepoName, char const *branchName, char const *commitMessage); + static int CommitFile(char const *localRepoName, char const *branchName, char const *fName, char const *commitMessage); + static int PushRepository(char const *localRepoName, char const *branchName, char const *userName, char const *userPassword); + static int PullRepository(char const *localRepoName, char const *branchName, char const *userName, char const *userPassword); // explicit GitLibrary(); // ~GitLibrary();