Skip to content
Go back

Git Hooks를 사용하여 생성 및 수정 날짜 설정하기

Updated:  at  09:44 AM

이 글에서는 AstroPaper 블로그 테마의 프론트매터에서 생성(pubDatetime) 및 수정(modDatetime) 날짜를 자동으로 입력하기 위해 pre-commit Git hook을 사용하는 방법을 설명하겠습니다.

목차

모든 곳에서 사용하기

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 "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)$)로 끝나야 합니다. 이는 수정되지 않은 마크다운 파일이 아닌 줄을 필터링합니다 egrep -i "^(M).*\.(md)$".


개선 - 더 명시적으로

blog 디렉토리의 마크다운 파일만 찾도록 추가할 수 있습니다. 이는 올바른 프론트매터를 가진 유일한 파일들입니다.


정규식은 문자와 파일 경로 두 부분을 캡처합니다. 이 목록을 while 루프로 파이프하여 일치하는 줄을 반복하고 문자를 a에, 경로를 b에 할당합니다. 지금은 a를 무시할 것입니다.

파일의 draft 상태를 알기 위해서는 프론트매터가 필요합니다. 다음 코드에서는 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가 마크다운을 컴파일하고 작업을 수행하려면 프론트매터에서 예상되는 내용을 알아야 합니다. 이는 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().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 테마에서 새 글 작성하기