Processing Git changes in GitHub Actions
I decided to automate posting announcements to social media platforms when I publish a new blog post. Since I'm already using GitHub Actions to publish blog posts on schedule as well as deploy any changes to the site, I wanted to use the same for social media posting as well. The first step was finding out if a blog post is being published with a particular deploy and gathering some information about it.
I create blog posts as Markdown files in a specific folder in my Git repository. So, I first had to identify which files were added or modified in the current commit. After a short search, I found a perfect GitHub action for that: changed-files can easily do that and a lot more. The following configuration made it return all the changes to the Markdown files in the folder I was interested in:
- name: Get new posts
id: changed-posts
uses: tj-actions/changed-files@v45
with:
files: "src/documents/blog/posts/*.md"
I just had to set the correct fetch-depth
value on the Checkout action for this to work:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
I was interested in commits with only a single file added in that folder. And its file name had to match the current date, e.g., 20250207-MyBlogPost.html.md
. The added_files
and added_files_count
outputs made this really easy to check. This meant that all further steps related to my blog post announcement would require the following condition:
if: steps.changed-posts.outputs.added_files_count == 1 && contains(steps.changed-posts.outputs.added_files, format('/{0}', env.DATE))
Let me break it down:
- The
steps.changed-posts.outputs.added_files_count == 1
part should be obvious: it checks that exactly one Markdown file was added to the folder. In a previous step, I stored the current date in the correct format (
yyyymmdd
) to theDATE
environment variable. I used bash to format it correctly and stored the result in an environment variable by writing it to the$GITHUB_ENV
environment file:- name: Get current date run: echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV
Using built-in functions
format
andcontains
I prefixed the date with\
to ensure it occurs at the beginning of the file name and then checked that the resulting substring is present in the path of the added file.
To avoid repeating this same expression in multiple steps (and not risk those copies to diverge if I ever had to modify the condition), I stored the result of the expression to another environment variable:
- name: Check if new posts exist
run: echo "NEW_POST=${{ steps.changed-posts.outputs.added_files_count == 1 && contains(steps.changed-posts.outputs.added_files, format('/{0}', env.DATE)) }}" >> $GITHUB_ENV
In subsequent steps, I could now simply check the value of this environment variable instead:
if: ${{ env.NEW_POST == 'true' }}
To compose the blog post URL, I had to remove the .md
extension from the file path, as well as the first two folders. And then prefix the result with the base URL of my site:
- name: construct URL
if: ${{ env.NEW_POST == 'true' }}
run: echo "POST_URL=${BASE_URL}$(echo ${{steps.changed-posts.outputs.added_files}} | cut -c 15- | rev | cut -c 4- | rev)" >> $GITHUB_ENV
I'm not too happy with how I have achieved this but since there are no built-in functions for substring operations, I resorted to cut
and rev
bash commands.
I set the BASE_URL
environment variable beforehand at the job level:
env:
BASE_URL: https://www.damirscorner.com/
In addition to the URL, I also needed the blog post title and tags. I had to read those from the frontmatter in the Markdown file. I used the Markdown to Output Action to extract the frontmatter properties into corresponding individual outputs:
- name: Read frontmatter properties
id: post-frontmatter
if: ${{ env.NEW_POST == 'true' }}
uses: markpatterson27/markdown-to-output@v1
with:
filepath: ${{ steps.changed-posts.outputs.added_files }}
I tried using Markdown Meta action first which doesn't output the body of the file and therefore seems a better match for my needs, but I abandoned the idea because it emitted several warnings when ran. I didn't want to depend on a seemingly unmaintained action.
With that, I had all the information I needed to compose my social media message. In the process of getting this far, I put into practice several more general approaches to working with GitHub actions:
- When choosing custom actions to use, make sure that they are still maintained and that they don't have too many open issues.
- Use built-in expression functions when possible. Fall back to bash commands when you can't find a built-in function for your task.
- Store expression results in environment variables instead of copying the same expression all over the place.