just safe

This commit is contained in:
Gerhard Hoffmann 2024-04-03 16:29:58 +02:00
parent 7ba9cb8147
commit 4ab36ec64f
5 changed files with 665 additions and 566 deletions

View File

@ -37,7 +37,8 @@ HEADERS += \
calculator_c_interface_lib_global.h \ calculator_c_interface_lib_global.h \
git_library.h \ git_library.h \
tariff_calculator.h \ tariff_calculator.h \
local_git_repository.h local_git_repository.h \
global_defines.h
# Default rules for deployment. # Default rules for deployment.
unix { unix {

View File

@ -19,10 +19,8 @@
#include <stdint.h> #include <stdint.h>
#include <algorithm> #include <algorithm>
static QMap<QString, git_repository *> customerRepoMap; //static QMap<QString, git_repository *> customerRepoMap;
static std::mutex m; //static std::mutex m;
static char const *user = NULL;
static char const *pass = NULL;
#ifndef _WIN32 #ifndef _WIN32
# include <unistd.h> # include <unistd.h>
@ -33,241 +31,6 @@ static char const *pass = NULL;
extern "C" { extern "C" {
#endif #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) { TariffCalculatorHandle NewTariffCalculator(void) {
return new TariffCalculator(); return new TariffCalculator();
@ -290,276 +53,24 @@ int CloneRepositoryInternal(char const *url, char const *local_path) {
return GitLibrary::CloneRepository(url, local_path); return GitLibrary::CloneRepository(url, local_path);
} }
int CheckoutLocalBranch(char const *local_path, char const *branch_name) { int CheckoutRepositoryInternal(char const *url, char const *localRepoName, char const *branchName) {
int err = 0; return GitLibrary::CheckoutRepository(url, localRepoName, branchName);
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, int CommitFileInternal(char const *localRepoName, char const *branchName,
char const *file_name, char const *comment) { char const *fName, char const *commitMessage) {
assert(local_path != NULL); return GitLibrary::CommitFile(localRepoName, branchName, fName, commitMessage);
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) { int PushRepositoryInternal(char const *localRepoName, char const *branchName,
git_repository *repo = NULL; char const *userName, char const *userPassword) {
int error = -1; return GitLibrary::PushRepository(localRepoName, branchName, userName, userPassword);
}
fprintf(stderr, "%s:%d: %s %s\n", __func__, __LINE__, local_path, branch_name); int PullRepositoryInternal(char const *localRepoName,
char const *branchName, char const *user,
QString const currentWD = QDir::currentPath(); char const *password) {
return GitLibrary::PullRepository(localRepoName, branchName, user, password);
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<std::mutex> 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 <local_git_repository.h> #include <local_git_repository.h>

View File

@ -30,9 +30,10 @@ void DeleteTariffCalculator(TariffCalculatorHandle handle) CALCULATOR_C_INTERFAC
int InitGitLibraryInternal(void) CALCULATOR_C_INTERFACE_LIB_EXPORT; int InitGitLibraryInternal(void) CALCULATOR_C_INTERFACE_LIB_EXPORT;
int ShutdownGitLibraryInternal(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 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 CheckoutRepositoryInternal(char const *url, char const *localRepoName, char const *branchName) 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 CommitFileInternal(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 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 #ifdef __cplusplus
} }

View File

@ -1,4 +1,5 @@
#include "git_library.h" #include "git_library.h"
#include "global_defines.h"
#include "local_git_repository.h" #include "local_git_repository.h"
#include <QDir> #include <QDir>
@ -6,6 +7,352 @@
#include <git2.h> #include <git2.h>
#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() { int GitLibrary::Init() {
return git_libgit2_init(); return git_libgit2_init();
} }
@ -14,80 +361,296 @@ int GitLibrary::Shutdown() {
return git_libgit2_shutdown(); return git_libgit2_shutdown();
} }
int GitLibrary::CheckoutRepository(char const *localRepoName, char const *branchName) { CWD::CWD(QString const &localRepoName) {
QString localRepo(localRepoName); m_prev = QDir::current();
QDir localRepoDir; m_valid = false;
if (strstr(localRepoName, "customer_") == localRepoName) { // localRepoPath starts with 'customer_' if (!localRepoName.startsWith("customer_")) {
QString const &p = QDir::cleanPath(LocalGitRepository::GetReposRootDirectory() qCritical() << HEADER
+ QDir::separator() + localRepoName); << "localRepoName not starting with customer_*";
localRepoDir.setPath(p); return;
} 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 (!QDir(localRepoDir).exists()) { QString const &p = QDir::cleanPath(LocalGitRepository::GetReposRootDirectory()
if (!QDir().mkpath(localRepoDir.absolutePath())) { + QDir::separator() + localRepoName);
qCritical() << __func__ << ":" << __LINE__
<< "can not create git-repositories-root-dir" << localRepoDir; if (!QDir(p).exists()) {
return -1; 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()) { if (m_valid) {
qCritical() << __func__ << ":" << __LINE__ QDir::setCurrent(p);
<< "local repository" << gitDir.path() << "already exists," qCritical() << HEADER << "setCurrent working directory" << p;
<< "no clone necessary"; m_localRepoDir.setPath(p);
return -1; 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; int res = 0;
QDir const &current = QDir::current();
qCritical() << HEADER;
qCritical() << " url:" << url;
qCritical() << "repo:" << localRepoName;
git_clone_options opts; git_clone_options opts;
if ((res = git_clone_options_init(&opts, GIT_CLONE_OPTIONS_VERSION)) == 0) { if ((res = git_clone_options_init(&opts, GIT_CLONE_OPTIONS_VERSION)) == 0) {
opts.checkout_branch = "master"; opts.checkout_branch = "master";
if (QDir::setCurrent(localRepoDir.absolutePath())) { git_repository *repo = LocalGitRepository::GetInstance(localRepoName)->GetGitRepository();
git_repository *out = LocalGitRepository::GetInstance(localRepoName)->GetGitRepository(); if (!repo) {
if ((res = git_clone(&out, url, ".", &opts)) < 0) { if ((res = git_clone(&repo, url, ".", &opts)) < 0) {
git_error const *error = git_error_last(); 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()); LocalGitRepository::GetInstance(localRepoName)->SetGitRepository(repo);
} else {
qCritical() << __func__ << ":" << __LINE__
<< "ERROR setCurrent" << localRepoDir.absolutePath();
res = -1;
} }
} else { } else {
git_error const *error = git_error_last(); git_error const *error = git_error_last();
qCritical() << __func__ << ":" << __LINE__ << error->message; qCritical() << HEADER << error->message;
} }
return res; 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<std::mutex> 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;
}

View File

@ -9,13 +9,36 @@
#include <QDir> #include <QDir>
#include <git2.h> #include <git2.h>
class GitLibrary { class CWD {
QDir m_prev;
QDir m_localRepoDir;
QDir m_localRepoGitDir;
bool m_valid;
public: 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 Init();
static int Shutdown(); static int Shutdown();
static int CloneRepository(char const *url, char const *localRepoName); 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(); // explicit GitLibrary();
// ~GitLibrary(); // ~GitLibrary();