#include "git_library.h" #include "global_defines.h" #include "local_git_repository.h" #include #include #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 // struct merge_options { const char **heads; size_t heads_count; git_annotated_commit **annotated; size_t annotated_count; unsigned int no_commit : 1; }; static void merge_options_init(struct merge_options *opts) { memset(opts, 0, sizeof(*opts)); opts->heads = NULL; opts->heads_count = 0; opts->annotated = NULL; opts->annotated_count = 0; } static void parse_options(const char **repo_path, struct merge_options *opts) { #if 0 struct args_info args = ARGS_INFO_INIT; for (args.pos = 1; args.pos < argc; ++args.pos) { const char *curr = argv[args.pos]; if (curr[0] != '-') { opts_add_refish(opts, curr); } else if (!strcmp(curr, "--no-commit")) { opts->no_commit = 1; } else if (match_str_arg(repo_path, &args, "--git-dir")) { continue; } } #endif } static void opts_add_refish(struct merge_options *opts, const char *refish) { size_t sz; assert(opts != NULL); sz = ++opts->heads_count * sizeof(opts->heads[0]); if ((opts->heads = (const char **)realloc((void *) opts->heads, sz)) != 0) { opts->heads[opts->heads_count - 1] = refish; } } static int resolve_heads(git_repository *repo, struct merge_options *opts) { git_annotated_commit **annotated = (git_annotated_commit **)calloc(opts->heads_count, sizeof(git_annotated_commit *)); size_t annotated_count = 0, i; int err = 0; for (i = 0; i < opts->heads_count; i++) { err = resolve_refish(&annotated[annotated_count++], repo, opts->heads[i]); if (err != 0) { fprintf(stderr, "failed to resolve refish %s: %s\n", opts->heads[i], git_error_last()->message); annotated_count--; continue; } } if (annotated_count != opts->heads_count) { fprintf(stderr, "unable to parse some refish\n"); free(annotated); return -1; } opts->annotated = annotated; opts->annotated_count = annotated_count; return 0; } static int perform_fastforward(git_repository *repo, const git_oid *target_oid, int is_unborn) { // git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT; git_checkout_options ff_checkout_options; // #define GIT_CHECKOUT_OPTIONS_INIT {GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE} git_checkout_options_init(&ff_checkout_options, GIT_CHECKOUT_OPTIONS_VERSION); ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; git_reference *target_ref; git_reference *new_target_ref; git_object *target = NULL; int err = 0; if (is_unborn) { const char *symbolic_ref; git_reference *head_ref; /* HEAD reference is unborn, lookup manually so we don't try to resolve it */ err = git_reference_lookup(&head_ref, repo, "HEAD"); if (err != 0) { qCritical() << HEADER << "failed to lookup HEAD ref"; return -1; } /* Grab the reference HEAD should be pointing to */ symbolic_ref = git_reference_symbolic_target(head_ref); /* Create our master reference on the target OID */ err = git_reference_create(&target_ref, repo, symbolic_ref, target_oid, 0, NULL); if (err != 0) { qCritical() << HEADER << "failed to create master reference"; return -1; } git_reference_free(head_ref); } else { /* HEAD exists, just lookup and resolve */ err = git_repository_head(&target_ref, repo); if (err != 0) { qCritical() << HEADER << "failed to get HEAD reference"; return -1; } } /* Lookup the target object */ err = git_object_lookup(&target, repo, target_oid, GIT_OBJECT_COMMIT); if (err != 0) { qCritical() << HEADER << QString("failed to lookup OID %s").arg(git_oid_tostr_s(target_oid)); return -1; } /* Checkout the result so the workdir is in the expected state */ ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; err = git_checkout_tree(repo, target, &ff_checkout_options); if (err != 0) { qCritical() << HEADER << "failed to checkout HEAD reference"; return -1; } /* Move the target reference to the target OID */ err = git_reference_set_target(&new_target_ref, target_ref, target_oid, NULL); if (err != 0) { qCritical() << HEADER << "failed to move HEAD reference"; return -1; } git_reference_free(target_ref); git_reference_free(new_target_ref); git_object_free(target); return 0; } static void output_conflicts(git_index *index) { git_index_conflict_iterator *conflicts; const git_index_entry *ancestor; const git_index_entry *our; const git_index_entry *their; int err = 0; check(git_index_conflict_iterator_new(&conflicts, index), "failed to create conflict iterator", NULL); while ((err = git_index_conflict_next(&ancestor, &our, &their, conflicts)) == 0) { qCritical() << HEADER << QString("conflict: a:%s o:%s t:%s") .arg(ancestor ? ancestor->path : "NULL") .arg(our->path ? our->path : "NULL") .arg(their->path ? their->path : "NULL"); } if (err != GIT_ITEROVER) { qCritical() << HEADER << "error iterating conflicts"; } git_index_conflict_iterator_free(conflicts); } static int create_merge_commit(git_repository *repo, git_index *index, struct merge_options *opts) { git_oid tree_oid, commit_oid; git_tree *tree; git_signature *sign; git_reference *merge_ref = NULL; git_annotated_commit *merge_commit; git_reference *head_ref; git_commit **parents = (git_commit **)calloc(opts->annotated_count + 1, sizeof(git_commit *)); if (!parents) { qCritical() << HEADER << "calloc failed"; return -1; } const char *msg_target = NULL; size_t msglen = 0; char *msg; size_t i; int err; /* Grab our needed references */ check(git_repository_head(&head_ref, repo), "failed to get repo HEAD", NULL); if (resolve_refish(&merge_commit, repo, opts->heads[0])) { qCritical() << HEADER << QString("failed to resolve refish %s").arg(opts->heads[0]); free(parents); return -1; } /* Maybe that's a ref, so DWIM it */ err = git_reference_dwim(&merge_ref, repo, opts->heads[0]); check(err, "failed to DWIM reference", git_error_last()->message); /* Grab a signature */ check(git_signature_now(&sign, "Me", "me@example.com"), "failed to create signature", NULL); #define MERGE_COMMIT_MSG "Merge %s '%s'" /* Prepare a standard merge commit message */ if (merge_ref != NULL) { check(git_branch_name(&msg_target, merge_ref), "failed to get branch name of merged ref", NULL); } else { msg_target = git_oid_tostr_s(git_annotated_commit_id(merge_commit)); } msglen = snprintf(NULL, 0, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target); if (msglen > 0) { msglen++; } if ((msg = (char *)malloc(msglen)) == nullptr) { qCritical() << HEADER << 'malloc failed'; goto cleanup; } err = snprintf(msg, msglen, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target); /* This is only to silence the compiler */ if (err < 0) { goto cleanup; } /* Setup our parent commits */ err = git_reference_peel((git_object **)&parents[0], head_ref, GIT_OBJECT_COMMIT); check(err, "failed to peel head reference", NULL); for (i = 0; i < opts->annotated_count; i++) { git_commit_lookup(&parents[i + 1], repo, git_annotated_commit_id(opts->annotated[i])); } /* Prepare our commit tree */ check(git_index_write_tree(&tree_oid, index), "failed to write merged tree", NULL); check(git_tree_lookup(&tree, repo, &tree_oid), "failed to lookup tree", NULL); /* Commit time ! */ err = git_commit_create(&commit_oid, repo, git_reference_name(head_ref), sign, sign, NULL, msg, tree, opts->annotated_count + 1, (const git_commit **)parents); check(err, "failed to create commit", NULL); /* We're done merging, cleanup the repository state */ git_repository_state_cleanup(repo); cleanup: free(parents); return err; } static int merge(git_repository *repo) { struct merge_options opts; git_index *index; git_repository_state_t state; git_merge_analysis_t analysis; git_merge_preference_t preference; const char *path = "."; int err = 0; merge_options_init(&opts); parse_options(&path, &opts); state = (git_repository_state_t)git_repository_state(repo); if (state != GIT_REPOSITORY_STATE_NONE) { fprintf(stderr, "repository is in unexpected state %d\n", state); goto cleanup; } err = resolve_heads(repo, &opts); if (err != 0) goto cleanup; err = git_merge_analysis(&analysis, &preference, repo, (const git_annotated_commit **)opts.annotated, opts.annotated_count); check(err, "merge analysis failed", NULL); if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) { printf("Already up-to-date\n"); return 0; } else if (analysis & GIT_MERGE_ANALYSIS_UNBORN || (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD && !(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD))) { const git_oid *target_oid; if (analysis & GIT_MERGE_ANALYSIS_UNBORN) { printf("Unborn\n"); } else { printf("Fast-forward\n"); } /* Since this is a fast-forward, there can be only one merge head */ target_oid = git_annotated_commit_id(*opts.annotated); assert(opts.annotated_count == 1); return perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); } else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) { //git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; git_merge_options merge_opts; git_merge_options_init(&merge_opts, GIT_MERGE_OPTIONS_VERSION); // TODO: check this... // merge_opts.file_flags = GIT_MERGE_FIND_RENAMES; // GIT_MERGE_OPTIONS_VERSION, GIT_MERGE_FIND_RENAMES } //git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; git_checkout_options checkout_opts; git_checkout_options_init(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; merge_opts.flags = 0; merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3; checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS; if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) { printf("Fast-forward is preferred, but only a merge is possible\n"); return -1; } err = git_merge(repo, (const git_annotated_commit **)opts.annotated, opts.annotated_count, &merge_opts, &checkout_opts); check(err, "merge failed", NULL); } /* If we get here, we actually performed the merge above */ check(git_repository_index(&index, repo), "failed to get repository index", NULL); if (git_index_has_conflicts(index)) { /* Handle conflicts */ output_conflicts(index); } else if (!opts.no_commit) { create_merge_commit(repo, index, &opts); printf("Merge made\n"); } cleanup: free((char **)opts.heads); free(opts.annotated); return 0; } #ifdef __cplusplus } #endif QString GitLibrary::m_userName; QString GitLibrary::m_userPassword; int GitLibrary::Init() { return git_libgit2_init(); } int GitLibrary::Shutdown() { return git_libgit2_shutdown(); } CWD::CWD(QString const &localRepoName) { m_prev = QDir::current(); m_valid = false; if (!localRepoName.startsWith("customer_")) { qCritical() << HEADER << "localRepoName not starting with customer_*"; return; } QString const &p = QDir::cleanPath(LocalGitRepository::GetReposRootDirectory() + QDir::separator() + localRepoName); if (!QDir(p).exists()) { if (!QDir().mkpath(p)) { qCritical() << HEADER << "ERROR cannot mkdir" << p; } } m_valid = QDir(p).exists(); 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; 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"; 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() << HEADER; qCritical() << " error:" << error->message; qCritical() << "localRepoDir:" << cwd.localRepoDir().absolutePath(); } LocalGitRepository::GetInstance(localRepoName)->SetGitRepository(repo); } } else { git_error const *error = git_error_last(); 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 *remoteRepoName) { // "git pull" is a basically a "git fetch" followed by a "git merge" git_repository *repo = nullptr; int error = 0; if ((error = git_repository_open(&repo, localRepoName)) != 0) { // error } // remoteRepoName typically "origin" if ((error = fetch(repo, remoteRepoName)) != 0) { // error } if ((error = merge(repo)) != 0) { // error } if (repo) { git_repository_free(repo); } return error; }