-
-
Notifications
You must be signed in to change notification settings - Fork 600
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: adds an article about git hook date setting (#211)
* feat: adds post on git hook date setting * fix: make necessary adjustments and fix typo --------- Co-authored-by: Sat Naing <[email protected]>
- Loading branch information
Showing
1 changed file
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
--- | ||
author: Simon Smale | ||
pubDatetime: 2024-01-03T20:40:08Z | ||
modDatetime: | ||
title: How to use Git Hooks to set Created and Modified Dates | ||
featured: false | ||
draft: false | ||
tags: | ||
- docs | ||
- FAQ | ||
canonicalURL: https://smale.codes/posts/setting-dates-via-git-hooks/ | ||
description: How to use Git Hooks to set your Created and Modified Dates on AstroPaper | ||
--- | ||
|
||
In this post I will explain how to use the pre-commit Git hook to automate the input of the created (`pubDatetime`) and modified (`modDatetime`) in the AstroPaper blog theme frontmatter | ||
|
||
## Table of contents | ||
|
||
## Have them Everywhere | ||
|
||
[Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) are great for automating tasks like [adding](https://gist.github.com/SSmale/3b380e5bbed3233159fb7031451726ea) or [checking](https://itnext.io/using-git-hooks-to-enforce-branch-naming-policy-ffd81fa01e5e) the branch name to your commit messages or [stopping you committing plain text secrets](https://gist.github.com/SSmale/367deee757a9b2e119d241e120249000). Their biggest flaw is that client-side hooks are per machine. | ||
|
||
You can get around this by having a `hooks` directory and manually copy them to the `.git/hooks` directory or set up a symlink, but this all requires you to remember to set it up, and that is not something I am good at doing. | ||
|
||
As this project uses npm, we are able to make use of a package called [Husky](https://typicode.github.io/husky/) (this is already installed in AstroPaper) to automatically install the hooks for us. | ||
|
||
## The Hook | ||
|
||
As we want this hook to run as we commit the code to update the dates and then have that as part of our change we are going to use the `pre-commit` hook. This has already been set up by this AstroPaper project, but if it hadn't, you would run `npx husky add .husky/pre-commit 'echo "This is our new pre-commit hook"'`. | ||
|
||
Navigating to the `hooks/pre-commit` file, we are going to add one or both of the following snippets. | ||
|
||
### Updating the modified date when a file is edited | ||
|
||
```shell | ||
# Modified files, update the modDatetime | ||
git diff --cached --name-status | egrep -i "^(M).*\.(md)$" | while read a b; do | ||
cat $b | sed "/---.*/,/---.*/s/^modDatetime:.*$/modDatetime: $(date -u "+%Y-%m-%dT%H:%M:%SZ")/" > tmp | ||
mv tmp $b | ||
git add $b | ||
done | ||
``` | ||
|
||
`git diff --cached --name-status` gets the files from git that have been staged for committing. The output looks like: | ||
|
||
```shell | ||
A src/content/blog/setting-dates-via-git-hooks.md | ||
``` | ||
|
||
The letter at the start denotes what action has been taken, in the above example the file has been added. Modified files have `M` | ||
|
||
We pipe that output into the grep command where we are looking at each line to find that have been modified. The line needs to start with `M` (`^(M)`), have any number of characters after that (`.*`) and end with the `.md` file extension (`.(md)$`).This is going to filter out the lines that are not modified markdown files `egrep -i "^(M).*\.(md)$"`. | ||
|
||
--- | ||
|
||
#### Improvement - More Explicit | ||
|
||
This could be added to only look for files that we markdown files in the `blog` directory, as these are the only ones that will have the right frontmatter | ||
|
||
--- | ||
|
||
The regex will capture the two parts, the letter and the file path. We are going to pipe this list into a while loop to iterate over the matching lines and assign the letter to `a` and the path to `b`. We are going to ignore `a` for now. | ||
|
||
The next part with the sed command is a bit magical to me as I don't often use it, it was copied from [another blog post on doing something similar](https://mademistakes.com/notes/adding-last-modified-timestamps-with-git/). In essence, it is looking inside the frontmatter tags (`---`) of the file to find the `pubDatetime:` key, getting the full line and replacing it with the `pubDatetime: $(date -u "+%Y-%m-%dT%H:%M:%SZ")/"` same key again and the current datetime formatted correctly. | ||
|
||
This replacement is in the context of the whole file so we put that into a temporary file (`> tmp`), then we move (`mv`) the new file into the location of the old file, overwriting it. This is then added to git ready to be committed as if we made the change ourselves. | ||
|
||
--- | ||
|
||
#### NOTE | ||
|
||
For the `sed` to work the frontmatter needs to already have the `modDatetime` key in the frontmatter. There are some other changes you will need to make for the app to build with a blank date, see [further down](#empty-moddatetime-changes) | ||
|
||
--- | ||
|
||
### Adding the Date for new files | ||
|
||
Adding the date for a new file is the same process as above, but this time we are looking for lines that have been added (`A`) and we are going to replace the `pubDatetime` value. | ||
|
||
```shell | ||
# New files, add/update the 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 | ||
``` | ||
|
||
--- | ||
|
||
#### Improvement - Only Loop Once | ||
|
||
We could use the `a` variable to switch inside the loop and either update the `modDatetime` or add the `pubDatetime` in one loop. | ||
|
||
--- | ||
|
||
## Populating the frontmatter | ||
|
||
If your IDE supports snippets then there is the option to create a custom snippet to populate the frontmatter.[In AstroPaper v4 will come with one for VSCode by default.](https://github.com/satnaing/astro-paper/pull/206) | ||
|
||
<video autoplay muted="muted" controls plays-inline="true" class="border border-skin-line"> | ||
<source src="https://github.com/satnaing/astro-paper/assets/17761689/e13babbc-2d78-405d-8758-ca31915e41b0" type="video/mp4"> | ||
</video> | ||
|
||
## Empty `modDatetime` changes | ||
|
||
To allow Astro to compile the markdown and do its thing, it needs to know what is expected in the frontmatter. It does this via the config in `src/content/config.ts` | ||
|
||
To allow the key to be there with no value we need to edit line 10 to add the `.nullable()` function. | ||
|
||
```typescript | ||
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 image must be at least 1200 X 630 pixels!", | ||
}) | ||
.or(z.string()) | ||
.optional(), | ||
description: z.string(), | ||
canonicalURL: z.string().optional(), | ||
readingTime: z.string().optional(), | ||
}), | ||
}); | ||
``` | ||
|
||
To stop the IDE complaining in the blog engine files I have also done the following: | ||
|
||
1. added `| null` to line 15 in `src/layouts/Layout.astro` so that it looks like | ||
|
||
```typescript | ||
export interface Props { | ||
title?: string; | ||
author?: string; | ||
description?: string; | ||
ogImage?: string; | ||
canonicalURL?: string; | ||
pubDatetime?: Date; | ||
modDatetime?: Date | null; | ||
} | ||
``` | ||
|
||
1. added `| null` to line 5 in `src/components/Datetime.tsx` so that it looks like | ||
|
||
```typescript | ||
interface DatetimesProps { | ||
pubDatetime: string | Date; | ||
modDatetime: string | Date | undefined | null; | ||
} | ||
``` |