Skip to content

ci: enforce consistent shell formatting with shfmt#514

Merged
sandr01d merged 1 commit intowfxr:mainfrom
sandr01d:shfmt
Apr 11, 2026
Merged

ci: enforce consistent shell formatting with shfmt#514
sandr01d merged 1 commit intowfxr:mainfrom
sandr01d:shfmt

Conversation

@sandr01d
Copy link
Copy Markdown
Collaborator

@sandr01d sandr01d commented Apr 9, 2026

Check list

  • I have performed a self-review of my code
  • I have commented my code in hard-to-understand areas
  • I have added unit tests for my code
  • I have made corresponding changes to the documentation

Description

Enforce a consistent shell script file using shfmt. This requires shfmt v3.13.1 to be able to correctly parse the zsh specific syntax in forgit.plugin.zsh. I kept the formatting as close as possible to what we currently have. Of course I am open for discussion should anyone want to have a different configuration.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Breaking change
  • Test
  • Documentation change
  • CI

Test environment

  • Shell
    • bash
    • zsh
    • fish
  • OS
    • Linux
    • Mac OS X
    • Windows
    • Others:

Summary by CodeRabbit

  • Chores

    • Added a CI formatting job and updated global editor formatting settings.
  • Documentation

    • Added a local shell formatting step to the contribution guide.
  • Style

    • Normalized shell/Bash/Zsh formatting across scripts, completions, plugin, and tests (quoting, spacing, indentation, and control-flow layout).

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d90cd608-74be-4246-967f-e3bdb88c5176

📥 Commits

Reviewing files that changed from the base of the PR and between 39378e8 and f84ea98.

📒 Files selected for processing (11)
  • .editorconfig
  • .github/workflows/ci.yaml
  • CONTRIBUTING.md
  • bin/git-forgit
  • completions/git-forgit.bash
  • forgit.plugin.zsh
  • tests/clean-select-files.test.sh
  • tests/empty-state.test.sh
  • tests/helper-functions.test.sh
  • tests/preview-context.test.sh
  • tests/working-tree-changes.test.sh
✅ Files skipped from review due to trivial changes (8)
  • CONTRIBUTING.md
  • tests/clean-select-files.test.sh
  • tests/empty-state.test.sh
  • .editorconfig
  • tests/helper-functions.test.sh
  • tests/working-tree-changes.test.sh
  • tests/preview-context.test.sh
  • completions/git-forgit.bash
🚧 Files skipped from review as they are similar to previous changes (3)
  • .github/workflows/ci.yaml
  • bin/git-forgit
  • forgit.plugin.zsh

📝 Walkthrough

Walkthrough

This PR adds editorconfig formatting settings, a GitHub Actions format job using shfmt, documents shfmt in CONTRIBUTING, and applies widespread shell whitespace/quoting/indentation normalizations across scripts, completions, plugin code, and tests. No public API signatures were changed.

Changes

