клонировать git-репозиторий, используя git init, fetch и checkout - PullRequest
1 голос
/ 27 мая 2019

Введение

Я делаю эксперименты с использованием популярного libgit2, написанного на C.
Я пытаюсь сделать clone, но использую необычный способ. По порядку, команды git:

  1. git init
  2. git remote add origin https://repository.git
  3. git fetch origin
  4. git checkout master

Используя git bash и следующие команды, я могу получить существующее хранилище со всей его историей.

Вопрос

Теперь давайте посмотрим мою текущую реализацию C ++. Следующий код пытается скопировать поведение предыдущих написанных команд git.

#define url         "https://repository.git"
#define path        "./"
#define user        "user"
#define pass        "pass"

/** credential callback **/
int credentials(git_cred **cred, const char *, const char *, unsigned int, void *) {
    return git_cred_userpass_plaintext_new(cred, user, pass);
}

class Git {
public:
    Git() {
        git_libgit2_init();
    }

    ~Git() {
        git_repository_free(repository);
        git_libgit2_shutdown();
    }

    void update() {
        init();
        fetch();
        checkout();
    }

private:
    void init() {
        assertSuccess(git_repository_init(&repository, path, GIT_CVAR_FALSE));

        git_remote *remote = nullptr;
        git_remote_callbacks options = GIT_REMOTE_CALLBACKS_INIT;

        assertSuccess(git_remote_create(&remote, repository, "origin", url));

        options.credentials = credentials;
        git_remote_connect(remote, GIT_DIRECTION_FETCH, &options, nullptr, nullptr);
    }

    void fetch() {
        git_remote* remote = nullptr;
        assertSuccess(git_remote_lookup(&remote, repository, "origin"));

        git_fetch_options options = GIT_FETCH_OPTIONS_INIT;
        options.callbacks.credentials = credentials;
        assertSuccess(git_remote_fetch(remote, nullptr, &options, nullptr));
    }

    void checkout() {
        git_checkout_options options = GIT_CHECKOUT_OPTIONS_INIT;
        options.checkout_strategy = GIT_CHECKOUT_FORCE;

        assertSuccess(git_checkout_head(repository, &options));

        assertSuccess(git_checkout_index(repository, nullptr, &options));

        assertSuccess(git_repository_set_head(repository, "refs/heads/master"));
        git_object *treeish = nullptr;
        assertSuccess(git_revparse_single(&treeish, repository, "master"));
        assertSuccess(git_checkout_tree(repository, treeish, &options));
    }

    void assertSuccess(int error) {
        if (!error) return;

        const git_error *e = giterr_last();
        std::cout << "code: " << e->klass << " error: " << e->message << std::endl;
        exit(1);
    }

private:
    git_repository *repository = nullptr;
};

int main() {
    Git git;
    git.update();
    return 0;
}

Очевидно, это не работает. Запустив эту программу (вызывая Git().update()), я получаю следующую ошибку на этапе проверки:

code: 4 error: reference 'refs/heads/master' not found

Репозиторий git создан, и я вижу удаленный источник, который был успешно установлен, хотя git bash. Я могу сделать руководство git checkout master из git bash, поэтому я полагаю, что моя текущая реализация checkout не удалась.

Может ли кто-нибудь рассказать мне об этой ошибке? Я не смог найти достаточно ресурсов или поддержки для всех найденных примеров в интернете.

* 1047 РЕДАКТИРОВАТЬ *
Поскольку тестирование моего кода может помочь, позвольте мне дать CMakeLists.txt для компиляции libgit2. (исходный код https://github.com/libgit2/libgit2)

cmake_minimum_required(VERSION 3.13)
project(test)

include_directories(libgit/include)
LINK_DIRECTORIES(${LIBSSH2_LIBRARY_DIRS})
add_subdirectory(libgit)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_BUILD_TYPE Release)

add_executable(test src/Git.h)
target_link_libraries(test git2)

1 Ответ

0 голосов
/ 27 мая 2019

Недостающее звено в том, что, поскольку вы создаете хранилище с нуля, ваше хранилище все еще не рождено (то есть его HEAD указывает на несуществующий refs/heads/master ref). Кроме того, checkout заботится только о libgit2-land - извлекать файлы из ODB, он не пишет и не обновляет ссылки.

Следовательно, вы пропускаете шаг, где git checkout использует (по всей вероятности) git update-ref, чтобы master указал на OID origin/master, и вы можете сделать это через git_reference_create и друзей .

Что-то вроде следующего (скомпилировано с мозгом):

static int setup_tracking_branch(char *branch_name, git_reference *upstream)
{
    git_reference *tracking;
    git_oid up_oid = git_reference_target_peel(upstream);
    char *ui_name;

#if 0
    /* should be constructed from `upstream`. IIRC there are some
     * git_reference accessors that can help
     * (eg. `refs/remotes/origin/heads/master` is `origin/master`).
     */
#else
    ui_name = "origin/master";
#endif

    if (git_reference_create_matching(&tracking,
                                      git_reference_owner(upstream),
                                      branch_name, up_oid, 0, NULL, "branch: created from %s", ui_name) < 0 ||
        git_branch_set_upstream(tracking, git_reference_name(upstream)) < 0) {
        printf("failed to create remote-tracking branch\n");
        return -1;
    }

cleanup:
    git_reference_free(tracking);

    return 0;
}

Это имя, которое вы хотите, чтобы новая ветвь было (-b), и удаленная ветвь для отслеживания (-t), хотя это явно не полная переопределение или даже корректность, поэтому YMMV.

...