Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ per-file-ignores =
WPS202,
# WPS204: "Found overused expression" -- every test wires the same hermetic env/PATH sandbox and re-asserts "no download happened"; deduplicating that into helpers would hide what each test actually guarantees
WPS204,
tests/pytest/update_notification_test.py:
# WPS226: "Forbid the overuse of string literals" -- same legitimate test-fixture rationale as tool_version_test.py above
WPS226,
# WPS202: "Found too many module members" -- same one-scenario-per-test rationale as tool_version_test.py above
WPS202,
# WPS204: "Found overused expression" -- same hermetic env/PATH sandbox wiring rationale as tool_version_test.py above
WPS204,
# WPS210: "Found too many local variables" -- black-box hook subprocess tests inherently wire many pieces (dispatcher, sandbox PATH, cache dir, cache file, hook_run/combined); splitting further would hide the full env each assertion depends on
WPS210,
# WPS218: "Found too many assert statements" -- each assertion pins a distinct, independently-meaningful guarantee (message content, cache-file state, exit code); merging them would make failures less pinpointed
WPS218,
# WPS402: "Found noqa comments overuse" -- same subprocess.run(# noqa: S603) pattern as tool_version_test.py's tmp_repo fixture, just with a couple more scenario-specific git calls (rev-parse/tag) on top
WPS402,
# WPS118: "Found too long name" -- allow long test function names
WPS118,
# We will not spend time on fixing complexity in deprecated hook
src/pre_commit_terraform/terraform_docs_replace.py: WPS232

Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ jobs:
echo "files=$( echo "$DIFF" | xargs echo )" >> $GITHUB_OUTPUT

- name: Install shfmt
env:
# renovate: datasource=github-releases depName=shfmt lookupName=mvdan/sh
SHFMT_VERSION: 3.14.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why switch from latest? And why Hadolint install down the file hasn't received the same change? 🤔

Leaving concise explanatory comments helps others and future ourselves to figure out why something is one way rather than another.

