如何使用git2r(libgit2)获取初始提交中包含的文件?

时间:2017-01-02 20:41:05

标签: r git libgit2 ropensci

我正在使用R包git2rlibgit2进行交互。我想获取每次提交中更新的文件列表,类似于git log --statgit log --name-only的输出。但是,我无法获取初始提交中包含的文件。下面我提供代码来设置示例Git存储库以及基于我的研究尝试的解决方案。

可重复的例子

下面的代码在/tmp中创建一个临时目录,创建空文本文件,然后单独提交每个文件。

# Create example Git repo
path <- tempfile("so-git2r-ex-")
dir.create(path)
setwd(path)
# Set the number of fake files
n_files <- 3
file.create(paste0("file", 1:n_files, ".txt"))
library("git2r")
repo <- init(".")
for (i in 1:n_files) {
  add(repo, sprintf("file%d.txt", i))
  commit(repo, sprintf("Added file %d", i))
}

选项1 - 比较两棵树的差异

这个SO post建议您执行diff比较所需提交的树对象及其父提交。这种方法很有效,除了初始提交,因为没有父提交将它与它进行比较。

get_files_from_diff <- function(c1, c2) {
  # Obtain files updated in commit c1.
  # c2 is the commit that preceded c1.
  git_diff <- diff(tree(c1), tree(c2))
  files <- sapply(git_diff@files, function(x) x@new_file)
  return(files)
}

log <- commits(repo)
n <- length(log)
for (i in 1:n) {
  print(i)
  if (i == n) {
    print("Unclear how to obtain list of files from initial commit.")
  } else {
    files <- get_files_from_diff(log[[i]], log[[i + 1]])
    print(files)
  }
}

选项2 - 解析提交摘要

SO post建议通过解析提交摘要来获取提交信息,例如更改的文件。这与git log --stat非常相似,但异常是初始提交。它没有列出任何文件。查看source code,提交摘要中的文件是通过上面相同的方法获得的,这解释了为什么没有为初始提交显示文件(它没有父提交)。

for (i in 1:n) {
  summary(log[[i]])
}

更新

这应该是可能的。 Git命令diff-tree有一个标志--root,用于将根提交与NULL树(source)进行比较。从手册页:

   --root
       When --root is specified the initial commit will be shown as a
       big creation event. This is equivalent to a diff against the
       NULL tree.

此外,libgit2库具有函数git_diff_tree_to_tree,它接受​​NULL树。不幸的是,我不清楚是否可以通过git2r git2r_diff将NULL树传递给git2r C函数diff method for git-tree objects。有没有办法用git2r创建一个NULL树对象?

> tree()
Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function ‘tree’ for signature ‘"missing"’
> tree(NULL)
Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function ‘tree’ for signature ‘"NULL"’

1 个答案:

答案 0 :(得分:0)

我根据同事的见解提出了一个解决方案,您可以通过检查git_tree对象来获取当前正在跟踪的文件。这显示了到目前为止已跟踪的所有文件,但由于根提交是第一次提交,这意味着必须在该提交中添加这些文件。

摘要方法打印文件,可以使用as方法捕获此数据框。

summary(tree(log[[n]]))
#    mode type                                      sha      name
# 1 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file1.txt
as(tree(log[[n]]), "data.frame")
#    mode type                                      sha      name
# 1 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file1.txt

以下函数从根提交中获取文件。虽然在这个小例子中并不明显,但主要的复杂因素是子目录被表示为树,因此您需要递归搜索树以获取所有文件名。

obtain_files_in_commit_root <- function(repo, commit) {
  # Obtain the files in the root commit of a Git repository
  stopifnot(class(repo) ==  "git_repository",
            class(commit) == "git_commit",
            length(parents(commit)) == 0)
  entries <- as(tree(commit), "data.frame")
  files <- character()
  while (nrow(entries) > 0) {
    if (entries$type[1] == "blob") {
      # If the entry is a blob, i.e. file:
      #  - record the name of the file
      #  - remove the entry
      files <- c(files, entries$name[1])
      entries <- entries[-1, ]
    } else if (entries$type[1] == "tree") {
      # If the entry is a tree, i.e. subdirectory:
      #  - lookup the entries for this tree
      #  - add the subdirectory to the name so that path is correct
      #  - remove the entry from beginning and add new entries to end of
      #    data.frame
      new_tree_df <- as(lookup(repo, entries$sha[1]), "data.frame")
      new_tree_df$name <- file.path(entries$name[1], new_tree_df$name)
      entries <- rbind(entries[-1, ], new_tree_df)
    } else {
      stop(sprintf("Unknown type %s found in commit %s",
                   entries$type[1], commit))
    }
  }

  return(files)
}

obtain_files_in_commit_root(repo, log[[n]])
# [1] "file1.txt"