跳到主要内容

如何找出 Git 仓库中可能过时的文件

使用场景

在维护 Git 仓库,特别是文档类型的仓库,随着文档数量的增加,很容易出现信息不准确或过时的情况。为了解决这个问题,你可以将文档的内容与对应功能的代码或存在的 issue 绑定,当上游代码发生变化时,自动触发文档的更新。这种方式适合文档项目从 0 到 1 的初期,但是对于已经存在的文档项目重新维护这样的绑定关系就会变得很麻烦。

为了及时发现文档仓库中可能过时的内容,你可以使用 git log 命令获取文档的最后一次更新信息,以发现长期未更新的文档。下面具体介绍如何使用 git log 生成指定目录下所有 Markdown 文件的最后一次 commit 信息。

使用方法

备注

如果在 macOS 上使用该脚本,需要先安装 gnu-sedfindutils

brew install gnu-sed findutils
  1. 获取需要使用的脚本 generate_last_commit_report.sh

    git clone https://gist.github.com/862f24cec9a5915c71019dea2795c423.git scripts
    chmod +x scripts/generate_last_commit_report.sh
  2. 获取某个文档仓库、某个分支的最新内容:

    git clone https://github.com/{OWNER}/{REPO}.git {DOC_REPO}
    cd {DOC_REPO}
    git checkout {BRANCH}
  3. 运行 generate_last_commit_report.sh 脚本生成报告,其中第一个参数 {DOC_REPO} 为要生成报告的文件路径,第二个参数 {OWNER}/{REPO} 为文档仓库的 GitHub 地址:

    ./scripts/generate_last_commit_report.sh {DOC_REPO} {OWNER}/{REPO}

源码解读

下面以 pingcap/docs 为例介绍如何一步步写出这个脚本。

git clone https://github.com/pingcap/docs.git docs
cd docs

1. 获取一个文件的最后一次 commit

  1. 要获取一个文件的所有 commit log,你可以使用 git log 命令。下面以 _index.md 文件为例:

    Wiki: How to show the commit logs of a specific file?

    git log _index.md
  2. 获取指定文件的最后一次 commit 信息:

    要限制 commit logs 的数量,可以使用 -<number>, -n <number>, --max-count=<number> 选项:

    Wiki: How to show the latest commit log of a specific file?

    git log -1 _index.md
  3. 自定义最后一次 commit 信息的内容:

    如果需要自定义 commit 信息的内容,可以使用 --format=format:<string> 选项。更多使用方法,可参考 pretty formats

    下面示例的自定义格式为:最后一次 commit 的 UNIX 时间,commit 哈希值,最后一次 commit 作者名称,最后一次 commit 的 YYYY-MM-DD 格式时间,最后一次 commit 的相对时间

    Wiki: How to show commit logs in a custom format?

    git log -1 --format=format:"%at,%H,%an,%as,%ar%n" _index.md
  4. 使用 -P, -no-pager 选项不分页输出自定义的 commit 信息:

    Wiki: How to show the commit logs without paging?

    git --no-pager log -1 --format=format:"%at,%H,%an,%as,%ar%n" _index.md

2. 获取一个仓库所有 Markdown 文件的最后一次 commit 信息并按 commit 日期排序

  1. 获取一个仓库所有 Markdown 文件

    for FILE in $(gfind . -name "*.abc"); do
    echo "$FILE"
    done

    上面使用 for 循环遍历所有 .md 文件,当一个文件名中包含空格时,上面的命令会将其分割为多个文件名,例如:

    touch "a b c.abc"
    for FILE in $(gfind . -name "*.abc"); do
    echo "$FILE"
    done
    quote

    For loops over find output are fragile. Use find -exec or a while read loop.

    —— SC2044: ShellCheck Wiki

    为了避免这种情况,可以使用 while read -r 循环遍历所有 .md 文件:

    gfind . -name '*.md' | while read -r FILE; do
    echo "$FILE"
    done
  2. 获取所有 Markdown 文件的最后一次 commit 信息

    gfind . -name '*.md' | while read -r FILE; do
    git --no-pager log -1 --format=format:"%at,$FILE,%H,%an,%as,%ar%n" "$FILE"
    done
  3. 使用 sed 's~^./~~' 将输出结果中文件路径的 ./ 前缀删除,然后按照最后一次 commit 对应的 Unix 时间戳排序

    Wiki: sed 's/regexp/replacement/'

    (
    gfind . -name '*.md' | sed 's~^./~~' | while read -r FILE; do
    git --no-pager log -1 --format=format:"%at,$FILE,%H,%an,%as,%ar%n" "$FILE"
    done
    ) | sort --numeric-sort
  4. 使用 sed -E 's~^[0-9]+,~~' 去掉无意义的 Unix 时间戳:

    Wiki: sed -E 's/regexp/replacement/'

    (
    gfind . -name '*.md' | sed 's~^./~~' | while read -r FILE; do
    git --no-pager log -1 --format=format:"%at,$FILE,%H,%an,%as,%ar%n" "$FILE"
    done
    ) | sort --numeric-sort | sed -E 's~^[0-9]+,~~'

