Git records content and history, while configuration determines how a local installation presents, authenticates, and transforms that content. Separating repository policy from per-developer convenience avoids surprising commits.
Inspect and Scope Configuration #
Git reads configuration from several scopes. Later scopes override earlier values for a scalar key:
| Scope | Typical Location | Intended Use |
|---|---|---|
| System | /etc/gitconfig | Installation-wide defaults |
| Global | ~/.gitconfig or ~/.config/git/config | Developer preferences |
| Local | .git/config | One repository |
| Worktree | .git/config.worktree when enabled | One linked worktree |
| Command | git -c name=value ... | One invocation |
git config --list --show-origin --show-scope shows the source of every
effective value. Query a key before changing it, because a value can be
inherited from a system or global configuration:
git config --show-origin --get-all user.email
git config --local pull.rebase false
git -c fetch.prune=true fetchConfiguration syntax resembles an INI file. Git accepts many values when a
file is written, then validates them when a command consumes the value.
Therefore, a successful git config core.eol invalid does not prove that the
value is usable. Prefer documented enum values and inspect a change with
git config --show-origin --get core.eol.
Put project policy in tracked files such as .gitattributes and
.gitignore, not in an untracked local configuration. A clone then receives
the policy without setup instructions.
Recover from Common Repository States #
Git keeps unreachable objects for a retention period, so recovery often starts with locating an object rather than accepting data loss. Make a safety branch before a destructive operation:
git branch backup/before-recoveryRecover a Dropped Stash That Contains Untracked Files #
git stash pop applies a stash and then drops its reference when the apply
succeeds. A former stash can remain reachable only through an unreachable
commit until pruning. Search without reflogs:
git fsck --no-reflogs --unreachable |
awk '/unreachable commit/ { print $3 }'Inspect each candidate before applying it:
git stash show --patch --include-untracked <object-id>
git stash apply <object-id>git stash apply accepts a stash-shaped commit object. Copy recovered files
to a safe location or commit them before further cleanup, because
git gc can prune unreachable objects after the expiry policy permits it.
Synchronize Deleted Remote Branches #
git fetch adds and advances remote-tracking references but does not remove a
local refs/remotes/origin/topic merely because the remote deleted topic.
Prune obsolete tracking references explicitly:
git fetch --prune origin
git remote prune origin
git config --global fetch.prune truePruning never deletes a local branch. Inspect branches whose upstream was removed with:
git branch -vvAbort a Conflicted git stash pop
#
A conflicted stash pop leaves conflict markers and does not drop the stash.
git merge --abort does not apply because stash pop is not a merge
operation with merge state. Either resolve the files and commit the result, or
discard the partially applied worktree state:
git reset --hard HEAD
git clean -fd # only after checking which untracked paths may be removedUse git clean -nd first. reset --hard does not remove untracked files,
while git clean -fd can do so.
Retrieve a File from Another Revision #
Read a historical path without changing the index:
git show <revision>:path/from/repository/rootRestore a path into the worktree and index only when that is intended:
git restore --source=<revision> --staged --worktree path/from/repository/rootUse Submodules as Pinned Dependencies #
A submodule entry in a tree has mode 160000 and stores a commit ID, not a
copy of the nested repository. .gitmodules records the path and suggested
URL; the superproject’s .git/config records local activation and URL
overrides.
Add a submodule and commit both the metadata and gitlink:
git submodule add https://example.com/library.git vendor/library
git commit -m "Add library submodule"Clone a project and initialize the exact commits recorded by the superproject:
git clone --recurse-submodules https://example.com/project.git
# Or, after a plain clone:
git submodule update --init --recursiveTo advance a dependency, first check out and commit the intended revision inside the submodule, then commit the changed gitlink in the superproject:
git -C vendor/library fetch --tags origin
git -C vendor/library switch --detach v2.4.0
git add vendor/library
git commit -m "Update library to v2.4.0"git submodule update --remote follows a configured branch and changes the
gitlink; it does not by itself record a reproducible update. Review and commit
the new gitlink afterward. Removing a submodule requires removing its
.gitmodules entry and gitlink, then deinitializing local metadata:
git submodule deinit -f vendor/library
git rm -f vendor/library
rm -rf .git/modules/vendor/libraryTreat a submodule URL as executable supply-chain configuration. Pinning a commit makes a build reproducible only while the referenced object remains available from a trusted remote or a preserved mirror.
Add Repository State to a Bash Prompt #
Git’s contrib/completion/git-prompt.sh defines __git_ps1. Source the copy
installed with Git, rather than copying a version from another installation:
if [[ -r /usr/share/git/completion/git-prompt.sh ]]; then
. /usr/share/git/completion/git-prompt.sh
GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=1
GIT_PS1_SHOWUPSTREAM=auto
path_color="\[$(tput setaf 2)\]"
git_color="\[$(tput setaf 207)\]"
reset_color="\[$(tput sgr0)\]"
PS1="${path_color}[\u] \w${git_color}"'$(__git_ps1 " <%s>")'"${path_color} \$ ${reset_color}"
unset path_color git_color reset_color
fiCommon installation paths include
/usr/share/git/completion/git-prompt.sh,
/usr/local/etc/bash_completion.d/git-prompt.sh, and
/opt/homebrew/etc/bash_completion.d/git-prompt.sh.
The \[ and \] markers tell Bash that terminal-control bytes have zero
display width. Omitting them breaks cursor placement and wrapping. *, +,
%, and $ in the prompt describe unstaged changes, staged changes,
untracked files, and stashed changes. Prompt-time status checks can be costly
in a large repository or a slow filesystem, so disable state variables when
latency matters.
Sign Commits and Tags with OpenPGP #
An OpenPGP signature proves that a holder of a private key signed a specific commit or annotated tag. It neither audits code nor encrypts repository content. Generate or select a signing key:
gpg --full-generate-key
gpg --list-secret-keys --keyid-format=long
gpg --fingerprint <long-key-id>Configure a fingerprint or long key ID, then enable signing where policy requires it:
git config --global user.signingkey <fingerprint>
git config --global gpg.format openpgp
git config --global commit.gpgsign true
git config --global tag.gpgSign trueTest the key and inspect a signed commit:
printf 'test\n' | gpg --clearsign
git commit --allow-empty -m "Verify commit signing"
git show --show-signature --no-patch HEAD
git verify-commit HEAD
git verify-tag v1.0.0On macOS, configure an installed pinentry-mac in ~/.gnupg/gpg-agent.conf
and restart the agent with gpgconf --kill gpg-agent. A terminal session can
also require export GPG_TTY="$(tty)". Upload only an ASCII-armored public key
to a hosting service. A verified-hosting badge also requires an account and
commit email association that the hosting service recognizes.
Choose a Pull-Request Merge Strategy #
Each strategy changes permanent ancestry, not merely the display of history.
| Strategy | Result on main | Use When | Local Git command equivalent |
|---|---|---|---|
| Merge commit | Preserves feature commits and adds a two-parent commit | Branch topology and individual commits are useful | git merge --no-ff feature |
| Squash and merge | Adds one new commit containing the complete change | A pull request is one logical change or contains fixups | git merge --squash feature && git commit |
| Rebase and merge | Replays each feature commit linearly with new IDs | Each commit is meaningful and a linear history is required | git rebase main feature && git switch main && git merge --ff-only feature |
| Fast-forward (not supported by Github PR) | Moves main to the feature tip without a merge commit | A strictly linear history is desired; GitHub’s PR UI does not offer this option | git merge --ff-only feature |
A merge commit preserves the original commits:
A---B---C---------M main
\ /
D---E---F featureSquashing gives main one new commit after C; D, E, and F are not
ancestors of that result. Delete the feature branch after a squash merge,
because reusing it produces confusing ancestry.
Rebase and merge produces D', E', and F' after C. The changed parent
of each replayed commit changes its ID and invalidates a signature on the
original commit. Clean unmerged branches with interactive rebase before
integration, and use --force-with-lease rather than --force when a
reviewed branch must be updated.
Comments
Read or comment on GitHub (sign-in required to comment)