我正在寻找一种挑选两个或更多提交的方法。
我的目标是能够选择多个提交,以允许用户在提交更改之前先查看这些更改,而不要求用户在每次选择之后都进行提交。
我在下面添加了一个代码段,该代码段将接受一个存储库路径,然后是两次提交,并尝试连续挑选它们。但是我不确定我需要设置哪些选项以允许两次提交被选中。
就像第一个樱桃采摘作品一样,但是第二个却失败了 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;
}
答案 0 :(得分:1)
正在发生的事情是libgit2拒绝覆盖已修改的磁盘上的文件,但是它的内容实际上并未由git存储在任何地方。该文件“非常宝贵”,git和libgit2将竭尽全力避免覆盖它。
没有办法克服这个问题,因为Cherry-picking并没有根据工作目录的内容在提交中应用差异。。它将对HEAD
的提交中的差异应用。也就是说,您唯一的选择是忽略此Cherry-Pick中的更改,或覆盖先前的Cherry-Pick中引入的更改。
让我给你一个具体的例子:
假设在提交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
中为libgit2中的提交2(或命令行中的git cherry-pick --no-commit
)是第一次提交,它将从{{1}中读取文件},并应用提交2的更改。这是一个简单的示例,因此内容从字面上看与提交2的内容匹配。该文件将放置在磁盘上。
现在,如果您什么都不做-不提交,则仍处于提交1。如果再次执行HEAD
(这次是提交2'),则libgit2将读取git_cherrypick
中的文件,并应用更改以提交2'。再次,在这个简单的示例中,将2'中的更改应用于文件1中的内容将为您提交2'中文件的内容。
因为不会要做的是从工作目录中读取文件。
因此,当现在尝试将这些结果写入工作目录时,存在结帐冲突。因为磁盘上文件的内容与HEAD
或我们尝试检出的文件中的文件的值不匹配。所以你被封锁了。
您可能想要做的是在此阶段创建一个提交。我知道您说过,您不想避免“要求用户在每次选择之后都提交”。但是,在libgit2中创建一个提交对象(它很轻巧并且可以很容易地丢弃(最终将被垃圾回收))和执行HEAD
这在道德上等同于运行更新分支指针的行为之间是有区别的。
如果您仅创建一个提交并将其写入对象数据库(而无需切换到它或将其检出),那么您可以将该数据重用于工作中的其他步骤,而无需让用户看上去已经完成了提交。它完全位于内存中(对象数据库中很少),而无需访问工作目录。
我鼓励您做的是将您想要的每个提交樱桃挑选到索引中,该索引在内存中起作用,并且不接触磁盘。当您对结果满意时,可以创建一个提交对象。您需要使用git commit
API而不是git_cherrypick_commit
来生成索引,然后将其转换为的树。例如:
git_cherrypick
最终,您将获得终端提交,您可以将其检出-我的意思是在libgit2 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);
检出概念中,这只是将这些内容放入您的工作目录中。不过,不要更新任何分支指针。这样将得到以下结果:仅在工作目录(和索引)中修改文件,但用户未提交任何内容,其git_checkout
尚未移动。
HEAD
(您可以将git_checkout_tree(repo, final_result_commit, NULL);
传递给git_commit *
。它知道该怎么做。)
我可以通过为您提供git_checkout_tree
API来使此很多变得容易。这将使您无需中间人即可创建不需要的提交。但我认为没有人愿意这样做。 (对不起!)
之所以我不认为有人愿意这样做,是因为您所描述的内容更准确地称为 rebase 。 Rebase是一组顺序的补丁程序应用程序或挑选步骤。 ( Interactive rebase涉及更多,因此暂时忽略它。)
libgit2具有git_cherrypick_tree
的机制,可以完全在内存中工作,从而节省了一些将索引转换为树并将提交写入磁盘的工作。可以调用它来完全在内存中工作(请参阅git_rebase
),这可能会对您有所帮助。
在任何一种情况下,最终结果基本上都是相同的,一系列的提交在用户不知道的情况下被写入对象数据库,并更新其工作目录以最终匹配。