3. 生成报告

为了方便查看,下面的步骤将输出的结果生成 Markdown 表格。

  1. --format 中的分隔符改为 |,同时将 sed 命令修改为 's~^[0-9]+ ~~'

    (
    gfind . -name '*.md' | sed 's~^./~~' | while read -r FILE; do
    git --no-pager log -1 --format=format:"%at | $FILE | %H | %an | %as | %ar |%n" "$FILE"
    done
    ) | sort --numeric-sort | sed -E 's~^[0-9]+ ~~'
  2. 使用 GitHub 仓库地址、commit 哈希值以及文件名为文件 FILE 添加链接:

    (
    gfind . -name '*.md' | sed 's~^./~~' | while read -r FILE; do
    git --no-pager log -1 --format=format:'%at | '"[$FILE](https://github.com/pingcap/docs/blob/%H/$FILE)"' | %an | %as | %ar |%n' "$FILE"
    done
    ) | sort --numeric-sort | sed -E 's~^[0-9]+ ~~'
  3. 为表格加表头,并输出到文件 docs_commit_log.md

    (
    echo '| File | Last Commit Author | Last Commit Date | Relative Date |'
    echo '| ---- | ------------------ | ---------------- | ------------- |'
    (
    gfind . -name '*.md' | sed 's~^./~~' | while read -r FILE; do
    git --no-pager log -1 --format=format:'%at | '"[$FILE](https://github.com/pingcap/docs/blob/%H/$FILE)"' | %an | %as | %ar |%n' "$FILE"
    done
    ) | sort --numeric-sort | sed -E 's~^[0-9]+ ~~'
    )>"$PWD"/docs_commit_log.md
  4. 使用 DIRREPO 变量,使脚本更通用:

    #!/bin/bash
    set -e

    DIR=$1
    REPO=$2

    (
    echo '| File | Last Commit Author | Last Commit Date | Relative Date |'
    echo '| ---- | ------------------ | ---------------- | ------------- |'
    (
    cd "$DIR"
    gfind . -name '*.md' | sed 's~^./~~' | while read -r FILE; do
    git --no-pager log -1 --format=format:'%at | '"[$FILE](https://github.com/$REPO/blob/%H/$FILE)"' | %an | %as | %ar |%n' "$FILE"
    done
    ) | sort --numeric-sort | sed -E 's~^[0-9]+ ~~'
    )>"$PWD"/docs_commit_log.md
  5. 为了兼容 macOS,在脚本中使用 gfindgsed

    #!/bin/bash
    set -e

    DIR=$1
    REPO=$2

    FIND=$(which gfind || which find)
    SED=$(which gsed || which sed)

    (
    echo '| File | Last Commit Author | Last Commit Date | Relative Date |'
    echo '| ---- | ------------------ | ---------------- | ------------- |'
    (
    cd "$DIR"
    $FIND . -name '*.md' | $SED 's~^./~~' | while read -r FILE; do
    git --no-pager log -1 --format=format:'%at | '"[$FILE](https://github.com/$REPO/blob/%H/$FILE)"' | %an | %as | %ar |%n' "$FILE"
    done
    ) | sort --numeric-sort | $SED -E 's~^[0-9]+ ~~'
    ) >"$PWD"/docs_commit_log.md