从提交树中提取与代码更改相关的提交

Extract commits related to code changes from commit tree

现在我可以使用 pygit2 库遍历 github 存储库的提交树。我正在获取存储库中每个文件更改的所有提交。这意味着我正在获取存储库中扩展名为 .rtf 的文本文件的更改。如何过滤掉仅与代码更改相关的提交?我不想要与文本文档相关的更改。

感谢任何帮助或指点。谢谢

last = repo[repo.head.target]

t0=last

f = open(outputFile,'w')

print t0.hex


for commit in repo.walk(last.id):
     if t0.hex == commit.hex:
        continue

     print commit.hex
     out=repo.diff(t0,commit)
     f.write(out.patch)
     t0=commit;

作为输出的一部分,我得到了 rtf 文件的差异以及以下内容:

diff --git a/archived-output/NEW/action-core[best].rtf b/archived-output/NEW/action-core[best].rtf
deleted file mode 100644
index 56cdec6..0000000
--- a/archived-output/NEW/action-core[best].rtf
+++ /dev/null
@@ -1,8935 +0,0 @@
-{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}
-{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}

要么我必须从树中过滤提交,要么我必须过滤输出。我在想是否可以通过在遍历树时删除相应的提交来删除与 rtf 文件相关的更改。

If that is possible, how do we get the list of modified files?

啊,现在你问对了问题! Git,当然不会在每次commit时存储修改文件的列表。相反,每个提交代表整个存储库在某个时间点的状态。为了找到被修改的文件,您需要将一次提交中包含的文件与上一次提交进行比较。

对于 repo.walk() 返回的每个提交,tree 属性引用关联的 Tree 对象(它本身是代表文件和目录的 TreeEntry 对象的列表包含在那个特定的 Tree).

一个 Tree 对象有一个 diff_to_tree() 方法可以用来将它与另一个 Tree 对象进行比较。这个 returns 一个 Diff 对象,它充当 Patch 对象列表的迭代器。每个 Patch 对象指的是正在比较的两个 Tree 之间单个文件中的更改。

Patch 对象真的是这一切的关键,因为这就是 我们确定哪些文件已被修改。

以下代码演示了这一点。对于每次提交,它将打印 新的、修改的或删除的文件列表:

import stat
import pygit2


repo = pygit2.Repository('.')

prev = None
for cur in repo.walk(repo.head.target):

    if prev is not None:
        print prev.id
        diff = cur.tree.diff_to_tree(prev.tree)
        for patch in diff:
            print patch.status, ':', patch.new_file_path,
            if patch.new_file_path != patch.old_file_path:
                print '(was %s)' % patch.old_file_path,
            print

    if cur.parents:
        prev = cur
        cur = cur.parents[0]

如果我们运行这个反对a sample repository,我们可以看看 前几次提交的输出:

c285a21e013892ee7601a53df16942cdcbd39fe6
D : fragments/configure-flannel.sh
A : fragments/flannel-config.service.yaml
A : fragments/write-flannel-config.sh
M : kubecluster.yaml
b06de8f2f366204aa1327491fff91574e68cd4ec
M : fragments/enable-services-master.sh
M : fragments/enable-services-minion.sh
c265ddedac7162c103672022633a574ea03edf6f
M : fragments/configure-flannel.sh
88a8bd0eefd45880451f4daffd47f0e592f5a62b
A : fragments/configure-docker-storage.sh
M : fragments/write-heat-params.yaml
M : kubenode.yaml

并将其与 git log --oneline --name-status 的输出进行比较:

c285a21 configure flannel via systemd unit
D       fragments/configure-flannel.sh
A       fragments/flannel-config.service.yaml
A       fragments/write-flannel-config.sh
M       kubecluster.yaml
b06de8f call daemon-reload before starting services
M       fragments/enable-services-master.sh
M       fragments/enable-services-minion.sh
c265dde fix json syntax problem
M       fragments/configure-flannel.sh
88a8bd0 configure cinder volume for docker storage
A       fragments/configure-docker-storage.sh
M       fragments/write-heat-params.yaml
M       kubenode.yaml

...aaaand,看起来几乎相同。希望这就足够了 给你开始了。

这主要是将重写为

  • 当前pygit2API
  • Python3

它还修复了迭代逻辑中的一个缺陷:当遍历修订范围 (a..b) 时,原始代码将无法将最后修订与其父版本进行比较。

以下近似命令

git log --name-status --pretty="format:Files changed in %h" origin/devel..master

在 larsks 给出的示例存储库上。

不过我无法跟踪文件重命名。这被打印为删除和添加。从未到达打印重命名的代码行。

import pygit2

repo = pygit2.Repository('.')

# Show files changed between origin/devel and current HEAD
devel = repo.revparse_single('origin/devel')
walker = repo.walk(repo.head.target)
walker.hide(devel.id)

for cur in walker:
    if cur.parents:
        print (f'Files changed in {cur.short_id}')
        prev = cur.parents[0]

        diff = prev.tree.diff_to_tree(cur.tree)
        for patch in diff:
            print(patch.delta.status_char(), ':', patch.delta.new_file.path)
            if patch.delta.new_file.path != patch.delta.old_file.path:
                print(f'(was {patch.delta.old_file.path})'.)
        print()