Home / Alt manpages / git-sh-setup(1)

  • git-sh-setup(1)
  • User command
  • linux

Build Safer Git Shell Scripts with git-sh-setup

You will write a small POSIX shell script that uses Git's installed git-sh-setup scriptlet to find repository state, reject a bare repository when a working tree is needed, move to the top level and stop before operating on uncommitted changes. Allow about 15 minutes for a first smoke test. This is an authoring guide for Git shell scripts, not a command for normal interactive use.

1. Check the installed helper

git-sh-setup is sourced by another shell script. It is not intended to be executed as a standalone user command. The manual page on this machine belongs to Git 2.43.0 from the git-man package, version 1:2.43.0-1ubuntu7.3. Treat the exact functions and diagnostics below as the installed version's interface.

$ git --version
git version 2.43.0
$ dpkg-query -W -f='${Package} ${Version}\n' git-man
git-man 1:2.43.0-1ubuntu7.3
$ test -r "$(git --exec-path)/git-sh-setup" && echo readable
readable

Checkpoint: your script should source the helper through Git's own executable path, so it follows the Git installation selected by git:

. "$(git --exec-path)/git-sh-setup"

2. Set the usage text before sourcing

Set USAGE before the source line. The helper uses it when its usage() function needs to report the script's command syntax. Set LONG_USAGE as well when a longer explanation is useful. The source line also sets shell variables such as GIT_DIR and GIT_OBJECT_DIRECTORY; the manual says these are not exported to child processes.

#!/bin/sh
USAGE='[-n] <path>'
LONG_USAGE='Inspect a path in the current Git repository.'
. "$(git --exec-path)/git-sh-setup"

printf 'repository: %s\n' "$GIT_DIR"

Do not put untrusted text into USAGE or LONG_USAGE and then evaluate it. Keep these values fixed strings in the script. If your script needs to accept options, use Git's documented option parsing setup before relying on the helper's usage handling.

3. Require the repository shape you need

Call require_work_tree before commands that need files checked out. It fails outside a work tree and also fails for a bare repository. If you only need the repository metadata, use is_bare_repository and handle its output instead.

require_work_tree
printf 'working tree: %s\n' "$(git rev-parse --show-toplevel)"

For a repository whose work tree may be missing, require_work_tree_exists gives a separate check. It is commonly used before cd_to_toplevel, because changing directory is impossible when no working tree exists.

Checkpoint: test both cases without changing repository content:

$ printf 'bare='; is_bare_repository
bare=false
$ require_work_tree
$ printf 'usable work tree\n'
usable work tree

The exact failure text includes your script name. A non-zero exit status is the part a caller should rely on, not a copied diagnostic string.

4. Normalise the current directory

Use cd_to_toplevel when the rest of the script expects paths relative to the repository root. It runs Git's top-level lookup and changes the current shell's directory. Call require_work_tree first if a working tree is mandatory.

require_work_tree
cd_to_toplevel
printf 'root=%s\n' "$PWD"
git status --short

This changes only the script process's current directory. It does not change the caller's interactive shell. Relative paths in later commands now resolve from the repository root, which avoids a common error where a script works from one subdirectory but reads or writes the wrong path from another.

5. Refuse to run on uncommitted tracked changes

Call require_clean_work_tree before a scripted operation that rewrites files or branches. Pass the action as its first argument and an optional hint as its second. The helper checks for unstaged changes and staged changes to tracked files, then exits with status 1 if it finds either.

require_clean_work_tree update "Please commit or stash them."
printf 'safe to update tracked files\n'

A typical failure looks like this:

Cannot update: You have unstaged changes.
Please commit or stash them.

This check is a safety boundary, not a backup. Before calling it, decide how your script should treat untracked files, ignored files and submodules. The installed helper passes --ignore-submodules to its index and diff checks, so changes inside submodules are not what this function is reporting.

Warning

Do not "fix" a failed check by adding, resetting or stashing files automatically unless the script's user explicitly requested that state change. To recover, inspect git status --short, then either commit the intended work or stash it yourself. If the script itself made a temporary change, restore that change only when you can identify the exact path and previous contents.

6. Mark ref updates in the reflog

If the script runs Git commands that update refs, call set_reflog_action with the user-facing operation name. It sets and exports GIT_REFLOG_ACTION only when that variable is not already set, preserving an outer command's description.

set_reflog_action 'repo-maintenance'
git update-ref refs/heads/example HEAD

Do not leave a temporary custom reflog value in the environment after a nested operation. Use a subshell or save and restore the original value when the inner action needs its own label. Reflog messages help explain which scripted operation moved a reference, but they do not make an unsafe ref update reversible forever. Keep the normal Git backup and recovery plan for the repository.

7. Use the remaining helpers deliberately

die writes an error to standard error and exits. usage exits after displaying the usage text. git_editor selects an editor through GIT_EDITOR, core.editor, VISUAL or EDITOR; it can fail when no editor is configured and the terminal is dumb. get_author_ident_from_commit emits shell code for eval, so use it only with trusted commit data and review the quoting boundary carefully. create_virtual_base modifies its first file, leaving only lines shared with the second, so make copies or use disposable paths before calling it.

These helpers are implementation support for Git's own shell scripts. They do not replace input validation, quoting or a recovery plan. In particular, quote paths and user values, keep elevated privileges out of the script unless the operation genuinely needs them, and test ref-writing code in a disposable repository first.

Done means

  • The script sources the helper through $(git --exec-path) and defines its usage text first.
  • It rejects a bare repository before using working-tree paths.
  • It normalises paths with cd_to_toplevel where required.
  • It stops on staged or unstaged changes before a risky tracked-file operation.
  • It labels ref updates without overwriting an outer reflog action.
  • Any state-changing recovery, including commits, stashes or file replacement, is explicit and user-controlled.