git init
git init is the command that turns an ordinary folder into a Git repository. It is almost always the very first command you run on a new project, and it is also the command you run to start tracking history in a folder that already has files in it. Understanding exactly what git init creates — and what it does not do — is the foundation for everything else you will learn about Git.
Overview / How it works
Every Git repository is really just a hidden directory named .git sitting alongside your project files. That directory is a self-contained database: it stores every version of every file you commit, the branches you create, and the configuration for that one repository. When you run git init inside a folder, Git creates this .git directory and nothing else — your existing files are left completely untouched and are not yet tracked.
Inside a freshly initialized .git directory you will find several important pieces:
.git/objects/— the object database. This is where Git will eventually store blobs (the raw content of files), trees (snapshots of a directory, mapping file names to blobs and sub-trees), and commits (a pointer to one tree, plus a commit message, author, timestamp, and a pointer to the parent commit or commits). Every object is identified by the SHA-1 hash of its content, so two files with identical content are stored only once, no matter how many commits or branches reference them..git/refs/— where branches and tags live. A branch, such asmain, is nothing more than a small text file containing the SHA-1 of the commit it currently points to. Branches are cheap and fast in Git precisely because creating one just writes a 41-byte pointer — it never copies files..git/HEAD— a pointer to the branch you currently have checked out. Right aftergit init,HEADis a symbolic reference likeref: refs/heads/main, even though themainbranch does not exist as a real ref yet — it only comes into existence once you make your first commit..git/config— settings that apply only to this repository (remotes, your name/email overrides, merge and rebase behavior, and so on). This is whatgit config --localedits, as opposed to--global(per-user) or--system(machine-wide) config..git/index— the staging area, sometimes called the “index” or “cache”. This file does not exist immediately aftergit init; it is created the first time you rungit add. The index is what sits between your working directory and your next commit — it holds a snapshot of exactly what will go into the next commit, which is whygit addandgit commitare two separate steps.
Because git init only creates this database and never modifies or deletes any of your project files, it is a very safe command to run. It is also idempotent: running git init again in a directory that is already a repository does not destroy your history — Git simply reports that it re-initialized the existing repository and leaves your commits, branches, and config alone.
Syntax
git init [options] [directory]
| Flag | Description |
|---|---|
| (no arguments) | Initializes a repository in the current directory. |
<directory> |
A path to create and initialize instead of the current directory. Git creates the directory if it does not already exist. |
-b <name>, --initial-branch=<name> |
Sets the name of the initial branch (what HEAD will point to) instead of relying on the default. Available since Git 2.28. |
--bare |
Creates a repository with no working directory — only the contents of what would normally be the .git folder, placed directly in the target directory. Used for repositories that only serve as a remote (a push/pull target), not a place to edit files. |
-q, --quiet |
Suppresses the “Initialized empty Git repository…” output, useful in scripts. |
--template=<dir> |
Copies files from a custom template directory into the new .git directory (for example, default hooks) instead of Git’s built-in templates. |
Since Git 2.28 you can also avoid depending on the -b flag every time by setting a global default branch name once:
git config --global init.defaultBranch main
Without this, older Git versions default new repositories to a branch named master, which is why explicitly naming it or configuring the default is worth doing on any machine you set up.
Examples
Example 1: Starting a brand-new project
mkdir hello-git
cd hello-git
git init
Output:
Initialized empty Git repository in /home/user/hello-git/.git/
This is the most common way to use git init: create an empty folder, move into it, and initialize it. At this point there are no commits and no branch yet — running git status would report “No commits yet” and list any files in the directory as untracked.
Example 2: Turning an existing project into a Git repository
You do not need to start from an empty folder. If you already have a project with files in it and just haven’t started tracking it with Git, run git init right where the files are:
cd my-existing-project
git init
git add .
git commit -m "chore: initial commit"
Output:
Initialized empty Git repository in /home/user/my-existing-project/.git/
[main (root-commit) 3f9a1c2] chore: initial commit
4 files changed, 128 insertions(+)
create mode 100644 README.md
create mode 100644 index.html
create mode 100644 src/app.js
create mode 100644 style.css
git init only creates the empty repository — it does not stage or commit anything on its own. The git add . stages every file in the directory into the index, and git commit takes that snapshot and creates the very first commit object, which is also when the main branch ref is actually written to .git/refs/heads/main for the first time.
Example 3: Setting the branch name explicitly and inspecting the result
git init --initial-branch=main project-api
cd project-api
ls -a
cat .git/HEAD
Output:
Initialized empty Git repository in /home/user/project-api/.git/
. .. .git
ref: refs/heads/main
Here git init is given a directory name as an argument, so Git creates project-api/ first and initializes the repository inside it — you don’t have to mkdir and cd separately. The --initial-branch=main flag guarantees the branch will be called main even on a machine where init.defaultBranch was never configured. Note that ls -a shows only the .git folder — no other project files exist yet — and .git/HEAD already contains a symbolic reference to refs/heads/main, even though that ref file doesn’t exist until the first commit.
How it works step by step
When you run plain git init, Git performs a small, well-defined sequence of steps:
- It checks whether a
.gitdirectory already exists in the target location. If one does, it reuses it (re-initializes) instead of overwriting your history. - It creates the
.gitdirectory and, inside it, the subdirectoriesobjects/,refs/heads/, andrefs/tags/. - It writes a
HEADfile containingref: refs/heads/<branch-name>, using either-b/--initial-branch, your configuredinit.defaultBranch, or Git’s built-in fallback name. - It writes a default
configfile (repository-local settings) and adescriptionfile (used by some old Git web tools). - It copies default hook scripts (currently disabled, with a
.samplesuffix) into.git/hooks/, such aspre-commit.sampleandcommit-msg.sample.
git init demo-repo
ls demo-repo/.git
Initialized empty Git repository in /home/user/demo-repo/.git/
HEAD config description hooks info objects refs
Nothing in your working directory changes, no object is written to objects/ yet (that happens on your first git add), and no branch ref exists yet in refs/heads/ (that happens on your first commit). git init only lays the empty scaffolding.
Common Mistakes
Mistake 1: Running git init inside an already-tracked project
cd my-project/src/components
git init
If my-project is already a Git repository, running git init again in a subdirectory creates a second, nested repository with its own .git folder. Git will then treat that subdirectory as an embedded repository, and files inside it can silently stop showing up in git status or git add for the parent repo, which is confusing and hard to notice until something goes missing from a commit. Before running git init, always check whether you’re already inside a repository:
git rev-parse --is-inside-work-tree
If that prints true, you’re already tracked and should not run git init again in a subfolder. If you genuinely need to nest one project inside another under version control, use git submodule instead of an accidental nested .git.
Mistake 2: Initializing in the wrong directory (like your home folder)
cd ~
git init
Running git init in your home directory, or any directory far broader than your intended project, turns every file underneath it into a potential candidate for tracking. It’s easy to later run git add . without realizing you’re standing several levels above your actual project, staging unrelated files, caches, or even other repositories. Always confirm your location with pwd before initializing, and prefer passing an explicit directory argument (git init my-project) so the target is unambiguous rather than relying on wherever your shell happens to be.
Mistake 3: Forgetting to set a consistent default branch name
Different machines and different Git versions can default a new repository’s first branch to master instead of main, especially on older installs. A team where some members’ Git defaults to master and others’ to main ends up with inconsistent branch names across otherwise-identical setups. Fix this once per machine with git config --global init.defaultBranch main, or make it explicit per-repository with git init -b main.
Best Practices
- Set
init.defaultBranchglobally once per machine so every new repository starts onmainwithout needing the-bflag each time. - Run
git rev-parse --is-inside-work-tree(or just check withgit status) before callinggit initif you’re unsure whether the current folder is already tracked. - Pass a directory argument (
git init my-app) instead ofmkdir && cd && git initwhen you want to create and initialize a new project folder in one step. - Add a
.gitignorefile right aftergit init, before your firstgit add, so build artifacts and dependency folders never get staged in the first place. - Follow up
git initwith a first commit quickly (even an emptyREADME.md) so themainbranch ref actually exists and collaborators have a stable point to branch from. - Use
--bareonly for repositories meant to act purely as a remote (for example, one you host yourself); never try to edit files directly inside a bare repository’s working area, because it has none.
Practice Exercises
- Exercise 1: Create a new folder called
notes-app, initialize it as a Git repository with the initial branch explicitly namedmain, and confirm the branch name by inspecting.git/HEAD. What does.git/HEADcontain before you make any commit? - Exercise 2: You have a folder called
legacy-scriptscontaining five files that have never been under version control. Initialize it as a Git repository, stage all the files, and make a single commit with a Conventional Commits-style message. Then checkgit logto confirm the commit exists. - Exercise 3: Run
git rev-parse --is-inside-work-treeinside a folder you’ve already initialized, and then inside one you haven’t. Explain, in your own words, why checking this before runninggit initagain would have prevented Mistake 1 above.
Summary
git initcreates a.gitdirectory that turns a plain folder into a Git repository; it never touches or commits your existing files.- The
.gitdirectory holds the object database (objects/), branch and tag pointers (refs/), the current branch pointer (HEAD), repository-local settings (config), and the staging index once you start adding files. - A branch is just a small file pointing at a commit’s SHA-1 hash; right after
git init,HEADpoints at a branch name that doesn’t exist as a ref yet, until your first commit creates it. - Use
-b/--initial-branchorgit config --global init.defaultBranch mainto make sure new repositories consistently start onmain. - Use
--bareonly for remote-only repositories with no working directory, and checkgit rev-parse --is-inside-work-treebefore re-runninggit initto avoid creating accidental nested repositories.
