GitPython:如何检测文件是否被删除?

GitPython: How to detect if file is deleted?

我正在编写一个自动化脚本来处理 staged/unstaged 文件以获得预提交挂钩。 我想知道文件是否被 git(git status --porcelain -- {filename} 中的“D”或“D”)标记为已删除,以便我可以将其从列表中删除。如果可能的话,我想通过 GitPython API 来完成。

之前尝试的步骤:

其中 deleted_file 似乎是唯一明智的候选者 - 但它似乎没有反映 git status --porcelain 的结果,因为 [=43= 中的所有已删除文件]状态设置为deleted_file=False(与未删除的相同)。

现在我直接依赖 git 来断言文件是否被删除:

def _is_deleted(path: str):
    files = _gitstatus()
    return 'D' in files[path]


@lru_cache(maxsize=1)
def _gitstatus():
    child = subprocess.Popen(["git", "status", "--porcelain"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = [stream.decode('utf-8') if stream is not None else '' for stream in child.communicate()]
    if child.returncode != 0:
        raise Exception(err)
    files = {
        line[3:]: tuple(line[0:2])
        for line in out.split('\n')
    }
    return files

但如果可能,我更愿意使用 GitPython API。知道如何实现上述功能的等效结果吗?

在命令行中,我会使用:

  • git diff --name-status 获取跟踪文件与索引相比的状态,
  • git diff --staged --name-status 获取暂存文件与当前提交相比的状态。

我认为您可以从 GitPython 的 diff api 中提取相同的信息:

hcommit = repo.head.commit
hcommit.diff()                  # diff tree against index
hcommit.diff('HEAD~1')          # diff tree against previous tree
hcommit.diff(None)              # diff tree against working tree

index = repo.index
index.diff()                    # diff index against itself yielding empty diff
index.diff(None)                # diff index against working copy
index.diff('HEAD')              # diff index against current HEAD tree

git diff 将是 index.diff(None)git diff --staged 将是 index.diff('HEAD')hcommit.diff().

这会回答你的问题吗?

这是在使用 GitPython library, it's a bit involved but you can get the similar result to as git status using the Repo.index.diff(). This function is similar if not the same as the git.diff.Diff,您可以在此处的文档中查看如何过滤文件。

from git import Repo

repo = Repo()

if repo.is_dirty():

    index = repo.index
    
    for obj in index.diff(None).iter_change_type('D'):
        print('File path', obj.b_path)
        print('Change type', obj.change_type)
        print('Is deleted', obj.deleted_file)

(git-test) ➜  (01/09 20:18) /tmp/git-test git:(master) ✗ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    test.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        git-test.py

no changes added to commit (use "git add" and/or "git commit -a")


(git-test) ➜  (01/09 20:19) /tmp/git-test git:(master) ✗ python git-test.py
File path test.txt
Change type D
Is deleted True

已更新以显示未跟踪、暂存和已删除的文件

from git import Repo

repo = Repo()

if repo.is_dirty():

    index = repo.index

    print('Untracked files', repo.untracked_files)

    print('Staged files', [item.a_path for item in repo.index.diff('HEAD')])

    for obj in index.diff(None):
        print('File path', obj.b_path)
        print('Change type', obj.change_type)
        print('Is deleted', obj.deleted_file)

(git-test) ➜  (02/09 13:58) /tmp/git-test git:(master) ✗ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   test-2.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    test.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .idea/
        git-test.py

(git-test) ➜  (02/09 13:58) /tmp/git-test git:(master) ✗ python git-test.py
Untracked files ['.idea/.gitignore', '.idea/git-test.iml', '.idea/misc.xml', '.idea/modules.xml', '.idea/vcs.xml', 'git-test.py']
Staged files ['test-2.txt']
File path test.txt
Change type D
Is deleted True