有没有什么技巧可以在使用 git log --follow 命令时跟踪一个已更名的目录?

12

git命令有一个很有用的命令,可以在文件重命名后跟踪文件,例如git log --follow path/to/some/file。不幸的是,它只适用于单个文件。我想能够执行等效于git log --follow some/directory的操作。

可能的一种方法是通过使用git ls-tree命令输出并在循环中执行此操作,但问题是涉及多个文件的提交将无法"合并"成一个提交。

是否有更好的方法?注意:使用git 2.7.4版本。


好问题。你找到答案了吗? - TonySalimi
3个回答

10

不。

Git 不存储目录,只存储文件。当你在任何要查看整个提交记录的命令(如git loggit diff)中使用路径名称时,Git 会从整个提交记录开始,然后缩小到与该路径匹配的文件。这里的目录只是选择其中的每个文件。

--follow 选项只能跟踪一个文件。因此,如果你可以设法将其应用于一个目录,Git 首先会将该目录转换为一组文件,然后选择其中一个文件并跟踪它。

(实际的 --follow 代码非常复杂。它利用了重命名检测代码,但仅在比较提交记录时是从新到旧顺序时才起作用:如果添加了 --reverse,则 --follow 根本不起作用。可能需要彻底重写整个代码。通过重新编码,也许可以使其处理多个文件名,甚至是包含文件的目录。)


1
我知道只有文件被存储; 我想也许有人已经找到了解决办法,甚至是hackish的解决办法...谁知道呢 :) - fge
@JérômePouiller:Git不使用任何常规的问题跟踪软件。如果您愿意,可以提交错误报告,但是如果您搜索Git邮件列表档案,您会发现“--follow”是一个hack的事实出现了很多次。迄今为止,没有人成功地提出了令人满意的重写方案... - torek
git log --follow works with a directory without any problem, simplest example: git log --follow . and I get git log pertaining to all files in the current dir (git version 2.25.1). Similarly the pathspec option works git log -- ... - bloody
@bloody:目录从不更名;对于此情况,“--follow”选项是无操作的。 - torek
好的,我错过了op问题中的重命名方面,抱歉。但是它不是“no-op”,它生成以下包含文件的日志 - 它具有路径意义,而不是dir-object。我建议,可能有用 :) - bloody
显示剩余3条评论

1

似乎没有内置的方法可以做到这一点。

可以使用脚本并遵循简单的算法来完成此操作:

  • 为每个文件生成与之关联的提交列表
  • 组合所有列表并对它们进行稳定排序(这很棘手,因为确定提交的顺序并不直观)
  • 迭代执行每个提交的 git log,并将其全部通过 less 管道传输

以下是使用 Python 3 和 sh 模块的 hacky 方法,由于各种原因,在 Windows 上无法工作。

#!/usr/bin/env python3

import os
import shlex
import sys
import tempfile

import sh

def _get_commits(fname):
    # Ask for the commit and the timestamp
    # .. the timestamp doesn't guarantee ordering, but good enough
    for c in sh.git('log', '--follow', '--pretty=%ct %h', fname,
                   _tty_out=False, _iter=True):
        c = c.strip().split()
        yield int(c[0]), c[1]

def git_log_follow_multi(filenames):
    if len(filenames) == 0:
        print("Specify at least one file to log")
    elif len(filenames) <= 1:
        os.system('git log --follow -p %s' % filenames[0])
    else:
        # Use git log to generate lists of commits for each file, sort
        commits = []
        for fname in filenames:
            commits += _get_commits(fname)

        # Sort the lists (python's sort is stable)
        commits.sort(reverse=True)

        # Uniquify (http://www.peterbe.com/plog/uniqifiers-benchmark)
        seen = set()
        seen_add = seen.add
        commits = [c for c in commits if not (c in seen or seen_add(c))]

        # Finally, display them
        tname = None

        try:
            file_list = ' '.join(shlex.quote(fname) for fname in filenames)

            with tempfile.NamedTemporaryFile(mode='w', delete=False) as fp:
                tname = fp.name
                for _, commit in commits:
                    fp.write('git log -p -1 --color %s %s\n' % (commit, file_list))

            # Use os.system to make our lives easier
            os.system('bash %s | less -FRX' % tname)
        finally:
            if tname:
                os.unlink(tname)


if __name__ == '__main__':
    git_log_follow_multi(sys.argv[1:])

现在,这个脚本并不完全符合你的需求,因为它需要一个文件列表,但是你可以使用glob来执行它,它会做你想要的事情。
./script.py src/*

0

正如torek所提到的那样, git diff --follow可以跟踪重命名,但仅适用于单个文件。在Bash(或Windows上的Git Bash)中组合几个命令可以得到包含目录中所有文件的组合结果:

find /path/to/directory -type f \
  -exec git log --oneline --follow '{}' \; \
  | cut -d ' ' -f 1 \
  | sort --unique > /tmp/commits ; \
git log --oneline \
  | cut -d ' ' -f 1 \
  | grep -F -f /tmp/commits \
  | xargs -I {} git --no-pager show {} --no-patch ; \
rm /tmp/commits
  1. find /path/to/directory -type f 可以找到所选目录下的所有文件。

    这可以进行调整,例如,-maxdepth 1 排除子目录中的文件,! -name '*.bin' 排除某些文件。

  2. -exec git log --oneline --follow '{}' \; 对所有找到的文件执行 git log,跟踪每个文件的重命名。

  3. cut -d ' ' -f 1 提取提交哈希值。

  4. sort --unique > /tmp/commits 删除重复项(并按字母数字顺序排序哈希值)。结果存储在临时文件中。

  5. git log --oneline 获取完整日志 按正确顺序

  6. cut -d ' ' -f 1 提取提交哈希值。

  7. grep -F -f /tmp/commits 提取影响我们感兴趣的文件的条目。它们现在按正确顺序排列。

  8. xargs -I {} git --no-pager show {} --no-patch 显示提交详细信息。

    这可以进行调整,例如,删除 --no-patch 以查看更改内容,或使用 --format=short 格式化输出。

  9. rm /tmp/commits 再次删除临时文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接