Get a file’s modified time through Git

As the name suggests, the modified time of a file is updated whenever the file is changed. Although regular modified times work most of the time, some situations require a more cautious approach.

For example, when using modified times as the last updated date for a published article, the date should only update if the file received substantive changes rather than structural ones. In those cases, an update should be a deliberate action.

Git repositories provide records of deliberate updates through commit logs. By retrieving the last commit date for a specific file, we can find the last date a file was purposefully updated.

Given a repository with two commits:

git log
commit 6d9fde3f6e4a9ccd8ef720bf7f95e0f9cb74b482
Author: Bob <bob@example.com>
Date:   Thu Mar 2 09:26:56 2023 +0100

    Update file.txt
    
    [date skip]

commit 4ec5e2c29eb03c082986227609310db886d55605
Author: Alice <alice@example.com>
Date:   Thu Mar 2 09:09:56 2023 +0100

    Add file.txt

We’ll use git log with the following options:

--format=%ad
Format the commit message to only show the author date
--date=format:"%F %R"
Format the returned date with %F %R as the strftime format, which shows a full date and time:
-1
Only return the last commit
git log -1 \
    --format=%ad \
    --date=format:"%F %R" \
    file.txt
2023-03-02 09:26

In order to allow commits that update files without updating their returned modified times, filter the commits out that match a specific flag in their messages, like [skip date]:

git log -1 \
    --format=%ad \
    --date=format:"%F %R" \
    --grep="\[date skip\]" \
    --invert-grep file.txt
2023-03-02 09:09

However, this implementation doesn’t work when the commits aren’t in chronological order. In this example, another commit is added with a date before the existing ones:

git log
commit f50c19f9c06499c5ca400729c4c5a0f858128b95
Author: Alice <alice@example.com>
Date:   Thu Mar 2 06:18:11 2023 +0100

    Update file.txt again

commit 6d9fde3f6e4a9ccd8ef720bf7f95e0f9cb74b482
Author: Bob <bob@example.com>
Date:   Thu Mar 2 09:26:56 2023 +0100

    Update file.txt
    
    [date skip]

commit 4ec5e2c29eb03c082986227609310db886d55605
Author: Alice <alice@example.com>
Date:   Thu Mar 2 09:09:56 2023 +0100

    Add file.txt

Because the new commit is the latest one, its commit time is returned:

git log -1 \
    --format=%ad \
    --date=format:"%F %R" \
    --grep="\[date skip\]" \
    --invert-grep file.txt
2023-03-02 06:18

To remedy this, we use a trick to get the commits in date order first. Then we’ll take the top one from the list and pass that to git show:

git show --quiet \
    --format=%ad \
    --date=format:"%F %R" \
    $(git log --format="%ad %H" \
          --date=format:%s \
          --grep="\[date skip\]"\
          --invert-grep |\
          sort --reverse |\
          awk '{ print $2 }' |\
          head -n1)
2023-03-02 09:09