Skip to content
返回

如何使用 Git Hooks 设置创建和修改日期

更新于:

在这篇文章中,我将解释如何使用 pre-commit Git 钩子自动输入 AstroPaper 博客主题前言中的创建(pubDatetime)和修改(modDatetime)日期。

目录

随处安装

Git 钩子非常适合自动化任务,例如向提交消息中添加检查分支名称,或者阻止你提交明文密码。它们最大的缺点是客户端钩子是针对每台机器的。你可以通过创建一个 hooks 目录并手动将它们复制到 .git/hooks 目录或设置符号链接来解决这个问题,但这都需要你记得去设置,而这正是我不擅长做的事情。 由于该项目使用 npm,我们可以利用一个名为 Husky 的包(AstroPaper 中已安装)为我们自动安装钩子。

更新!在 AstroPaper v4.3.0 中,pre-commit 钩子已被移除,取而代之的是 GitHub Actions。但是,你可以轻松自己安装 Husky

钩子

因为我们希望这个钩子在提交代码时运行以更新日期,然后将其作为更改的一部分,所以我们将使用 pre-commit 钩子。AstroPaper 项目已经设置好了这个,但如果没设置,你需要运行 npx husky add .husky/pre-commit 'echo "This is our new pre-commit hook"'。 导航到 hooks/pre-commit 文件,我们将添加以下一个或两个代码片段。

编辑文件时更新修改日期


更新:本节已更新为更智能的新版钩子。现在它直到文章发布才会增加 modDatetime。首次发布时,将 draft 状态设置为 first,然后见证奇迹发生。

# 修改过的文件,更新 modDatetime
git diff --cached --name-status | grep -i '^M.*\.md$' | while read _ file; do
  filecontent=$(cat "$file")
  frontmatter=$(echo "$filecontent" | awk -v RS='---' 'NR==2{print}')
  draft=$(echo "$frontmatter" | awk '/^draft: /{print $2}')
  if [ "$draft" = "false" ]; then
    echo "$file modDateTime updated"
    cat $file | sed "/---.*/,/---.*/s/^modDatetime:.*$/modDatetime: $(date -u "+%Y-%m-%dT%H:%M:%SZ")/" > tmp
    mv tmp $file
    git add $file
  fi
  if [ "$draft" = "first" ]; then
    echo "First release of $file, draft set to false and modDateTime removed"
    cat $file | sed "/---.*/,/---.*/s/^modDatetime:.*$/modDatetime:/" | sed "/---.*/,/---.*/s/^draft:.*$/draft: false/" > tmp
    mv tmp $file
    git add $file
  fi
done

git diff --cached --name-status 从 git 获取已暂存待提交的文件。输出如下所示:

A src/content/blog/setting-dates-via-git-hooks.md

开头的字母表示已采取的操作,在上面的示例中,文件已被添加。修改过的文件带有 M。 我们将该输出通过管道传输给 grep 命令,在该命令中我们查看每一行以查找已被修改的内容。该行需要以 M (^(M)) 开头,后跟任意数量的字符 (.*),并以 .md 文件扩展名 (.(md)$) 结尾。这将过滤掉非修改过的 markdown 文件的行 egrep -i "^(M).*\.(md)$"


改进 - 更明确

可以将其添加为仅查找 blog 目录中的 markdown 文件,因为只有这些文件才会有正确的前言。

正则表达式将捕获两部分,即字母和文件路径。我们将把此列表通过管道传输到 while 循环中,以遍历匹配的行,并将字母分配给 a,将路径分配给 b。我们暂时忽略 a。 要知道文件的草稿状态,我们需要它的前言。在以下代码中,我们使用 cat 获取文件内容,然后使用 awk 根据前言分隔符(---)拆分文件并获取第二个块(即前言,位于 --- 之间的部分)。从这里我们再次使用 awk 查找 draft 键并打印其值。

filecontent=$(cat "$file")
frontmatter=$(echo "$filecontent" | awk -v RS='---' 'NR==2{print}')
draft=$(echo "$frontmatter" | awk '/^draft: /{print $2}')

现在我们有了 draft 的值,我们将执行以下 3 种操作之一:将 modDatetime 设置为现在(当 draft 为 false 时 if [ "$draft" = "false" ]; then),清除 modDatetime 并将 draft 设置为 false(当 draft 设置为 first 时 if [ "$draft" = "first" ]; then),或者什么都不做(在任何其他情况下)。 接下来的 sed 命令部分对我来说有点神奇,因为我不经常使用它,它是从另一篇关于做类似事情的博客文章中复制的。本质上,它在文件的前言标签(---)内查找 pubDatetime: 键,获取整行并将其替换为 pubDatetime: $(date -u "+%Y-%m-%dT%H:%M:%SZ")/",即相同的键和格式正确的当前日期时间。这种替换是针对整个文件的,因此我们将其放入临时文件(> tmp)中,然后我们将新文件移动(mv)到旧文件的位置,覆盖它。然后将其添加到 git 中,准备像我们自己进行更改一样提交。


注意

为了使 sed 正常工作,前言中需要已经包含 modDatetime 键。为了让应用在日期为空的情况下构建,你还需要进行一些其他更改,请参阅下文

为新文件添加日期

为新文件添加日期的过程与上述相同,但这次我们要查找已添加(A)的行,我们将替换 pubDatetime 值。

# 新文件,添加/更新 pubDatetime
git diff --cached --name-status | egrep -i "^(A).*\.(md)$" | while read a b; do
  cat $b | sed "/---.*/,/---.*/s/^pubDatetime:.*$/pubDatetime: $(date -u "+%Y-%m-%dT%H:%M:%SZ")/" > tmp
  mv tmp $b
  git add $b
done

改进 - 仅循环一次

我们可以使用 a 变量在循环内切换,在一个循环中更新 modDatetime 或添加 pubDatetime

填充前言

如果你的 IDE 支持代码片段,则可以选择创建自定义片段来填充前言。在 AstroPaper v4 中,默认将附带一个用于 VSCode 的片段。

## 空的 `modDatetime` 更改 为了让 Astro 编译 markdown 并执行其操作,它需要知道前言中预期有什么内容。它通过 `src/content/config.ts` 中的配置来实现这一点。 为了允许键存在但没有值,我们需要编辑第 10 行以添加 `.nullable()` 函数。 ```ts const blog = defineCollection({ type: "content", schema: ({ image }) => z.object({ author: z.string().default(SITE.author), pubDatetime: z.date(), modDatetime: z.date().optional(), // [!code --] modDatetime: z.date().optional().nullable(), // [!code ++] title: z.string(), featured: z.boolean().optional(), draft: z.boolean().optional(), tags: z.array(z.string()).default(["others"]), ogImage: image().or(z.string()).optional(), description: z.string(), canonicalURL: z.string().optional(), readingTime: z.string().optional(), }), }); ``` 为了阻止 IDE 在博客引擎文件中报错,我还执行了以下操作: 1. 在 `src/layouts/Layout.astro` 的第 15 行添加了 `| null`,使其如下所示 ```typescript export interface Props { title?: string; author?: string; description?: string; ogImage?: string; canonicalURL?: string; pubDatetime?: Date; modDatetime?: Date | null; } ``` 2. 在 `src/components/Datetime.tsx` 的第 5 行添加了 `| null`,使其如下所示 ```typescript interface DatetimesProps { pubDatetime: string | Date; modDatetime: string | Date | undefined | null; } ```

Share this post on:

Previous Post
AstroPaper 5.0
Next Post
AstroPaper 4.0