2

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

首先,我在分支 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. 

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

4

2 回答 2

3

如果一定要自己写,可以看一下libgit2中现有实现使用的基本策略。让我们考虑实现强制签出(即忽略工作目录中任何修改过的文件),因为这是一个更简单的情况。

您还没有提到您的 libgit2 的年龄。假设您可以访问 diff 功能,我将编写以下内容,并且我什至会使用一些更新的访问器函数来获取 diff 数据。如果这些访问器函数在您的版本中不可用,您可能需要重新设计它以使用回调函数。如果核心 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.path和中的路径delta->old_file.path相对于存储库的工作目录,而不是进程的当前工作目录,因此打开和取消链接文件的调用将需要相应地调整路径
  2. 该代码根本不处理目录。在打开文件之前,您必须创建包含该文件的目录。删除文件后,如果包含该文件的目录是目录中的最后一个文件,则必须删除该目录。如果您有一个目录变成常规文件的分支,反之亦然,您必须在添加之前处理删除。
  3. 该代码不做任何错误检查,这是一个坏主意
  4. 此代码忽略索引中的未决更改和工作目录中的修改。但我们谈论的是强制结账,所以你得到你得到的。
  5. 我只是在脑海中写了上面的代码,所以可能有错别字等。

根据您的用例,也许可以忽略类型更改(即成为 blob 的目录等),并且可能--force可以接受模拟。如果不是,那么这真的开始变成很多代码。

于 2013-09-27T19:42:38.640 回答
2

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

git_checkout_head(repo, NULL);

所以我要说:将你的 libgit2 升级到更新的版本。你会很不满意继续使用旧版本。尤其是 checkout 并不是您想要自己实现的功能。(看看 libgit2 的checkout.c.)

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

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

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

  3. 更新索引以反映您使用git_index_add_bypath.

libgit2 checkout 文档中提供了一组非常详细的信息。

于 2013-09-26T17:46:39.797 回答