Git

What Is a Git Repository? Everything You Should Know

What Is a Git Repository? Everything You Should Know

Git keeps a project’s full change history as commits in a content-addressed object database, and that database is what people call a repository. Git’s layout documentation puts it in a hidden .git directory at the root of the working tree. A bare repository uses a directory named project.git instead.

The files you edit belong to the working tree. Only the .git directory holds history, so treating the whole project folder as the repository blurs the line.

Plenty of developers never think about this, mostly because Git is everywhere. In the 2022 Stack Overflow Developer Survey, 93.87% of respondents named it their primary version control system, and the number rose to 96.65% among professional developers.

What Is a Git Repository?

YouTube player

Every copy of a repository carries the full history, so it works offline. You can commit and browse old versions on a train with no signal.

On disk, you get a hidden .git directory at the root of the project folder. A bare repository drops the working files and lives in a directory named project.git, per the Git gitrepository-layout documentation.

The folder you edit is the working tree. The repository is the history Git keeps beside it.

Linus Torvalds wrote Git in April 2005 for Linux kernel development, after BitKeeper revoked its free licence for the kernel project.

The design breaks from older version control tools such as Subversion, where one central server owns the history. Atlassian’s Git tutorial points out that Git makes no distinction between working copies and the central repository, because each one is a full repository.

Git itself is the tool that reads and writes the repository. The repository is the data, stored in .git. GitHub, GitLab and Bitbucket are hosting platforms that keep remote copies.

Beginners mix up the tool and the host constantly, so it helps to see how Git differs from GitHub. A repository needs no platform at all. Running git init in any folder creates one.

Why is GitHub the heart of open source?

Uncover GitHub statistics: developer community growth, repository trends, collaboration patterns, and the platform that powers modern software development.

Explore GitHub Data →

What Is Inside the .git Directory?

YouTube player

Open it and you will find the object database, refs, HEAD, the staging index, the repository config, hooks, logs and info files. Delete it and the project keeps its files but loses its entire history (GitKraken).

Most of the everyday work happens in a handful of entries, which the table below covers.

PathWhat it storesExample
objects/Every blob, tree, commit and tag, loose or packedobjects/pack
refs/Branch, tag and remote-tracking pointersrefs/heads/main
HEADThe current branch, or a commit ID when detachedref: refs/heads/main
indexThe staging area for the next commit.git/index
configRepository-specific settings such as remote URLs[remote “origin”] section

Hooks live in hooks/ and run on events such as a commit. A fresh repository fills that folder with .sample files.

The gitrepository-layout page also documents packed-refs, logs and info/refs.

Blob, tree, commit and tag objects

A blob is the bytes of one file, with no filename and no mode. A tree is a directory listing, and each entry holds a mode, a type, a hash and a filename. A commit points to the top-level tree, its parent commits, the author and the message.

Looking to sharpen your Git skills? Branching, merging, rebasing, and everything else you need - including git rebase, git stash, and commit workflows - is on one page in the Git Cheat Sheet.

Tags add a fourth object. An annotated tag is a named object that points at another object, usually a commit.

Because names live in trees, two identical files share one blob.

Pro Git and Ken Muse’s storage walkthrough agree on the consequence. Change one line in one file and Git writes a new blob, new trees up to the root, and a new commit. Nothing is edited in place.

How SHA-1 hashes identify objects

Each object sits under .git/objects in a path built from its SHA-1 checksum. The first 2 characters name the subdirectory and the remaining 38 name the file (Pro Git).

Put together, that makes a 40-character hexadecimal ID, the commit hash you see in git log. The hash covers the object type, its size and its content, so identical content always gets the same ID.

The Git project picked SHA-256 as the successor hash in late 2018. A repository opts in at creation with git init –object-format=sha256, available since Git 2.29 (October 2020).

The Git hash-function-transition documentation lists the costs. Older versions of Git cannot read a SHA-256 repository at all. Borrowing objects from a SHA-1 repository through objects/info/alternates doesn’t work either, and object names grow from 20 bytes to 32 bytes.

The same document strongly discourages SHA-256 storage on public-facing servers until the Git protocol gains SHA-256 support.

How Branches, HEAD and Tags Work in a Repository

YouTube player

A branch is a text file that holds one commit ID. HEAD is a file that says which branch you are on, and a tag is a fixed name for a commit.

Git stores these pointers as plain files under .git/refs (Atlassian, Pro Git), so creating a branch costs one small file. Local branches get a file each in refs/heads/, tags go in refs/tags/, and refs/remotes/ records the tips of branches copied from a remote repository.

Every commit on a Git branch moves that branch file forward. Git writes the new commit ID into refs/heads/ and leaves the old commit untouched.

