libgit2-樱桃选择多个提交

时间:2018-08-31 20:52:55

标签: libgit2

我正在寻找一种挑选两个或更多提交的方法。

我的目标是能够选择多个提交,以允许用户在提交更改之前先查看这些更改,而不要求用户在每次选择之后都进行提交。

我在下面添加了一个代码段,该代码段将接受一个存储库路径,然后是两次提交,并尝试连续挑选它们。但是我不确定我需要设置哪些选项以允许两次提交被选中。

就像第一个樱桃采摘作品一样,但是第二个却失败了 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 :(得分: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),这可能会对您有所帮助。

在任何一种情况下,最终结果基本上都是相同的,一系列的提交在用户不知道的情况下被写入对象数据库,并更新其工作目录以最终匹配。