目前 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 merge失败了,参考了你sample9里的代码。git_remote_fetch以后FETCH_HEAD文件更新成功的,.git\refs\remotes\origin\master文件里的commit id和FETCH_HEAD里的一致,.git\refs\heads\master里的和前面两个不同。那我猜测是后面add and commit时几个write函数出错了,我加了出错打印,发现函数调用都是成功的,不知道哪里出了差错,请指教啊。
感谢,我之前确实忽略掉了这个问题。之所以会多出一个 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 掉。
如果是在本地分支有修改的情况下,行为就不对了。
如果找到了更好的方法也希望可以分享一下。
我目前是只需要把远端的分支覆盖的本地就好了,参照你说的方法成功了,谢谢。
如果本地也修改了,把本地的commit id 和 远端的commit id合并生成一个新的commit id,
然后再reset是不是能同时保留本地和远端的修改呢(在不发生冲突的条件下)?
如果有修改,我觉得还是用 merge 的方式,新建 commit 会好一些。就是文章里写的这种方法。 git_status_list 在 reset 之前能取到吧?可以考虑先取数据再 reset。 另外获取文件改动信息可以通过 diff 的方式 比对两个 commit id 对应的 tree 。
用你文章里介绍的merge方法,我想把远端分支的内容更新到本地,没有成功。
现象就是git_remote_fetch以后FETCH_HEAD文件更新成功,然而本地分支并没有被更新,我不知道问题出在哪儿了。代码的话基本上和你sample9里的一致,郁闷中。。。请指教。
git_merge 合并的结果是 stage 的,相当于 add 之后的结果,需要新建 commit 才能加入到版本库中。不过如果一 commit 就会出现你第一个回复的问题,新添一个新的 commit id。
我把远端分支同步到本地以后,还想通过git_status_list_entrycount知道哪些文件被修改过了,
但是发现reset以后,git_status_list_entrycount返回值为0。
通过git_remote_fetch得到commit id以后,有其他方法既能把远端分支同步到本地又能获取相应的git_status_list_entrycount吗?