libgit2 - вишня выбирает несколько коммитов - PullRequest
0 голосов
/ 31 августа 2018

Я ищу метод для выбора вишни двух или более коммитов.

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

Я добавил ниже фрагмент кода, который примет путь к репозиторию, за которым следуют два коммита и попытка выбрать их последовательно. Однако я не уверен, какие опции мне нужно установить, чтобы можно было выбрать два коммита.

Как работает первый вишневый кирка, но 2-й не работает с 1 незафиксированное изменение будет перезаписано слиянием

Я пытался использовать опцию GIT_CHECKOUT_ALLOW_CONFLICTS , но безуспешно. Какие параметры необходимы для того, чтобы вишня выбирала несколько коммитов?

#include <stdio.h>
#include "git2.h"

#define onError(error, errorMsg)\
if (error){\
    const git_error* lg2err = giterr_last();\
    if (lg2err){\
        printf("%s %s\n", errorMsg, lg2err->message);\
        return 1;\
    }\
}

int main(int argc, char* argv[])
{

    if(argc != 4) { printf("Provide repo commit1 commit2\n"); return 1;}

    printf("Repository: %s\n Commit1: %s\n Commit2: %s\n", argv[1], argv[2], argv[3]);

    int error;
    git_libgit2_init();
    git_repository * repo;
    git_oid cid1, cid2;
    git_commit *c1 =NULL;
    git_commit *c2 =NULL;

    error = git_repository_open(&repo, argv[1]);
    onError(error,"Repo open failed: ");

    git_cherrypick_options cherry_opts = GIT_CHERRYPICK_OPTIONS_INIT;

    git_oid_fromstr(&cid1, argv[2]);
    git_oid_fromstr(&cid2, argv[3]);
    error = git_commit_lookup(&c1, repo, &cid1);
    onError(error,"commit lookup failed: ");
    error = git_commit_lookup(&c2, repo, &cid2);
    onError(error,"commit2 lookup failed: ");

    error = git_cherrypick(repo, c1, &cherry_opts);
    onError(error,"cherry1 failed: ");
    error = git_cherrypick(repo, c2, &cherry_opts);
    onError(error,"cherry2 failed: ");

    return 0;
}

1 Ответ

0 голосов
/ 12 сентября 2018

Что происходит, так это то, что libgit2 отказывается перезаписывать файл на диске, который был изменен, но его содержимое на самом деле нигде не было сохранено git. Этот файл «драгоценный», и git и libgit2 приложат большие усилия, чтобы не перезаписать его.

Нет никакого способа преодолеть это, потому что вишневый сбор не применяет различия в коммите на основе содержимого вашего рабочего каталога . Это применяет различия в коммите к HEAD. То есть, единственные варианты - игнорировать изменения в этом вишневом пике или переписать изменения, внесенные в предыдущем вишневом пике.

Позвольте привести конкретный пример:

Предположим, что у вас есть файл при коммите 1:

one
two
three
four
five

И у вас есть некоторый коммит, основанный на 1 (назовем его 2), который меняет файл на:

one
2
three
four
five

