使用libgit2检出分支时如何更新工作树?

时间:2013-09-26 17:39:35

标签: git libgit2

我正在尝试使用旧版本的libgit2(没有checkout.h)实现类似checkout的功能。

首先,我在分支A上,看起来像:

Branch:
A         A0 --- A1
         /
Master  M

每次提交都会创建一个具有相同名称的文件,例如,标记为A1的提交会创建一个文件A1。如果我在这一点上看gitk,一切看起来都是我想要的方式。

现在我创建了一个新的分支B,我想添加一个提交:

Branch:
A         A0 --- A1
         /
Master  M
         \
B         B0

但是,当我使用我的代码“结帐”B时,它会使A0和A1未跟踪而不是删除它们,正如我所期望的那样:

(B)$ git status
# On branch B
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       A0
#       A1
nothing added to commit but untracked files present (use "git add" to track)

所以,我认为我的结帐代码缺少一些东西,这就是:

void Checkout(const char *branch_name, git_commit *branch_tip) {
  // Update index & tree                                                                                                                                                                                                                                                       
  git_tree *tree;
  git_commit_tree(&tree, branch_tip);
  git_index_read_tree(index_, tree);
  git_index_write(index_);
  git_tree_free(tree);

  // Reset head
  string branch_ref = string("refs/heads/") + branch_name;
  git_reference *head;
  git_reference_lookup(&head, repo_, kGitHeadFile);
  git_reference_set_target(head, branch_ref.c_str());
  git_reference_free(head);
}

(请注意,我实际上正在检查实际代码中每一行的返回代码,并且所有内容都返回0,只是不想在这里混乱。)

据我所知,此代码与git文档描述git checkout <branch>

的内容相符
To prepare for working on <branch>, switch to it by updating
the index and the files in the working tree, and by pointing
HEAD at the branch. 

是否有一些......“更新工作树”命令我需要运行吗?

2 个答案:

答案 0 :(得分:3)

如果您必须自己编写,可以查看libgit2中现有实现使用的基本策略。让我们考虑实施强制结账(即忽略工作目录中的任何修改过的文件),因为这是一个更简单的案例。

你还没有提到你的libgit2有多大。我将编写以下内容,假设您可以访问diff功能,我甚至会使用一些稍微更新的访问器函数来实现差异数据。如果您的版本中没有这些访问器功能,则可能必须重新设置此功能才能使用回调功能。如果核心差异功能不可用,那么你的libgit2太老了,我相信。

您需要考虑您来自的旧HEAD以及您要移动的新HEAD,以便了解哪些文件将被删除(与工作目录中未跟踪的文件相比)。在libgit2中最简单的事情就是:

git_diff_list *diff;
git_diff_delta *delta;
git_blob *blob;
size_t i;
FILE *fp;

git_diff_tree_to_tree(&diff, repo, from_tree, to_tree, NULL);

for (i = 0; i < git_diff_num_deltas(diff); ++i) {
    git_diff_get_patch(NULL, &delta, diff, i);

    switch (delta->status) {
    case GIT_DELTA_ADDED:
    case GIT_DELTA_MODIFIED:
        /* file was added or modified between the two commits */
        git_blob_lookup(&blob, repo, &delta->new_file.oid);

        fp = fopen(delta->new_file.path, "w");
        fwrite(git_blob_rawdata(blob), git_blob_rawsize(blob), 1, fp);
        fclose(fp);

        git_blob_free(blob);
        break;

    case GIT_DELTA_DELETED:
        /* file was removed between the two commits */
        unlink(delta->old_file.path);
        break;

    default:
        /* no change required */
    }
}

git_diff_list_free(diff);

/* now update the index with the tree we just wrote out */
git_index_read_tree(index, to_tree);
git_index_write(index);

/* and do the other stuff you have to update the HEAD */

上面的实际代码存在很多问题,您必须解决这些问题:

  1. delta->new_file.pathdelta->old_file.path中的路径相对于存储库的工作目录,而不是进程的当前工作目录,因此打开和取消链接文件的调用需要调整相应的路径
  2. 代码根本不处理目录。在打开文件之前,您必须创建包含该文件的目录。删除文件后,如果该文件是目录中的最后一个文件,则必须删除包含该文件的目录。如果你有一个目录变成常规文件的分支,反之亦然,你必须在添加之前处理删除。
  3. 代码不做任何错误检查,这是一个坏主意
  4. 此代码忽略索引中的挂起更改和工作目录中的修改。但我们谈论的是强制结账,所以你得到的就是你得到的。
  5. 我刚才把上面的代码写在了我的头顶,所以可能有拼写错误等。
  6. 根据您的使用情况,也许可以忽略类型更改(即变为blob的目录等),并且可能可以接受仿真--force。如果没有,那么这真的开始变成很多代码。

答案 1 :(得分:2)

您没有说明为什么您不能升级到最近的支持结帐的libgit2,以便您可以致电:

git_checkout_head(repo, NULL);

所以我要说:将你的libgit2升级到更新的东西。你会非常不满意继续使用旧版本。特别是,结账不是您想要自己实现的功能。 (看看libgit2&#39; s checkout.c。)

但回答你的问题,基本方法是:

  1. 将工作目录与HEAD指向的树进行比较。收集任何不同项目的列表,理想情况下使用缓存,这样就不需要计算索引和workdir之间明显不变的文件的哈希值。请确保从配置中加载过滤器,并根据需要应用您自己的版本,因为过滤器未在任何缺少结帐的libgit2版本中公开。

  2. 对于该列表中的每个项目,将数据写入磁盘。请务必根据需要应用任何过滤器。同样,您必须推出自己的过滤器。

  3. 更新索引以反映您使用git_index_add_bypath写入文件系统的内容。

  4. libgit2 checkout documentation中提供了一组非常详细的信息。