Processing Git changes in GitHub Actions

February 7th 2025 GitHub

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 the DATE 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 and contains 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.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License