Cohort / File(s) Summary
Configuration & CI
\.editorconfig, .github/workflows/ci.yaml, CONTRIBUTING.md
EditorConfig: set indent_size = 4, simplify = true, switch_case_indent = true. CI: added format job that installs shfmt v3.13.1 and runs shfmt --diff .. CONTRIBUTING: added shfmt --write . to local validation steps.
Main CLI script
bin/git-forgit
Extensive shell-style normalizations: unified redirections to &>/dev/null, expanded single-line conditionals into blocks, adjusted loop/parameter parsing spacing, and removed some unnecessary quoting in [[ ... ]] tests. No public signatures changed.
Completions & Shell plugin
completions/git-forgit.bash, forgit.plugin.zsh
Refactored function declaration formatting, tightened/removed some quotes in conditionals and arithmetic, condensed/simplified some guards, and expanded a few one-liners to explicit calls + return $?. Behavior preserved.
Tests
tests/*
tests/clean-select-files.test.sh, tests/empty-state.test.sh, tests/helper-functions.test.sh, tests/preview-context.test.sh, tests/working-tree-changes.test.sh
Whitespace, indentation, and minor redirection/array formatting tweaks across multiple tests; commands and assertions remain unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant GH as "GitHub Actions"
    participant Runner as "ubuntu-latest runner"
    participant Setup as "mfinelli/setup-shfmt@v4"
    participant Shfmt as "shfmt v3.13.1"
    participant Repo as "Repository"

    GH->>Runner: trigger "format" job
    Runner->>Repo: checkout repository
    Runner->>Setup: install shfmt
    Setup->>Shfmt: provide shfmt binary
    Runner->>Shfmt: run `shfmt --diff .`
    Shfmt-->>Runner: return formatting diffs
    Runner-->>GH: report job status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • wfxr
  • cjappl
  • carlfriedrich

Poem

🐰 I hopped through scripts with tidy paws,
I nudged the quotes and chased stray laws.
With shfmt in CI and rules to keep,
Tabs and braces now sleep deep.
A little hop for cleaner draws!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ci: enforce consistent shell formatting with shfmt' clearly and specifically summarizes the main change—adding CI enforcement for shell script formatting using shfmt.
Description check ✅ Passed The description includes a completed checklist, clear motivation for the change (shfmt v3.13.1 requirement for zsh syntax), and appropriate type of change selection (CI). Test environment is partially completed (zsh/Linux verified). The description adequately communicates the PR's purpose.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sandr01d sandr01d marked this pull request as ready for review April 9, 2026 18:03
@sandr01d sandr01d marked this pull request as draft April 9, 2026 18:04
@sandr01d sandr01d marked this pull request as ready for review April 9, 2026 18:08
@sandr01d sandr01d marked this pull request as draft April 9, 2026 18:09
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CONTRIBUTING.md (1)

57-66: ⚠️ Potential issue | 🟡 Minor

Use CI-equivalent shfmt check command in Local Validation.

This section says commands should match CI, but CI uses shfmt --list . while docs currently instruct shfmt --write ..

💡 Suggested documentation tweak
-shellcheck forgit.plugin.sh bin/git-forgit
-lib/bashunit .
-bash forgit.plugin.sh
-zsh forgit.plugin.zsh
-fish conf.d/forgit.plugin.fish
-shfmt --write .
+shellcheck forgit.plugin.sh bin/git-forgit
+lib/bashunit .
+bash forgit.plugin.sh
+zsh forgit.plugin.zsh
+fish conf.d/forgit.plugin.fish
+shfmt --list .
+# Optional auto-fix before rerunning checks:
+# shfmt --write .

Based on learnings: Align local validation with CI workflow (which runs on macOS and Ubuntu) before opening a PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CONTRIBUTING.md` around lines 57 - 66, Replace the local validation shfmt
command to match CI by changing the occurrence of "shfmt --write ." to "shfmt
--list ." in the CONTRIBUTING.md instructions; ensure the example command list
now shows "shfmt --list ." so local pre-PR checks mirror the CI workflow (which
uses shfmt --list).
🧹 Nitpick comments (2)
.editorconfig (1)

9-11: Scope the shfmt options to shell files instead of [*].

indent_size = 4 now becomes the default for every file type, even though this PR is only enforcing shell formatting. I’d move the shell-specific formatter knobs into shell/completion sections (plus bin/git-forgit) so unrelated files don’t inherit shell defaults.

♻️ Suggested config split
 [*]
 charset = utf-8
 end_of_line = lf
 indent_style = space
 insert_final_newline = true
 tab_width = 4
-indent_size = 4
-simplify = true
-switch_case_indent = true
 trim_trailing_whitespace = true
+
+[bin/git-forgit]
+indent_size = 4
+simplify = true
+switch_case_indent = true
+
+[*.{sh,bash,zsh,fish}]
+indent_size = 4
+simplify = true
+switch_case_indent = true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.editorconfig around lines 9 - 11, The global .editorconfig currently sets
shfmt options (indent_size, simplify, switch_case_indent) at the root which
applies to all files; move these keys into shell-specific sections so only shell
files get them—create/modify a [*.sh] (and add a specific section for
bin/git-forgit if it lacks a .sh extension, e.g., [bin/git-forgit]) and place
indent_size = 4, simplify = true, and switch_case_indent = true there so
unrelated files do not inherit shell formatting defaults.
tests/helper-functions.test.sh (1)

15-17: Use printf for exact fixture round-tripping.

This test is comparing raw parser output. echo -n / echo are shell-option dependent, so future cases like -nfoo or escape sequences can get distorted before the assertion runs.

♻️ Safer test harness
-    done < <(echo -n "$input" | _forgit_get_files_from_diff_line | xargs -0 -n 1 echo)
+    done < <(printf '%s' "$input" | _forgit_get_files_from_diff_line | xargs -0 -n 1 printf '%s\n')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/helper-functions.test.sh` around lines 15 - 17, The test uses echo -n
to feed raw fixture data into _forgit_get_files_from_diff_line which can mangle
inputs (leading -n, backslashes, etc.); replace the echo invocation with printf
'%s' "$input" so the exact bytes of the fixture are passed through for
_forgit_get_files_from_diff_line and subsequent xargs handling, ensuring exact
round-tripping in tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yaml:
- Around line 92-96: The workflow's Check format step uses an invalid list under
the with key for the mfinelli/setup-shfmt@v3 action; change the with input to a
key/value mapping (e.g., set shfmt-version: "3.13.1") and ensure the subsequent
run step remains as run: shfmt --list . so the action receives the correct
shfmt-version parameter.

In `@bin/git-forgit`:
- Line 177: The git invocation in bin/git-forgit uses git ls-files "$1"
--error-unmatch which treats filenames beginning with '-' as options; change the
command to pass the filename after the option terminator by invoking git
ls-files -- "$1" --error-unmatch (update the invocation where git ls-files is
called in the script) so paths like "-foo" are handled correctly.

---

Outside diff comments:
In `@CONTRIBUTING.md`:
- Around line 57-66: Replace the local validation shfmt command to match CI by
changing the occurrence of "shfmt --write ." to "shfmt --list ." in the
CONTRIBUTING.md instructions; ensure the example command list now shows "shfmt
--list ." so local pre-PR checks mirror the CI workflow (which uses shfmt
--list).

---

Nitpick comments:
In @.editorconfig:
- Around line 9-11: The global .editorconfig currently sets shfmt options
(indent_size, simplify, switch_case_indent) at the root which applies to all
files; move these keys into shell-specific sections so only shell files get
them—create/modify a [*.sh] (and add a specific section for bin/git-forgit if it
lacks a .sh extension, e.g., [bin/git-forgit]) and place indent_size = 4,
simplify = true, and switch_case_indent = true there so unrelated files do not
inherit shell formatting defaults.

In `@tests/helper-functions.test.sh`:
- Around line 15-17: The test uses echo -n to feed raw fixture data into
_forgit_get_files_from_diff_line which can mangle inputs (leading -n,
backslashes, etc.); replace the echo invocation with printf '%s' "$input" so the
exact bytes of the fixture are passed through for
_forgit_get_files_from_diff_line and subsequent xargs handling, ensuring exact
round-tripping in tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4cea9a1d-5fb2-4abe-946d-5a85c4380d1d

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6dc7c and 845a639.

📒 Files selected for processing (11)
  • .editorconfig
  • .github/workflows/ci.yaml
  • CONTRIBUTING.md
  • bin/git-forgit
  • completions/git-forgit.bash
  • forgit.plugin.zsh
  • tests/clean-select-files.test.sh
  • tests/empty-state.test.sh
  • tests/helper-functions.test.sh
  • tests/preview-context.test.sh
  • tests/working-tree-changes.test.sh

@sandr01d sandr01d force-pushed the shfmt branch 5 times, most recently from f5d35e1 to 9d01fe9 Compare April 9, 2026 18:27
@sandr01d sandr01d marked this pull request as ready for review April 9, 2026 18:39
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
bin/git-forgit (1)

176-178: ⚠️ Potential issue | 🟡 Minor

Pass the filename after -- to handle edge cases.

The argument order issue persists: a tracked file named -foo will be treated as an option. The -- separator should precede the filename.

🐛 Minimal fix
 _forgit_is_file_tracked() {
-    git ls-files "$1" --error-unmatch &>/dev/null
+    git ls-files --error-unmatch -- "$1" &>/dev/null
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/git-forgit` around lines 176 - 178, The helper _forgit_is_file_tracked
uses `git ls-files "$1" --error-unmatch` which treats filenames starting with
`-` as options; update the call inside _forgit_is_file_tracked to pass the
filename after the `--` separator (i.e., `git ls-files --error-unmatch --
"$1"`), so filenames like `-foo` are handled correctly and not parsed as
options.
🧹 Nitpick comments (1)
.github/workflows/ci.yaml (1)

82-91: Use shfmt --diff . for better CI feedback on formatting issues.

The format job configuration is correct. However, shfmt --list . only prints filenames that differ. Using shfmt --diff . (or -d) would additionally display the actual formatting changes, making it easier for contributors to understand what needs fixing without running shfmt locally.

💡 Suggested improvement
     - name: Check format
       uses: mfinelli/setup-shfmt@v4
       with:
         shfmt-version: 3.13.1
-    - run: shfmt --list .
+    - run: shfmt --diff .
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yaml around lines 82 - 91, Update the format job to use
shfmt's diff output so contributors see exact formatting changes: in the
"format" job where the step runs `shfmt --list .`, replace that invocation with
`shfmt --diff .` (or `shfmt -d .`) so the CI prints unified diffs; ensure the
step name ("Check format") and the mfinelli/setup-shfmt@v4 setup remain
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@bin/git-forgit`:
- Around line 176-178: The helper _forgit_is_file_tracked uses `git ls-files
"$1" --error-unmatch` which treats filenames starting with `-` as options;
update the call inside _forgit_is_file_tracked to pass the filename after the
`--` separator (i.e., `git ls-files --error-unmatch -- "$1"`), so filenames like
`-foo` are handled correctly and not parsed as options.

---

Nitpick comments:
In @.github/workflows/ci.yaml:
- Around line 82-91: Update the format job to use shfmt's diff output so
contributors see exact formatting changes: in the "format" job where the step
runs `shfmt --list .`, replace that invocation with `shfmt --diff .` (or `shfmt
-d .`) so the CI prints unified diffs; ensure the step name ("Check format") and
the mfinelli/setup-shfmt@v4 setup remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69fa0010-3a20-4f4b-aabf-36b149cb1c07

📥 Commits

Reviewing files that changed from the base of the PR and between 845a639 and 9d01fe9.

📒 Files selected for processing (11)
  • .editorconfig
  • .github/workflows/ci.yaml
  • CONTRIBUTING.md
  • bin/git-forgit
  • completions/git-forgit.bash
  • forgit.plugin.zsh
  • tests/clean-select-files.test.sh
  • tests/empty-state.test.sh
  • tests/helper-functions.test.sh
  • tests/preview-context.test.sh
  • tests/working-tree-changes.test.sh
✅ Files skipped from review due to trivial changes (9)
  • tests/empty-state.test.sh
  • CONTRIBUTING.md
  • .editorconfig
  • tests/helper-functions.test.sh
  • tests/clean-select-files.test.sh
  • completions/git-forgit.bash
  • tests/preview-context.test.sh
  • forgit.plugin.zsh
  • tests/working-tree-changes.test.sh

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bin/git-forgit (1)

110-129: ⚠️ Potential issue | 🟠 Major

Use shell escaping here instead of wrapping paths in single quotes.

files+=("'$1'") breaks as soon as a path contains '. That output is later interpolated into fzf execute/preview command strings, where unescaped quotes produce syntactically invalid shell commands.

Suggested fix
 _forgit_quote_files() {
-    local files add
+    local add escaped files
     files=()
     add=false
     while (("$#")); do
         case "$1" in
             --)
                 add=true
                 shift
                 ;;
             *)
-                if [ $add == true ]; then
-                    files+=("'$1'")
+                if [[ $add == true ]]; then
+                    printf -v escaped '%q' "$1"
+                    files+=("$escaped")
                 fi
                 shift
                 ;;
         esac
     done
     echo "${files[*]}"
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/git-forgit` around lines 110 - 129, _in_forgit_quote_files, file paths
are incorrectly wrapped with literal single quotes via files+=("'$1'"), which
breaks on paths containing single quotes; replace that naive quoting with proper
shell escaping (e.g. use printf '%q' on "$1" or otherwise escape single quotes
to '\'' before appending) so each entry in files is a safely escaped token,
update the append site (files+=(...)) accordingly and keep the final echo as
echo "${files[*]}" to emit the escaped tokens for later fzf command
interpolation.
♻️ Duplicate comments (1)
bin/git-forgit (1)

177-177: ⚠️ Potential issue | 🟡 Minor

Pass the path after -- in git ls-files.

A tracked file named like -foo is still treated as an option here, so this helper returns the wrong answer for exactly the edge case it is meant to handle.

Minimal fix
-    git ls-files "$1" --error-unmatch &>/dev/null
+    git ls-files --error-unmatch -- "$1" &>/dev/null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/git-forgit` at line 177, The git ls-files invocation treats filenames
beginning with '-' as options; update the call that currently reads git ls-files
"$1" --error-unmatch &>/dev/null to pass the path after the option terminator so
options are separated from the filename (i.e., use the -- separator and supply
"$1" after it), leaving the rest of the logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@bin/git-forgit`:
- Around line 110-129: _in_forgit_quote_files, file paths are incorrectly
wrapped with literal single quotes via files+=("'$1'"), which breaks on paths
containing single quotes; replace that naive quoting with proper shell escaping
(e.g. use printf '%q' on "$1" or otherwise escape single quotes to '\'' before
appending) so each entry in files is a safely escaped token, update the append
site (files+=(...)) accordingly and keep the final echo as echo "${files[*]}" to
emit the escaped tokens for later fzf command interpolation.

---

Duplicate comments:
In `@bin/git-forgit`:
- Line 177: The git ls-files invocation treats filenames beginning with '-' as
options; update the call that currently reads git ls-files "$1" --error-unmatch
&>/dev/null to pass the path after the option terminator so options are
separated from the filename (i.e., use the -- separator and supply "$1" after
it), leaving the rest of the logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b5c111c-79b4-4ddf-8378-f56ee94ebdc6

📥 Commits

Reviewing files that changed from the base of the PR and between 9d01fe9 and 39378e8.

📒 Files selected for processing (11)
  • .editorconfig
  • .github/workflows/ci.yaml
  • CONTRIBUTING.md
  • bin/git-forgit
  • completions/git-forgit.bash
  • forgit.plugin.zsh
  • tests/clean-select-files.test.sh
  • tests/empty-state.test.sh
  • tests/helper-functions.test.sh
  • tests/preview-context.test.sh
  • tests/working-tree-changes.test.sh
✅ Files skipped from review due to trivial changes (6)
  • tests/empty-state.test.sh
  • CONTRIBUTING.md
  • tests/clean-select-files.test.sh
  • tests/preview-context.test.sh
  • tests/working-tree-changes.test.sh
  • .editorconfig
🚧 Files skipped from review as they are similar to previous changes (3)
  • .github/workflows/ci.yaml
  • tests/helper-functions.test.sh
  • completions/git-forgit.bash

Copy link
Copy Markdown
Collaborator

@carlfriedrich carlfriedrich left a comment

Choose a reason for hiding this comment

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

@sandr01d Thanks a lot for your work! I had this on my wishlist for forgit for some time as well.

Copy link
Copy Markdown
Owner

@wfxr wfxr left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Collaborator

@cjappl cjappl left a comment

Choose a reason for hiding this comment

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

Cool!

@sandr01d sandr01d merged commit dfe682e into wfxr:main Apr 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants