Git .LastMod for JSON files

I’m using .Site.Data and {{ range }} to fetch and loop through data from JSON files in my data folder.

It works great.

My question is: For each JSON file, I’d like to display the timestamp of the file’s last commit.

I see Hugo makes this info available to templates using .LastMod when enabling enableGitInfo in config.

Is it possible at all to also fetch .LastMod for each JSON file, and if so, how? The files are versioned so Git would have the info.

As FYI, my loop:

        {{ range $i, $person := sort $.Site.Data.people "Name" }}
        <tr>
            {{ range $k, $prop := $.Page.Params.headers }}
                {{ if isset $person $prop }}
                <td>{{ index $person $prop }}</td>
                {{ end }}
            {{ end }}
        </tr>
        {{ end }}
    </tbody>```

.Lastmod is a .Page property. The data files are not pages. So we need to find another way.

Option 1: Get the modification time from the file system

This is not the same as the Git author date, but it’s easy to do.

{{ (os.Stat "data/a.json").ModTime }}

Option 2: Create a metadata file

Run a Bash script before each build that creates/updates a metadata file in the data directory.

Bash script
#!/usr/bin/env bash

main() {
  declare file_mod_date
  declare git_author_date
  declare key
  declare path
  declare paths
  declare metadata="data/metadata.toml"

  # Create/empty metadata file.
  true > "${metadata}"

  # Get paths to all data files, recursively.
  readarray -d '' paths < <(find data -type f -print0) || \
    { echo "Error: unable to create array of paths."; exit 1; }

  # Build TOML file.
  for path in "${paths[@]}"; do

    git_author_date=$(git log -1 --pretty="format:%cI" "${path}") || \
      { echo "Error: unable to obtain the Git author date."; exit 1; }

    file_mod_date=$(date -r "${path}" "+%Y-%m-%dT%H:%M:%S%:z") || \
      { echo "Error: unable to obtain the file mod date."; exit 1; }

    key=${path%.*}      # Remove extension
    key=${key/data\//}  # Remove the leading "data/"
    key=${key//\//.}    # Replace all / with .

    printf "[%s]\\n" "${key}" >> "${metadata}"
    if [[ ${#file_mod_date} -gt 0 ]]; then
      printf "file_mod_date = %s\\n" "${file_mod_date}" >> "${metadata}"
    fi
    if [[ ${#git_author_date} -gt 0 ]]; then
      printf "git_author_date = %s\\n" "${git_author_date}" >> "${metadata}"
    fi
    printf "path = \"%s\"\\n\\n" "${path}" >> "${metadata}"

  done
}

set -euo pipefail
main "$@"

For example, with this data structure:

data
├── sub/
│   └── c.json
├── a.json
└── b.json

the script creates this data/metadata.toml file:

[sub.c]
file_mod_date = 2021-05-12T18:58:21-07:00
git_author_date = 2021-05-13T00:00:12-07:00
path = "data/sub/c.json"

[b]
file_mod_date = 2021-05-12T17:45:30-07:00
git_author_date = 2021-05-13T00:00:12-07:00
path = "data/b.json"

[a]
file_mod_date = 2021-05-12T17:44:37-07:00
git_author_date = 2021-05-13T00:00:12-07:00
path = "data/a.json"

and you can get the Git author date with:

{{ site.Data.metadata.a.git_author_date }}
{{ site.Data.metadata.b.git_author_date }}
{{ site.Data.metadata.sub.c.git_author_date }}

or with the file modification time as a fallback:

{{ with site.Data.metadata.a.git_author_date }}
  {{ . }}
{{ else }}
  {{ site.Data.metadata.a.file_mod_date }}
{{ end }}
3 Likes

I did this once but it’s a bit of a hack.

The idea is to use Hugo modules to make the json file look like a regular page which will then have lastmod.

1 Like

Thank you for this incredibly thorough and detailed reply, I didn’t expect this! The bash script was spot on. Having never written one myself I really appreciated the sample you provided. Works perfectly.

1 Like

Thank you! I went with the bash script suggested by jmooring but this was an interesting approach.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.