#include "calculator_c_interface_lib.h" #include "local_git_repository.h" #include "git_library.h" //#include #include #include #include #include #include #include #include #include #include #include #include #include static QMap customerRepoMap; static std::mutex m; static char const *user = NULL; static char const *pass = NULL; #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; 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(); } void DeleteTariffCalculator(TariffCalculatorHandle handle) { delete handle; } int InitGitLibraryInternal(void) { return GitLibrary::Init(); } int ShutdownGitLibraryInternal(void) { LocalGitRepository::DestroyAllRepositories(); return GitLibrary::Shutdown(); } 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 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 PushLocalRepository(char const *local_path, char const *branch_name, char const *username, char const *password) { git_repository *repo = NULL; int error = -1; 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; } #include void SetReposRootDirectoryInternal(char const *p) { LocalGitRepository::SetReposRootDirectory(QString::fromUtf8(p)); } char const *GetReposRootDirectoryInternal() { return (char const *)LocalGitRepository::GetReposRootDirectory().constData(); } char const *GetLocalRepositoryPathInternal(char const *localGitRepo) { return (char const *)LocalGitRepository::GetInstance(localGitRepo)->localRepositoryPath().constData(); } int32_t GetFileMenuSizeInternal(char const *localGitRepo) { return LocalGitRepository::GetInstance(localGitRepo)->GetFileMenuSizeInternal(); } char const *GetFileMenuInternal(const char *localGitRepo) { QByteArray const &a = LocalGitRepository::GetInstance(localGitRepo)->GetFileMenuInternal().constData(); if (a.isValidUtf8()) { int const len = GetFileMenuSizeInternal(localGitRepo); if (len > 0) { char *json = new char [len+1]; // fprintf(stderr, "allocate pointer %p\n", json); memset(json, 0x00, len+1); memcpy(json, a.constData(), std::min(len, (int)a.size())); return json; } } return nullptr; } char const *GetFileNameInternal(char const *localGitRepo, char const *fileId) { QByteArray const &a = LocalGitRepository::GetInstance(localGitRepo)->GetFileNameInternal(fileId); if (a.isValidUtf8()) { char *c = new char[a.size() + 1]; memset(c, 0x00, a.size() + 1); memcpy(c, a.constData(), a.size()); return c; } return nullptr; } int32_t GetFileSize(char const *localGitRepo, char const *fileId) { return LocalGitRepository::GetInstance(localGitRepo)->GetFileSize(fileId); } char const *GetFileInternal(char const *localGitRepo, char const *fileId) { QByteArray const &a = LocalGitRepository::GetInstance(localGitRepo)->GetFileInternal(fileId); if (a.isValidUtf8()) { char *c = new char[a.size() + 1]; memset(c, 0x00, a.size() + 1); memcpy(c, a.constData(), a.size()); return c; } return nullptr; } bool SetFileInternal(char const *localGitRepo, char const *fileId, char const *json, int size) { return LocalGitRepository::GetInstance(localGitRepo)->SetFileInternal(QString(fileId), QByteArray(json, size)); } void DeleteMem(char *p) { if (p) { // fprintf(stderr, "delete pointer %p\n", p); delete p; } } #ifdef __cplusplus } #endif