HEAD normally holds a symbolic reference such as ref: refs/heads/main. When you commit, Git takes whatever commit HEAD resolves to as the parent of the new one (Pro Git).

The Git layout documentation goes further. A valid repository must have a HEAD file, and without it the directory is not a repository.

Checking out a tag, a commit or a remote branch puts a raw commit ID in HEAD. That state is a detached HEAD, and commits made there belong to no branch.

Switch away without creating a branch and those commits are reachable only through the reflog (HEAD@{n} entries), until garbage collection removes them.

Tags come in two weights. A lightweight tag is only a file in refs/tags. An annotated tag is a full tag object with a tagger, a date and a message, plus the ref that points to it.

The Working Tree, the Staging Area and the Repository

Git moves a file through the working tree, then the staging area (also called the index), then the repository. Pro Git ties these to the file states modified, staged and committed.

The working tree is just the files on disk that you edit, and Git sees each one as tracked or untracked.

The staging area is a binary file at .git/index that records the snapshot for the next commit.

The repository is the object database inside .git, where committed snapshots live. A clone copies this part, and the working tree is checked out from it.

The way staging works in Git lets a commit hold only part of your edits. With a dozen modified files, you stage three and leave the rest.

git add copies changes from the working tree into the index, and git commit writes the index into the repository as a new commit. git status lists each file by area. For changes, git diff shows the unstaged ones and git diff –staged shows what is already staged.

A file edited after git add appears in both diff outputs, because the index keeps the version you staged.

Untracked files stay in the working tree until you add them. git init adds nothing on its own, so the initial commit records only what you staged (Git Tower).

A .gitignore file keeps untracked files out of git status and git add. It does not untrack a file that an earlier commit already recorded.

How to Create a Git Repository

YouTube player

Run git init inside a folder to start a new repository, or git clone followed by a URL to copy an existing one. Pick init when the project lives only on your machine and clone when a remote already holds it.

Create a new repository with git init

  1. Open a terminal and move into the project folder with cd
  2. Run git init
  3. Run git status to see every file listed as untracked
  4. Stage the files you want with git add
  5. Record them with git commit -m “Initial commit”

The command writes a .git directory with objects, refs/heads, refs/tags and template files, plus an initial branch that holds no commits (git-init manual).

There is little more to what git init does, which is why running it again in an existing repository is safe. The manual lists picking up new templates as the main reason to rerun it.

The branch name is where people get caught. The current git-init manual page still names master as the default initial branch and says it will change to main when Git 3.0 is released. Git 3.0 had not shipped as of mid-2026, when Git 2.55 was the latest stable release. Set init.defaultBranch once and forget about it, or pass -b main for a single repository.

git init -b is ignored in a repository that already exists. Rename the branch there with git branch -m master main.

Copy an existing repository with git clone

git clone creates a new directory, copies the repository into it, and checks out the branch the source’s HEAD points to. You end up with a remote named origin that points at the source URL, remote-tracking branches under refs/remotes/origin, and a working tree with the default branch checked out.

The git clone command also takes options that change the result.

–depth creates a shallow clone, with history truncated to the number of commits you give it.

–origin names the remote something other than origin, and the clone.defaultRemoteName setting does the same for every clone.

–branch points the new HEAD at a branch you choose and checks that branch out in a non-bare clone.

Choosing between init and clone

Start with git init when the code exists only in a local folder, or when you plan to connect a host later. Start with git clone when a remote already holds the project and you want its history and branches without extra setup.

An existing folder of files needs git init, not clone. There is no remote to copy yet.

Local, Remote and Bare Repositories Compared

YouTube player

A local repository sits on your machine with a working tree. A remote repository sits on another machine or host, and a bare repository is any repository stored without a working tree.

TypeWorking treeTypical use
Non-bare (local)YesEditing, committing and merging on your machine
BareNoShared hub that receives pushes
RemoteUsually noneCentral copy on a server or hosting platform

Bare repositories

A bare repository holds only Git data. You cannot edit files in it, and it accepts git push and git fetch (GeeksforGeeks).

Two commands create one. git init –bare makes the current directory the repository itself, with no .git subfolder, and git clone –bare copies an existing repository into the same shape.

The git-clone manual’s own example turns a local /home/proj/.git into a bare copy at /pub/scm/proj.git. A bare repository suits a shared hub because nobody checks out files there.

The –bare option maps the source’s local branches to local branches in the copy. –mirror goes much further and maps every ref, including remote-tracking branches and notes, then sets a refspec so git remote update overwrites all of them.

Use –mirror when the copy must track every ref of the source. Use –bare for a hub people push to.

Remote repositories

A remote repository is any other copy that yours exchanges commits with. GitHub Docs defines it as a repository stored on GitHub rather than on your computer.

The address does not have to be a web host. The git-clone documentation and Atlassian’s tutorial both show SSH URLs and plain filesystem paths as valid sources.