И у вас есть еще один коммит в другой ветке. Он также основан на 1 (назовем это 2 '). Это изменяет файл, чтобы быть:

one
two
three
4
five

Что произойдет, если вы выполняете коммит 1 и выберите «2» и «2» без коммитов? Логично, что вы могли бы ожидать, что это сделает слияние! Но это не так.

Если вы используете коммит 1 и вы git_cherrypick для коммита 2 в libgit2 (или git cherry-pick --no-commit в командной строке) для первого коммита, он прочитает файл из HEAD и применит изменения для коммита 2. Это тривиальный пример, поэтому его содержимое буквально соответствует содержимому коммита 2. Этот файл будет помещен на диск.

Теперь, если вы больше ничего не делаете - вы не делаете это - тогда вы все еще на коммите 1. И если вы снова делаете git_cherrypick (на этот раз для фиксации 2 '), тогда libgit2 прочитает файл из HEAD и применить изменения для коммита 2 '. И снова, в этом тривиальном примере, применение изменений в 2 'к файлу в 1 дает вам содержимое файла в коммите 2'.

Потому что не будет делать то, что читает файл из рабочего каталога.

Так что теперь, когда он пытается записать эти результаты в рабочий каталог, возникает конфликт извлечения. Потому что содержимое файла на диске не соответствует значению файла в HEAD или в том, что мы пытаемся оформить. Итак, вы заблокированы.

Что вы, вероятно, хотите сделать, это создать коммит на этом этапе. Я знаю, что вы сказали, что не хотите "требовать от пользователей коммитов после каждого выбора вишни". Но есть разница между созданием объекта коммита в libgit2, который является легковесным и может быть легко удален (где он в конечном итоге будет собираться мусором) и выполняет моральный эквивалент запуска git commit, который обновляет указатель ветви.

Если вы просто создаете коммит и записываете его в базу данных объектов - не переключаясь на него и не проверяя его - тогда вы можете повторно использовать эти данные для других этапов вашей работы, даже не создавая у пользователя вид, что он сделал коммит , Он полностью находится в памяти (и немного в объектной базе данных), не попав в рабочий каталог.

Я бы посоветовал вам сделать выборку каждого коммита, который вы хотите, в индекс, который работает в памяти и не касается диска. Когда вы довольны результатами, вы можете создать объект коммита. Вам нужно будет использовать git_cherrypick_commit API вместо git_cherrypick для создания индекса, а затем превратить его в дерево для. Например:

git_reference *head;
git_signature *signature;
git_commit *base1, *base2, *result1;
git_index *idx1, *idx2;
git_oid tree1;

/* Look up the HEAD reference */
git_repository_head(&head, repo);
git_reference_peel((git_object **)&base1, head, GIT_OBJ_COMMIT);

/* Pick the first cherry, getting back an index */
git_cherrypick_commit(&idx1, repo, c1, base1, 0, &cherry_opts);

/* Write that index into a tree */
git_index_write_tree(&tree_id1, idx1);

/* And create a commit object for that tree */
git_signature_now(&signature, "My Cherry-Picking System", "foo@example.com"); 
git_commit_create_from_ids(&result_id1,
    repo,
    NULL, /* don't update a reference */
    signature,
    signature,
    NULL,
    "Transient commit that will be GC'd eventually.",
    &tree_id1,
    1,
    &cid1);
git_commit_lookup(&result1, repo, &result_id1);

/* Now, you can pick the _second_ cherry with the commit you just created as a base... */
git_cherrypick_commit(&idx2, repo, c1, result1, 0, &cherry_opts);

В конце концов вы получите свой терминальный коммит, и вы можете просто проверить его - и я имею в виду в libgit2 git_checkout понятие проверки, которое просто помещает это содержимое в ваш рабочий каталог. Тем не менее, не обновляйте указатели веток. Это даст результат, когда файлы изменяются только в рабочем каталоге (и индексе), но пользователь ничего не зафиксировал, их HEAD не было перемещено.

git_checkout_tree(repo, final_result_commit, NULL);

(Вы можете передать git_commit * на git_checkout_tree. Он знает, что делать.)

Я мог бы сделать это намного проще для вас, предоставив вам git_cherrypick_tree API. Это позволит вам исключить посредника в создании коммита, который вам не нужен. Но я не думал, что кто-то захочет сделать это. (К сожалению!)

Причина, по которой я не думал, что кто-то захочет это сделать, заключается в том, что то, что вы описываете, более точно называется rebase . Rebase - это последовательный набор шагов патча или вишневых пиков. ( Interactive rebase немного сложнее, поэтому давайте пока проигнорируем это.)

libgit2 имеет механизм git_rebase, который может работать полностью в оперативной памяти, что избавляет вас от некоторой бухгалтерии, связанной с преобразованием индексов в деревья и записью коммитов на диск. Его можно использовать для работы полностью в памяти (см. rebase_commit_inmemory), что может помочь вам в этом.

В любом случае конечный результат в значительной степени один и тот же: серия коммитов, которые были записаны в базу данных объектов без ведома пользователя, и обновление их рабочего каталога до совпадения в конце.

...