libgit2使用教程(九)git fetch & pull

目前 libgit2 对 pull 操作的支持还不是太好,所以目前能找到的资料都指出 pull 操作就是先 fetch 然后再 merge 目标分支。比如要实现 git pull origin master ,就先 fetch 然后将 master 切换到 head 再将 origin master 合并到 head。

那么就先 fetch

git_remote *remote = nullptr;
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;

// get a remote
git_remote_lookup(&remote, rep, "origin");
// git fetch
fetch_opts.callbacks.credentials = cred_acquire_cb;
git_remote_fetch(remote, nullptr, &fetch_opts, nullptr);

git_remote_fetch 这个接口的后三个参数都是可以传 null 的,但是如果报了下面这个错:
authentication required but no callback set
说明远端地址需要验证身份,所以我们要设置 ssh 证书的回调。

int cred_acquire_cb(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types,
                    void *payload)
{
    git_cred_ssh_key_new(cred, username_from_url, nullptr, "/Users/xiaochen/.ssh/id_rsa", nullptr);
    return 0;
}

这样就完成了 fetch 操作。so easy

prune 也是 fetch 操作的一个重要参数。表示:清理掉远端已经删除的分支对应的本地分支,有点绕,不过应该还好理解吧。具体信息可以查一下下面这个命令。
git fetch -p (或 git fetch –prune)
在代码里实现只需要在 option 参数里做一个设置:

fetch_opts.prune = GIT_FETCH_PRUNE;

这样就强制使用 prune 参数了。另外几个可选枚举值可以去读注释。

接下来为了实现类似 pull 的效果,就是蛋疼的合并操作了,顺便可以复习一下之前的内容。
首先将本地 master 设为 head

git_reference *local_master = nullptr;
git_branch_lookup(&local_master, rep, "master", GIT_BRANCH_LOCAL);
git_repository_set_head(rep, git_reference_name(local_master));

然后去拿 origin master 的 commit

git_reference *origin_master = nullptr;
git_branch_lookup(&origin_master, rep, "origin/master", GIT_BRANCH_REMOTE);

合并

git_merge_options merge_opt = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_opt = GIT_CHECKOUT_OPTIONS_INIT;
const git_annotated_commit *their_head[10];
git_annotated_commit_from_ref((git_annotated_commit **) &their_head[0], rep, origin_master);
git_merge(rep, their_head, 1, &merge_opt, &checkout_opt);

解决冲突

git_index *index = nullptr;
git_index_conflict_iterator *conflict_iterator = nullptr;
git_repository_index(&index, rep);
if (git_index_has_conflicts(index))
{
    const git_index_entry *ancestor_out = nullptr;
    const git_index_entry *our_out = nullptr;
    const git_index_entry *their_out = nullptr;
    git_index_conflict_iterator_new(&conflict_iterator, index);
    while (git_index_conflict_next(&ancestor_out, &our_out, &their_out, conflict_iterator) != GIT_ITEROVER)
    {
        if (ancestor_out) std::cout << "ancestor: " << ancestor_out->path << std::endl;
        if (our_out) std::cout << "our: " << our_out->path << std::endl;
        if (their_out) std::cout << "their: " << their_out->path << std::endl;
    }
    // git checkout --theirs <file>
    git_checkout_options opt = GIT_CHECKOUT_OPTIONS_INIT;
    opt.checkout_strategy |= GIT_CHECKOUT_USE_THEIRS;
    git_checkout_index(rep, index, &opt);
    git_index_conflict_iterator_free(conflict_iterator);
}

add 和 commit

git_commit_lookup(&their_commit, rep, git_reference_target(origin_master));
git_commit_lookup(&our_commit, rep, git_reference_target(local_master));
git_index_update_all(index, nullptr, nullptr, nullptr);
git_index_write(index);
git_index_write_tree(&new_tree_id, index);
git_tree_lookup(&new_tree, rep, &new_tree_id);
git_signature_now(&me, "XiaochenFTX", "xiaochenftx@gmail.com");
git_commit_create_v(&commit_id, rep, git_reference_name(local_master), me, me, "UTF-8", "pull commit", new_tree, 2,
                    our_commit, their_commit);

清空状态

git_repository_state_cleanup(rep);

打完收工。

示例代码:sample9

《libgit2使用教程(九)git fetch & pull》有8个想法

  1. 博主好。我尝试libgit2 merge失败了,参考了你sample9里的代码。git_remote_fetch以后FETCH_HEAD文件更新成功的,.git\refs\remotes\origin\master文件里的commit id和FETCH_HEAD里的一致,.git\refs\heads\master里的和前面两个不同。那我猜测是后面add and commit时几个write函数出错了,我加了出错打印,发现函数调用都是成功的,不知道哪里出了差错,请指教啊。

    1. 感谢,我之前确实忽略掉了这个问题。之所以会多出一个 oid ,是生成的新 commit 。如果需要不生成新的 commit ,只要把远端的更新拉到本地,有一个方法:
      const char *refs[] = {“refs/heads/master:refs/heads/master”};
      git_strarray refspecs = {(char**)refs, 1};

      git_remote_fetch(remote, &refspecs, &fetch_opts, nullptr);
      在 fetch 的时候传入 refspecs 。 本地分支可以直接同步成远端分支,只不过所有改动会在暂存区保留一份,需要手动 reset 掉。
      如果是在本地分支有修改的情况下,行为就不对了。
      如果找到了更好的方法也希望可以分享一下。

      1. 我目前是只需要把远端的分支覆盖的本地就好了,参照你说的方法成功了,谢谢。

        如果本地也修改了,把本地的commit id 和 远端的commit id合并生成一个新的commit id,
        然后再reset是不是能同时保留本地和远端的修改呢(在不发生冲突的条件下)?

        1. 如果有修改,我觉得还是用 merge 的方式,新建 commit 会好一些。就是文章里写的这种方法。 git_status_list 在 reset 之前能取到吧?可以考虑先取数据再 reset。 另外获取文件改动信息可以通过 diff 的方式 比对两个 commit id 对应的 tree 。

          1. 用你文章里介绍的merge方法,我想把远端分支的内容更新到本地,没有成功。

            现象就是git_remote_fetch以后FETCH_HEAD文件更新成功,然而本地分支并没有被更新,我不知道问题出在哪儿了。代码的话基本上和你sample9里的一致,郁闷中。。。请指教。

          2. git_merge 合并的结果是 stage 的,相当于 add 之后的结果,需要新建 commit 才能加入到版本库中。不过如果一 commit 就会出现你第一个回复的问题,新添一个新的 commit id。

      2. 我把远端分支同步到本地以后,还想通过git_status_list_entrycount知道哪些文件被修改过了,
        但是发现reset以后,git_status_list_entrycount返回值为0。

        通过git_remote_fetch得到commit id以后,有其他方法既能把远端分支同步到本地又能获取相应的git_status_list_entrycount吗?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

RSS
Follow by Email
YouTube
YouTube
Pinterest
fb-share-icon
LinkedIn
Share
VK
Weibo
WeChat
WhatsApp
Reddit
FbMessenger
Copy link
URL has been copied successfully!