How a Repository Connects to GitHub, GitLab and Bitbucket

YouTube player

A local repository connects to a host through a remote, which is a short name mapped to a URL and stored in .git/config. The commands stay the same on GitLab and Bitbucket, and only the URL changes.

  1. Create an empty repository on the host and copy its URL
  2. Run git remote add origin REMOTE-URL
  3. Run git remote -v to confirm the fetch and push URLs
  4. Run git push -u origin main

GitHub Docs adds one prerequisite: authenticate to GitHub on the command line before the first push.

The name origin is a convention, not a keyword. git clone creates it by default, and –origin or clone.defaultRemoteName replaces it (git-clone documentation).

The -u flag saves the remote branch as the default target, so a later git push needs no arguments.

Receiving changes takes two commands. git fetch downloads new commits into the remote-tracking branches under refs/remotes/origin and leaves your own branches alone, while git pull fetches and then merges.

GitHub also caps sizes (GitHub Docs, About large files on GitHub). Files added through the browser can be at most 25 MiB. Git warns when a pushed file passes 50 MiB, and anything above 100 MiB is blocked by default. For the repository as a whole, GitHub recommends staying under 1 GB, with under 5 GB strongly recommended.

When a Git Repository Fails or Does Not Apply

Git fails in predictable ways. A command runs outside a repository, the repository belongs to another user, or the project does not fit what a repository stores well. Each case has a documented fix.

Why Git reports it is not a repository

The message “fatal: not a git repository (or any of the parent directories): .git” means neither the current folder nor any parent contains a .git directory. Move into the project folder or run git init (Git Tower).

If you are unsure where you are, git rev-parse –is-inside-work-tree prints true inside a working tree and false inside .git or a bare repository. git rev-parse –is-bare-repository prints true for a bare one. git status fails with the same fatal message outside a repository.

The git status output doubles as the quickest check, because it needs a repository to run at all.

The dubious ownership error

Since Git 2.35.2, Git refuses to run in a repository owned by a different user (Atlassian Bitbucket support). The check closed CVE-2022-24765, where an untrusted .git directory could make Git run hooks and config as someone else (Ken Muse).

To trust one repository, run git config –global –add safe.directory with the repository path. The ‘\*’ wildcard works on Git 2.35.3, 2.36 and later, but it switches the check off for every folder. Skip the wildcard on shared machines.

CI runners hit this often. One report in the actions/checkout issue tracker shows setuptools\_scm failing on Linux with the error even though checkout v3 registers the workspace as a safe directory.

Where a repository is the wrong fit

YouTube player

A clone made with –depth holds only the number of recent commits you asked for. That copy breaks the full-history rule (git-clone documentation).

Large binaries are a bigger headache. Deleting a big file in a later commit leaves it in history, and GitHub Docs says a file added in an earlier commit has to be removed from the repository’s history to shrink it.

Secrets are worse. GitHub Docs warns never to add, commit or push passwords or API keys to a remote repository. Guides on hiding an API key before it reaches GitHub cover the safer setup.

Nested repositories cause their own confusion. A .git directory inside a subfolder makes that subfolder its own repository. A stray git init in a parent folder pulls every project beneath it that has no .git of its own into one repository.

Git Repository FAQ

How do you delete a Git repository?

Delete the project’s .git directory. The folder stops being a repository, and your working files stay in place.

A hosted copy is a separate matter. Removing a repository on GitHub leaves every local clone intact.

Can a repository store files other than code?

Yes. A blob holds the bytes of any file, so documents, images and config files all work.

Large binaries are the weak spot, because every version of them stays in history.

What does origin mean in Git?

Origin is the default name of the remote a repository was cloned from, or the first one you add by hand.

A repository can track several remotes at once. git remote -v lists each one with its fetch and push URLs.

Why do new repositories start on master instead of main?

Git’s own default is still master. The current git-init manual page says Git 3.0 will switch it to main, and Git 3.0 had not shipped as of mid-2026.

GitHub’s push instructions already use main, which is why a fresh local repository and a new GitHub repository often disagree on the branch name.

Can one Git repository contain another?

Yes. git submodule keeps one repository as a subdirectory of another.

git worktree works the other way around and attaches extra working trees to a single repository.

What to Set Up First in a New Git Repository

Write the .gitignore file first, make the initial commit second, and add the remote and push last.

Ignore rules come first because passwords, API keys and build output are cheaper to exclude before staging than to purge from published history. The remote comes last because a push hands every commit to everyone who clones.

The order has one exception. A repository created with git clone already carries history and an origin, so only the ignore file is left to check.

The downside is time. Nothing exists off your machine until the final step, so a disk failure before the first push takes the whole history with it.

Daily work on the repository then comes down to a short set of Git commands.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.