Что происходит, так это то, что 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
), что может помочь вам в этом.
В любом случае конечный результат в значительной степени один и тот же: серия коммитов, которые были записаны в базу данных объектов без ведома пользователя, и обновление их рабочего каталога до совпадения в конце.