run: >-
curl -L "$(
curl -s https://api.github.com/repos/mvdan/sh/releases/latest
| grep -o -E -m 1 "https://.+?linux_amd64"
)"
curl -L
https://github.com/mvdan/sh/releases/download/v${SHFMT_VERSION}/shfmt_v${SHFMT_VERSION}_linux_amd64
> shfmt
&& chmod +x shfmt && sudo mv shfmt /usr/bin/

Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ If you want to support the development of `pre-commit-terraform` and [many other
* [All hooks: Set env vars inside hook at runtime](#all-hooks-set-env-vars-inside-hook-at-runtime)
* [All hooks: Disable color output](#all-hooks-disable-color-output)
* [All hooks: Log levels](#all-hooks-log-levels)
* [All hooks: Check for a newer pre-commit-terraform release](#all-hooks-check-for-a-newer-pre-commit-terraform-release)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* [All hooks: Check for a newer pre-commit-terraform release](#all-hooks-check-for-a-newer-pre-commit-terraform-release)
* [All hooks: Check for a newer `pre-commit-terraform` release](#all-hooks-check-for-a-newer-pre-commit-terraform-release)

* [Most hooks: Pin a specific tool version](#most-hooks-pin-a-specific-tool-version)
* [Keeping pinned versions up-to-date using Renovate](#keeping-pinned-versions-up-to-date-using-renovate)
* [Many hooks: Parallelism](#many-hooks-parallelism)
Expand Down Expand Up @@ -228,7 +229,7 @@ Full list of dependencies and where they are used:
<!-- (Do not remove html tags here) -->
* [`pre-commit`](https://pre-commit.com/#install),
<sub><sup>[`terraform`](https://www.terraform.io/downloads.html) or [`opentofu`](https://opentofu.org/docs/intro/install/),
<sub><sup>[`git`](https://git-scm.com/downloads),
<sub><sup>[`git`](https://git-scm.com/downloads) 2.18+,
<sub><sup>[BASH `3.2.57` or newer](https://www.gnu.org/software/bash/#download),
<sub><sup>Internet connection (on first run),
<sub><sup>x86_64 or arm64 compatible operating system,
Expand Down Expand Up @@ -437,6 +438,29 @@ PCT_LOG=trace pre-commit run -a

Less verbose log levels will be implemented in [#562](https://github.com/antonbabenko/pre-commit-terraform/issues/562).

### All hooks: Check for a newer pre-commit-terraform release

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### All hooks: Check for a newer pre-commit-terraform release
### All hooks: Check for a newer `pre-commit-terraform` release

ps: perhaps check the anchor in the link in the table of contents above if you apply this commit suggestion.


> All, except deprecated hooks: `checkov`, `terraform_docs_replace`

To skip the check set one of:

* `CI=true` (most CI systems already export this automatically).
* `PCT_SKIP_UPDATE_CHECK=true` to disable it everywhere, including locally.
Comment on lines +447 to +448

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `CI=true` (most CI systems already export this automatically).
* `PCT_SKIP_UPDATE_CHECK=true` to disable it everywhere, including locally.
* `CI="true"` (most CI systems already export this automatically).
* `PCT_SKIP_UPDATE_CHECK="true"` to disable it everywhere, including locally.


```bash
# Skip the check for this run (or export it in CI)
PCT_SKIP_UPDATE_CHECK=true pre-commit run -a
```

How it works:

1. The check only runs when a hook is about to fail for its own reasons - a clean run stays completely silent, no matter how outdated your pin is.
2. On a failing run, it checks whether the `rev` pinned in your `.pre-commit-config.yaml`/`prek.toml` is behind the latest `pre-commit-terraform` release tag, at most once per invocation.
3. If you're behind, you'll see a one-line notice suggesting `pre-commit autoupdate --freeze` (or `prek update --freeze` if you use [prek](https://github.com/j178/prek))
4. The remote query itself - one read-only `git ls-remote` against this repo, no data about your code or repository sent anywhere - is rate-limited to once per 7 days. Within that window, a still-outdated pin keeps nagging on every failing run from the cached result, at no extra network cost.
5. The check never fails or meaningfully slows down your commit: the remote query is capped at 3 seconds, and if it can't reach GitHub (offline, firewalled CI runner, etc.) it prints a short notice and moves on - the hook's own exit code is unaffected either way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 3s timeout looks to me a bit short as it requires more or less good and stable networking. Would it make sense to bump it to at least 5s to cover systems with less reliable network though which still may benefit from this version check? 🤔

6. The last-checked timestamp and the upstream tag list from that check are cached as two files, `.last_update_check_time` and `.last_update_check_tags`, under the same cache root used for [pinned tool versions](#most-hooks-pin-a-specific-tool-version) (`PCT_TOOL_CACHE_DIR`, or `$XDG_CACHE_HOME`/`$HOME/.cache` + `pre-commit-terraform`) - see [Mount tools cache directory](#mount-tools-cache-directory) if you also want this to persist across Docker runs.

### Most hooks: Pin a specific tool version

> All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook.
Expand Down
205 changes: 205 additions & 0 deletions hooks/_check_new_version_on_failure.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#!/usr/bin/env bash

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the check and the notification only on failure? I perhaps missed this bit when this idea was brought up in the chat, though I was under impression that it's about notifying users that newer version exists in general. Honestly I can't see a reason behind presenting notification only when a random hook fails. Especially bounded to a periodical invocation 🤔 In such a case, I'd say that throwing "please check for a newer version which might have this internal hook failure already resolved" unconditionally would be more than sufficient and would eliminate a 200 lines of code with a complex logic 🤔

Also is this intentional to name the linked test Py file differently so its vaguely similar but does not follow the same naming pattern?

@MaxymVlasov MaxymVlasov Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the check and the notification only on failure?

Without verbose: true there will be no hook logs shown for Passed hooks, so there no sense to check them at all in such cases

Also is this intentional to name the linked test Py file differently so its vaguely similar but does not follow the same naming pattern?

No it's not. It was named after openspec doc used for this change. I'll rename it

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the check and the notification only on failure?

Without verbose: true there will be no hook logs shown for Passed hooks, so there no sense to check them at all in such cases

Ah, I see. Thanks for the pointer.
On the other side, I still think that it might make sense to hook notification to pre-commit-terraform run in general rather than just to failing runs.
So that people know they're lagging behind latest release even if they don't encounter any errors 🤔

set -eo pipefail

#######################################################################
# Kill a process and all of its descendants, children first, so none
# get orphaned mid-kill. `git ls-remote https://...` spawns a separate
# remote-helper child (`git remote-https`, confirmed via a real
# invocation) to do the actual network I/O - killing only the parent
# PID lets that helper survive and keep running after the hook itself
# returns.
# Arguments:
# pid (string) PID of the process (and its descendants) to kill
#######################################################################
function _pct_kill_process_tree {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the _pct suffix all of a sudden? I can't see its value unless we employ some third-party lib(s) and don't want any possible overlap. Also adding this suffix to only one single function looks odd.

Similarly I see no good reason for this prefix in tests/pytest/tool_version_test.py file. it's one out of 28 functions with this prefix there which — again — looks odd because it's one vs many.

My point is "it's none or all of them" please. Following consistent naming convention isn't mere verbiage in our IT domain I guess.

ps: similarly the underscore in the beginning of function names in this file — why is it? It's not anywhere else. And we already have sort of convention based on _common.sh and it looks a way neater I reckon. So if you think it's worth adding some common prefix to such helper functions, let's prepend e.g. aux:: string or similar.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the _pct suffix all of a sudden

Missed this one, thank you

ps: similarly the underscore in the beginning of function names in this file — why is it?

That's more Python convention to show "internal functions". But agree, it does not fit here. I'll just strip that stuff

local -r pid=$1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to check whether pid is not empty and is numeric and either skip silently if it's not or throw error or warning? 🤔
And maybe secure against threats or accidents where the pid value would appear to be 0, 1 or 2 (what are other important system pids?). Maybe even disallow pids lower than e.g. 100 or 1000 🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to check whether pid is not empty and is numeric and either skip silently if it's not or throw error or warning? 🤔

Is it even possible to run in such situation?

And maybe secure against threats or accidents where the pid value would appear to be 0, 1 or 2 (what are other important system pids?). Maybe even disallow pids lower than e.g. 100 or 1000 🤔

Need to double-check how docker will run it

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to check whether pid is not empty and is numeric and either skip silently if it's not or throw error or warning? 🤔

Is it even possible to run in such situation?

Depends on whether it's 100% guaranteed that the function is never triggered with incorrect input value. I'm just on that side of things where I prefer to validate any input to match what's expected and required =)

And maybe secure against threats or accidents where the pid value would appear to be 0, 1 or 2 (what are other important system pids?). Maybe even disallow pids lower than e.g. 100 or 1000 🤔

Need to double-check how docker will run it

Thank you.

local child
for child in $(pgrep -P "$pid" 2> /dev/null); do
_pct_kill_process_tree "$child"
done
kill -9 "$pid" 2> /dev/null || true
}

#######################################################################
# Check for newer pre-commit-terraform release and notify if outdated.
# The remote query is rate-limited to once per 7 days; within that
# window, an already-known-outdated pin still gets renagged every

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What renagged means? 🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May sense to reword it.
re-nagged
https://www.merriam-webster.com/dictionary/nagged

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks. I suspected it's to do "nag" again, though wasn't sure =) Maybe use "re-" (with hyphen) to denote it's not a straight "renag" word =)

# failing run, using cached tag data instead of a fresh query.
# Globals:
# CI (string) if set, skip entirely
# PCT_SKIP_UPDATE_CHECK (string) if set, skip entirely
# PCT_TOOL_CACHE_DIR (string) cache root location
# XDG_CACHE_HOME (string) fallback cache location
# HOME (string) fallback cache location
# Arguments:
# None
# Outputs:
# Prints a yellow notice if pinned revision is behind latest upstream tag,
# or if HEAD is untagged while a newer release exists. Prints nothing
# when already up-to-date or when check was skipped.
#######################################################################
function _check_new_version_on_failure {
if [[ -n ${CI:-} ]] || [[ -n ${PCT_SKIP_UPDATE_CHECK:-} ]]; then
return
fi
Comment on lines +42 to +44

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's Bash and we don't check for unbound vars (I also prefer minimized syntax rather than three line of code because of a single-word body of the code block):

Suggested change
if [[ -n ${CI:-} ]] || [[ -n ${PCT_SKIP_UPDATE_CHECK:-} ]]; then
return
fi
[[ $CI || $PCT_SKIP_UPDATE_CHECK ]] && return


local -r cache_root="${PCT_TOOL_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/pre-commit-terraform}"
# Holds only the last-checked timestamp
local -r time_cache_file="$cache_root/.last_update_check_time"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be personal preference, though I think it's worth to name the file according to its function for the sake of clarity and simplicity:

Suggested change
local -r time_cache_file="$cache_root/.last_update_check_time"
local -r last_update_check_time_file="$cache_root/.last_update_check_time"

# Hold the tag list, one "<commit-sha><TAB>refs/tags/<name>" line per
# tag - normalized from the raw `git ls-remote --tags` output (see
# below), not that raw output verbatim
local -r tags_cache_file="$cache_root/.last_update_check_tags"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, however I'm not pushing but only suggesting:

Suggested change
local -r tags_cache_file="$cache_root/.last_update_check_tags"
local -r last_update_check_tags_file="$cache_root/.last_update_check_tags"

# HEAD of *this* hook's own checkout - the pinned `rev` - not of the
# user's project repo, which is this function's actual CWD.
local -r hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sometimes stop by this common expression to identify function's file dir name and think why such a complex expression rather than a simpler "$(dirname "${BASH_SOURCE[0]}")" one? What are the reasons behind changing the directory and dereferencing symlinks at least in our use case? Any ideas? 🤔

local current_sha
current_sha=$(git -C "$hooks_dir" rev-parse HEAD 2> /dev/null) || current_sha=""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Might it be necessary to override any user-defined functions and aliases for git keyword? Just in case:
Suggested change
current_sha=$(git -C "$hooks_dir" rev-parse HEAD 2> /dev/null) || current_sha=""
current_sha=$(\git -C "$hooks_dir" rev-parse HEAD 2> /dev/null) || current_sha=""
  • While overwriting value of current_sha variable if the command fails looks elegant, do you think GIT CLI would output something to stdout if it fails? 🤔 (asking just out of interest so that this double reassurance doesn't look like overengineering)

#
# Try to get tags from valid cache when possible

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Try to get tags from valid cache when possible
# Try to get tags from a valid cache whenever possible

#
local known_tags=""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 lines above the expression is local var_name while here it is local var_name="". Such randomness in defining vars looks odd. And I also think that assigning empty value in this particular case is redundant as this var seems to haven't been used earlier in this function (it's btw why local vars are usually declared in the very beginning of the function definition and then assigned later as necessary).

local cache_is_fresh=false

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean to assign string false (or true), it's best to wrap it in double quotes (at least to eliminate confusion with false and true shell builtins or system binaries):

Suggested change
local cache_is_fresh=false
local cache_is_fresh="false"

if [[ -f $time_cache_file ]]; then
local cached_time
cached_time=$(< "$time_cache_file")
# A torn/partial write can leave a malformed or empty timestamp, so validate first
if [[ $cached_time =~ ^[0-9]+$ ]]; then
local -r age_seconds=$(($(date +%s) - cached_time))
# Negative age = a bogus future-dated timestamp (e.g. clock skew
# or the same corruption above) - never treat that as fresh.
if [[ $age_seconds -ge 0 ]] && [[ $age_seconds -lt 604800 ]]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [[ $age_seconds -ge 0 ]] && [[ $age_seconds -lt 604800 ]]; then
if [[ $age_seconds -ge 0 && $age_seconds -lt 604800 ]]; then

I'd also suggest to convert 604800 to a variable defined at the top of this function body. For simplicity and better visibility.

cache_is_fresh=true

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cache_is_fresh=true
cache_is_fresh="true"

[[ -f $tags_cache_file ]] && known_tags=$(< "$tags_cache_file")
fi
fi
fi

if [[ $cache_is_fresh == true && -z $known_tags ]]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [[ $cache_is_fresh == true && -z $known_tags ]]; then
if [[ $cache_is_fresh == "true" && ! $known_tags ]]; then

# Rate-limited, and no tag data has ever been cached (e.g. every
# attempt this week has failed) - nothing to compare against, so
# stay silent rather than nagging off no data. Only the timestamp,
# not the tag list, is what the 7-day window actually gates - a
# second failing run minutes after the first must not re-query
# just because no tags happen to exist yet.
return
fi
#
# No/stale cache, need to go to network
#
if [[ $cache_is_fresh == false ]]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [[ $cache_is_fresh == false ]]; then
if [[ $cache_is_fresh == "false" ]]; then

# `timeout` isn't guaranteed on every platform (e.g. stock macOS
# without GNU coreutils), so the 3s bound is enforced by hand: run
# `git ls-remote` in the background, race it against a `sleep 3`
# watchdog, and kill whichever loses. Output goes to a temp file
# since a backgrounded command can't be captured with `$(...)`.
local fresh_output
local tmp_output
tmp_output=$(mktemp)
git ls-remote --tags --sort=version:refname https://github.com/antonbabenko/pre-commit-terraform > "$tmp_output" 2>&1 &
Comment thread
MaxymVlasov marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given our recent chat around GIT CLI version and older Ubuntu versions, what do you think if just curl and jq directly?
The command would be similar to this:

curl -sSL 'https://api.github.com/repos/antonbabenko/pre-commit-terraform/tags?per_page=1' | jq -r '.[].name'

Also in modern Bash the > "$tmp_output" 2>&1 would be &> "$tmp_output". JFYI.

local git_pid=$!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to make the value be of integer type? (here and down the code)

Suggested change
local git_pid=$!
local -i git_pid=$!

(
sleep 3
_pct_kill_process_tree "$git_pid"
# Redirected above: if `sleep`'s own child process outlives the
# `kill` sent to this subshell below (SIGTERM to a foreground
# `sleep` orphans it rather than propagating), an inherited copy
# of the caller's stdout/stderr pipe would otherwise stay open -
# and callers reading that pipe until EOF (e.g. Python's
# `subprocess.communicate`) would block for the orphan's full
# remaining sleep, not just until `git` actually finishes.
) > /dev/null 2>&1 &

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) > /dev/null 2>&1 &
) &> /dev/null &

local watchdog_pid=$!

local exit_code=0
Comment on lines +112 to +114

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local watchdog_pid=$!
local exit_code=0
local -i watchdog_pid=$!
local -i exit_code=0

wait "$git_pid" 2> /dev/null || exit_code=$?
kill "$watchdog_pid" 2> /dev/null || true
wait "$watchdog_pid" 2> /dev/null || true

fresh_output=$(< "$tmp_output")
rm -f "$tmp_output"

if [[ $exit_code -eq 0 ]]; then
# Without `--refs`, `git ls-remote` yields *two* lines for an
# annotated tag: its own ref (sha = the tag *object*, never a
# commit) and a peeled "<ref>^{}" line (sha = the commit it
# actually points at). Only the peeled sha can ever match
# `current_sha`, so collapse each pair to one line, preferring
# the peeled sha whenever a tag has one; lightweight tags (single
# line, already a commit sha) pass through unchanged.
known_tags=$(awk -v OFS='\t' '
{
if (prev_ref != "" && $2 == prev_ref "^{}") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the clarity's sake:

Suggested change
if (prev_ref != "" && $2 == prev_ref "^{}") {
if (prev_ref != "" && $2 == sprintf("%s%s", prev_ref. "^{}")) {

print $1, prev_ref
prev_ref = ""
next
}
if (prev_ref != "") print prev_sha, prev_ref
prev_sha = $1
prev_ref = $2
}
END {
if (prev_ref != "") print prev_sha, prev_ref
}
' <<< "$fresh_output")
mkdir -p "$cache_root"
date +%s > "$time_cache_file"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JFYI: Bash has builtin capability to print dates

Suggested change
date +%s > "$time_cache_file"
printf "%(%s)T" > "$time_cache_file"

ps: use printf "%(%s)T\n" if newline char is necessary.

echo "$known_tags" > "$tags_cache_file"
else
# 137 = 128 + SIGKILL(9) - the watchdog fired.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't take is as a nitpick, though you don't usually use trailing period char in comments =)

Suggested change
# 137 = 128 + SIGKILL(9) - the watchdog fired.
# 137 = 128 + SIGKILL(9) - the watchdog fired

if [[ $exit_code -eq 137 ]]; then
Comment thread
coderabbitai[bot] marked this conversation as resolved.
common::colorify "yellow" "Update check timed out."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this is not "Update" check. It's "New version check".
Maybe something like this? 🤔

Suggested change
common::colorify "yellow" "Update check timed out."
common::colorify "yellow" "Check for new version of \"pre-commit-terraform\" timed out."

else
common::colorify "yellow" "Update check failed (exit ${exit_code})."
fi
common::colorify "yellow" "Will try again in a week." \
"Set CI=true or PCT_SKIP_UPDATE_CHECK=true to never check for updates."

# Skip update check for a week
mkdir -p "$cache_root"
date +%s > "$time_cache_file"
return
fi
fi
#
# Valid cache
#
local latest_tag_name
latest_tag_name=$(echo "$known_tags" | tail -n1 | awk '{print $2}' | sed 's|^refs/tags/||')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awk can do all three of tail, awk and sed like this:

Suggested change
latest_tag_name=$(echo "$known_tags" | tail -n1 | awk '{print $2}' | sed 's|^refs/tags/||')
latest_tag_name=$(awk 'END{sub("^refs/tags/", "", $2); print $2}' <(echo "$known_tags"))

Or otherwise please use cut instead of awk as using cut fits best to split fields.


# Find which tag, if any, matches current HEAD
local current_tag=""
while IFS=$'\t' read -r sha tag; do
if [[ $sha == "$current_sha" ]]; then
current_tag=${tag#refs/tags/}
break
fi
done <<< "$known_tags"

if [[ $latest_tag_name == "$current_tag" ]]; then
# Already up-to-date, silent
return
elif [[ -n $current_tag ]]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif [[ -n $current_tag ]]; then
elif [[ $current_tag ]]; then

common::colorify "yellow" "pre-commit-terraform ${current_tag} is outdated; latest is ${latest_tag_name}."
else
common::colorify "yellow" "pre-commit-terraform pinned to a non-release commit; latest release is ${latest_tag_name}."
fi
common::colorify "yellow" 'Run "pre-commit autoupdate --freeze" (or "prek update --freeze") to upgrade.'
}

# Check for update only on hooks failure. `errexit` is disabled around
# the call and the original pending status is captured/re-exited
# explicitly - otherwise any unguarded failure inside the checker
# itself (e.g. an unwritable cache dir) would, under `set -e`, replace
# the hook's real exit code with the checker's own failure instead of
# just being a best-effort, non-fatal notice.
# shellcheck disable=SC2154 # False positive: assigned inside the trap string itself
trap '
_pct_update_check_exit_code=$?
if [[ $_pct_update_check_exit_code -ne 0 ]]; then
set +e
_check_new_version_on_failure
set -e
fi
exit $_pct_update_check_exit_code
' EXIT
6 changes: 4 additions & 2 deletions hooks/_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ HOOK_ID=${0##*/}
readonly HOOK_ID=${HOOK_ID%%.*}

#######################################################################
# Init arguments parser
# Initialize common functions and environment for hooks
# Arguments:
# script_dir - absolute path to hook dir location
#######################################################################
function common::initialize {
local -r script_dir=$1
# source getopt function
# Init arguments parser (getopt function)
# shellcheck source=../lib_getopt
. "$script_dir/../lib_getopt"
# Initialize update check on failure
. "$script_dir/_check_new_version_on_failure.sh"
}

#######################################################################
Expand Down
8 changes: 8 additions & 0 deletions tests/pytest/tool_version_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,10 @@ class _HookWiring(NamedTuple):
'head',
'mkdir',
'mktemp',
'pgrep',
'rm',
'sed',
'sleep',
'sort',
'tail',
'tr',
Expand Down Expand Up @@ -268,6 +270,12 @@ def _hook_env( # pragma: win32 no cover
# Read directly by `tools/install/_common.sh`. Forwarded as an
# empty string when absent, which that script treats as unset.
'GITHUB_TOKEN': os.environ.get('GITHUB_TOKEN', ''),
# This suite is about tool-version resolution, not the update
# notification: without this, every hook invocation below would
# also attempt a real `git ls-remote` against GitHub, adding
# network flakiness/latency here and polluting the `cache_dir`
# fixture with unrelated `.last_update_check_*` files.
'PCT_SKIP_UPDATE_CHECK': 'true',
**cache_env,
}

Expand Down
Loading
Loading