skip to content

In Git, when is the repository directory not the .git folder beside your files?

level: seniorimportance: should knowfreq 32%

answer

  1. The pointer can be a file, not a folder
  2. One line, one key, a path
  3. Two features rely on this redirection
  4. Two environment variables can override discovery
  5. Ask Git where it landed rather than guessing

basics

~20 s

When .git is a file containing a gitdir: line — used by linked worktrees, submodules and git init --separate-git-dir — or when GIT_DIR and GIT_WORK_TREE, or the --git-dir and --work-tree options, point Git at a repository stored elsewhere.

solid answer

~40 s

Git decides on a repository directory per invocation, and it is not always `./.git`. If `.git` is a regular **file**, its single `gitdir: <path>` line redirects Git to the real repository directory: linked worktrees point into `.git/worktrees/<name>` of the main repository, submodule checkouts point into the superproject's `.git/modules/<name>`, and `git init --separate-git-dir=<path>` does the same for a normal repository. Independently, the `GIT_DIR` and `GIT_WORK_TREE` environment variables — or the equivalent `--git-dir` and `--work-tree` options that must precede the subcommand — override the discovery entirely, which is how people version a home directory from a repository stored outside it. Whatever the mechanism, `git rev-parse --git-dir` and `--show-toplevel` report where Git actually landed, and `--git-common-dir` reports the shared directory when worktrees are involved.

code

console · 8 lines
console
$ cat .git
gitdir: /home/ada/proj/.git/worktrees/hotfix
$ git rev-parse --git-dir
/home/ada/proj/.git/worktrees/hotfix
$ git rev-parse --git-common-dir
/home/ada/proj/.git
$ git rev-parse --show-toplevel
/home/ada/hotfix

go deeper

for a junior

Know that .git is usually a directory but can be a file containing a path, and that this is normal rather than a sign of corruption.

for a middle

Explain the gitdir: redirection and name what produces it — linked worktrees, submodules, and initialising with a separate repository directory.

for a senior

Show you can operate with it: overriding discovery with the git-dir and work-tree settings, diagnosing a checkout with no repository directory, and knowing which state a worktree keeps private.

for a principal

Own the tooling consequence — scripts, backups and build contexts that assume a .git directory will break on worktrees and submodules, so the assumption belongs in your standards, not in each script.

## Discovery, and how it is overridden By default Git walks up from the current directory looking for `.git`. What it finds may be a directory (the ordinary case) or a **file**, and either way the environment can override the whole search. ## Case 1: .git as a file A `.git` file contains exactly one line: `gitdir: <path to the real repository directory>` The path may be absolute or relative. Three common producers: - **Linked worktrees.** `git worktree add` creates a second checkout of the same repository in another directory. Its `.git` file points at `.git/worktrees/<name>` inside the main repository. That per-worktree directory holds the things that must be private to this checkout — its own `HEAD`, `index` and `ORIG_HEAD` — while a `commondir` entry redirects shared state (objects, most refs, config) back to the main repository. That is why every worktree sees the same commits and cannot check out the same branch twice. - **Submodules.** Modern Git stores a submodule's repository in the superproject under `.git/modules/<name>`, leaving a `.git` file in the submodule's working directory. Keeping the repository outside the submodule checkout is what lets the superproject switch to a commit where the submodule does not exist without destroying its history. - **`git init --separate-git-dir=<path>`.** The same redirection for a plain repository, used when the repository must live on different storage from the working tree. ## Case 2: environment and command-line overrides `GIT_DIR` names the repository directory and `GIT_WORK_TREE` names the working tree; `--git-dir` and `--work-tree` are the equivalent options and must appear **before** the subcommand, as in `git --git-dir=... --work-tree=... status`. Setting them decouples the two halves completely. The classic use is versioning configuration files in a home directory: keep a bare repository at, say, `/srv/dotfiles.git`, and run commands with the work tree set to `$HOME`. Nothing pollutes the home directory with a `.git` folder, and the repository can be excluded from ordinary scans and backups on its own terms. The related `core.worktree` config records a working tree location inside the repository's own config, which is what a `--separate-git-dir` setup uses. A sharp edge: `GIT_DIR` exported in a shell affects **every** Git command in that shell, including ones run from inside an unrelated repository. Hooks are also invoked with `GIT_DIR` set, which is why a hook that runs Git against another repository must clear or override these variables explicitly rather than assuming discovery will work. ## Finding out where you actually are Do not infer — ask: - `git rev-parse --git-dir` — the repository directory in use. - `git rev-parse --absolute-git-dir` — the same as an absolute path. - `git rev-parse --show-toplevel` — the working tree root. - `git rev-parse --git-common-dir` — the shared directory, which differs from `--git-dir` inside a linked worktree. - `git rev-parse --is-inside-work-tree` and `--is-bare-repository` — booleans for scripts. These are the reliable way for a script or a hook to orient itself, and they answer the confusing cases ("why does this checkout have no `.git` directory?") immediately. ## Why it matters operationally Moving a repository that uses redirection breaks absolute `gitdir:` paths; `git worktree repair` exists to fix worktree links after such a move. Tools that assume `./.git` is a directory — backup scripts, editors, container build contexts — will misbehave on worktrees and submodules. And a `.git` **file** copied without its target is inert: the checkout looks like a repository and is not one.

  • Inside a linked worktree, which state is private and which is shared with the main repository?
    Private state lives in `.git/worktrees/<name>` — its own HEAD, index and ORIG_HEAD — so each worktree can sit on a different commit. Objects, most refs and the repository config are shared through the `commondir` link, which is why all worktrees see the same history and Git refuses to check out one branch in two of them.
  • Why does a submodule's repository live under the superproject's .git/modules?
    So the submodule's history survives operations on the superproject. If the superproject checks out a commit where the submodule does not exist, the checkout directory can go away while the repository stays. The submodule's working directory keeps only a `.git` file pointing back at it.
  • What is the risk of exporting GIT_DIR in your shell?
    It applies to every Git command in that shell, so running Git inside an unrelated repository silently operates on the exported one. Hooks inherit it too, which is why a hook invoking Git elsewhere must clear or override `GIT_DIR` and `GIT_WORK_TREE` rather than relying on discovery.

It is a forwarding address: the envelope still says .git, but inside is a note telling Git the repository actually lives somewhere else.

saying these in an interview costs you the question

  • Assumes .git is always a directory
  • Thinks a .git file is a corrupted repository
  • Believes worktrees each have a full copy of objects
  • Exports GIT_DIR globally and forgets it is set
  • Moves a repository and expects gitdir paths to follow

context