1

我正在使用 R 包git2rlibgit2接口。我想获取每次提交中更新的文件列表,类似于git log --stator的输出git 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 帖子建议您执行比较所需提交的树对象及其父提交的差异。这很好用,除了初始提交,因为没有父提交可以比较它。

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 - 解析提交摘要

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

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 树。不幸的是,我不清楚是否可以通过git-tree objects的 git2r diff 方法将 NULL 树传递给 git2r C 函数git2r_diff。有没有办法用 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"’
4

1 回答 1

0

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

summary 方法打印文件,并且可以使用该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"
于 2017-01-09T19:42:05.430 回答