Skip to content

如何使用 Git Hooks 設定創建和修改日期

Updated: at 06:59 PM

在這篇文章中,我將解釋如何使用 pre-commit Git hook 自動輸入 AstroPaper 博客主題前置資料中的創建 (pubDatetime) 和修改 (modDatetime) 日期。

目錄

在任何地方使用它們

Git hooks 非常適合自動化任務,例如將分支名稱添加到提交消息或檢查分支名稱,或阻止你提交純文本秘密。它們最大的缺點是客戶端鉤子是每台機器的。

你可以通過擁有一個 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 "這是我們的新 pre-commit 鉤子"'

導航到 hooks/pre-commit 文件,我們將添加以下一個或兩個片段。

編輯文件時更新修改日期


更新:

此部分已更新為更智能的鉤子版本。它現在不會在帖子發布之前增加 modDatetime。在首次發布時,將草稿狀態設置為 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 "首次發布 $file,草稿設置為 false 並刪除 modDateTime"
    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 獲取已經暫存的文件。輸出如下:

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 查找草稿鍵並打印其值。

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

現在我們有了 draft 的值,我們將做 3 件事之一,將 modDatetime 設置為現在(當草稿為 false 時 if [ "$draft" = "false" ]; then),清除 modDatetime 並將草稿設置為 false(當草稿設置為 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() 函數。

const blog = defineCollection({
  type: "content",
  schema: ({ image }) =>
    z.object({
      author: z.string().default(SITE.author),
      pubDatetime: z.date(),
-     modDatetime: z.date().optional(),
+     modDatetime: z.date().optional().nullable(),
      title: z.string(),
      featured: z.boolean().optional(),
      draft: z.boolean().optional(),
      tags: z.array(z.string()).default(["others"]),
      ogImage: image()
        .refine(img => img.width >= 1200 && img.height >= 630, {
          message: "OpenGraph 圖片必須至少為 1200 X 630 像素!",
        })
        .or(z.string())
        .optional(),
      description: z.string(),
      canonicalURL: z.string().optional(),
      readingTime: z.string().optional(),
    }),
});

為了防止 IDE 在博客引擎文件中報錯,我還做了以下更改:

  1. src/layouts/Layout.astro 的第 15 行添加 | null,使其看起來像這樣:
export interface Props {
  title?: string;
  author?: string;
  description?: string;
  ogImage?: string;
  canonicalURL?: string;
  pubDatetime?: Date;
  modDatetime?: Date | null;
}
  1. src/components/Datetime.tsx 的第 5 行添加 | null,使其看起來像這樣:
interface DatetimesProps {
  pubDatetime: string | Date;
  modDatetime: string | Date | undefined | null;
}

Previous Post
在 AstroPaper 部落格文章中添加 LaTeX 方程式
Next Post
如何在 AstroPaper 中新增社交圖標