Repositories and Cloning

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

This page covers what a repository actually is, the three states a file moves through, and the three ways to start one — initializing, cloning and forking — from the command line, on GitHub.com, in IntelliJ IDEA and in VS Code. It targets Git 2.55.x, GitHub CLI (gh) 2.101.x, IntelliJ IDEA 2026.2, current stable VS Code with the GitHub Pull Requests and Issues extension, and github.com as of September 2026, where the default branch for a new repository is main. For the very first setup steps (installing Git, git config, authentication), see Getting Started with Git and GitHub.

Concept

What a repository is

A Git repository is a project directory plus a .git subdirectory that Git creates inside it. That .git directory is the repository in the strict sense: it holds the full history of every committed snapshot, the configuration (.git/config), and the refs — named pointers such as branches and tags — that give that history human-readable names. Deleting .git turns the folder back into a plain directory with no history; the files you see day to day are only the working tree, a checked-out snapshot of one point in that history.

The three states and three areas

Git tracks a file through three states, and the state maps to the area of the repository the file currently sits in:

Area What it holds

Working tree

The files as they sit on disk, where you edit them with an editor or IDE.

Staging area (index)

A snapshot of what will go into the next commit. git add copies the current working-tree content of a file into the index; it does not track the file going forward, only that one snapshot.

Repository

The committed history inside .git. git commit takes whatever is in the staging area and records it as a new snapshot, with the previous commit as its parent.

flowchart LR WT["Working tree"] -->|git add| SA["Staging area / index"] SA -->|git commit| REPO["Repository .git"] REPO -->|git restore --staged| SA SA -->|git restore| WT

git add moves a snapshot of a file from the working tree into the staging area; git commit moves everything currently staged into the repository as a new, permanent snapshot. git restore reverses the arrows: without --staged it overwrites a working-tree file with the version from the index (or from a commit with --source), discarding uncommitted edits; with --staged it un-stages a file, copying it from the last commit back into the index without touching the working tree. The full command reference, including git reset and detached-HEAD checkouts, is in Checkout, Switch and Restore.

The file lifecycle

Relative to the last commit and the index, a tracked or untracked file is always in exactly one of four states:

State Meaning

Untracked

Git has never seen this path before (it is not in the last commit or in the index). New files start here.

Unmodified

Tracked, and identical to the version in the last commit.

Modified

Tracked, and different from the version in the last commit, but not yet staged.

Staged

The index holds a snapshot of this file that differs from the last commit, ready for the next git commit.

A file can be both modified and staged at once: staging a file copies its content at that moment; editing it again afterwards leaves the newer content modified-but-unstaged while the index still holds the older, staged version. git status reports exactly this, split into "Changes to be committed" (staged), "Changes not staged for commit" (modified) and "Untracked files":

git status
git status -s        # short format: XY <path>; X = index, Y = working tree (?? = untracked, M = modified, A = added)

Derived from Git Basics — Recording Changes to the Repository. Committing itself, including message conventions and amending, is covered in Commits.

Command line

git init: starting a repository from scratch

mkdir my-project && cd my-project
git init                       # creates .git/ with no commits and no remote
git init --initial-branch=main # or set init.defaultBranch=main once, globally

git init turns the current directory into a Git repository: it creates .git/ with an empty object database, an empty index and a HEAD pointing at an unborn branch. Nothing is tracked or committed yet. Modern Git honours init.defaultBranch; without it, git init still names the first branch master. Derived from git-init and Git Basics — Getting a Git Repository.

git clone: copying an existing repository

git clone https://github.com/<owner>/<repo>.git          # HTTPS
git clone git@github.com:<owner>/<repo>.git               # SSH
git clone https://github.com/<owner>/<repo>.git my-dir     # clone into a custom directory name
git clone --branch release_1.4 --single-branch <url>        # only one branch's history

git clone downloads the full history from the remote (every commit, branch and tag it exposes), checks out the remote’s default branch into a new working directory, and configures a remote named origin pointing back at the URL you cloned. GitHub offers two clone URL forms for the same repository:

Form Example Authentication

HTTPS

https://github.com/<owner>/<repo>.git

A GitHub username with a personal access token (<TOKEN>), or a credential helper / gh auth login caching one; works through most corporate proxies.

SSH

git@github.com:<owner>/<repo>.git

An SSH key pair registered with your GitHub account; no token prompt afterwards, but the outbound port (22) must not be blocked.

Derived from git-clone and Cloning a repository. Fetching new commits into an existing clone, pulling and pushing are covered in Fetch, Pull and Push.

.gitignore: telling Git what to leave alone

A .gitignore file lists patterns for paths Git should never offer as untracked — build output, dependency caches, IDE settings, secrets:

# .gitignore
*.log
/build/
node_modules/
.env
!keep-this.log

matches anything except /; a leading or embedded / anchors the pattern to the directory holding the .gitignore; a trailing / matches directories only; * matches across any number of directory levels; and a leading ! re-includes a path an earlier pattern excluded. A pattern only ever ignores files Git does not already track — add .gitignore entries before the first commit of that path, or run git rm --cached <path> to stop tracking it afterwards. Derived from gitignore.

Two useful complements to a per-project .gitignore:

git config --global core.excludesFile ~/.gitconfig_global_ignore   # your own machine-wide ignores (editor swap files, OS junk)
git check-ignore -v path/to/file                                    # which pattern, in which file, is ignoring this path

The global ignore file is personal and lives outside any repository, so it never lands in a commit; keep project-specific patterns (build directories, generated files a teammate also needs ignored) in the repository’s own .gitignore instead. For starting points per language and tool, GitHub maintains the github/gitignore templates repository, which is also what backs the .gitignore template dropdown described below. Derived from gitignore.

Remotes: origin, git remote -v, git remote add

A remote is a named URL Git can fetch from and push to. git clone creates one automatically, named origin, pointing at the URL you cloned; a repository created with git init has none until you add one:

git remote -v                                        # list remotes with their fetch/push URLs
git remote add origin git@github.com:<owner>/<repo>.git
git remote add upstream https://github.com/<original-owner>/<repo>.git   # a second remote, e.g. a fork's source
git remote rename origin old-origin
git remote remove upstream

origin is only a convention — the name git clone happens to use — not a keyword; a repository can have several remotes (origin for your fork, upstream for the project you forked from) or none at all. Derived from git-remote and Git Basics — Working with Remotes. Pushing to and pulling from a remote is covered in Fetch, Pull and Push.

On GitHub.com

Creating a repository

  1. Click the + icon in the top-right corner of any GitHub.com page and choose New repository.

  2. Pick the Owner (your account or an organization) and a repository Name (letters, digits, ., -, _); optionally add a Description.

  3. Choose Public or Private.

  4. Optionally check Add a README file.

  5. Optionally pick a .gitignore template from the dropdown — these are the same templates published in github/gitignore, selected by language or tool (for example Java, Node, Maven).

  6. Optionally choose a license from the Choose a license dropdown (for example MIT, Apache-2.0, GPL-3.0); GitHub adds a LICENSE file with the license text and your name/year filled in.

  7. Click Create repository.

Only tick README/.gitignore/license when the repository starts empty; if you plan to push an existing local project, leave them unchecked to avoid an unrelated-history conflict on the first push. Derived from Creating a new repository.

Copying the clone URL

On the repository’s Code tab, click the green Code button, choose the HTTPS or SSH tab, and click the copy icon next to the URL — then git clone it as shown above. Derived from Cloning a repository.

Forking vs. cloning

Cloning downloads a repository’s history to your machine; it does not create anything new on GitHub, and you cannot push back to it unless you already have write access. Forking creates your own server-side copy of someone else’s repository under your account, which you do have write access to; you then clone your fork (not the original) and, to contribute changes back, open a pull request from your fork to the original. Click Fork near the top-right of the repository page, optionally rename it and choose an owner, then clone the resulting https://github.com/<your-username>/<repo>.git. Working with the pull request itself is covered in Pull Requests. Derived from Contributing to a project.

Equivalent GitHub CLI commands

gh repo create <name> --public --add-readme --gitignore Java --license MIT --clone
gh repo clone <owner>/<repo>
gh repo fork <owner>/<repo> --clone

gh repo create mirrors the web form (--private/--internal instead of --public, --description, -g/--gitignore <template>, -l/--license <key>); with --clone it also creates a local clone immediately, and -s <dir> --push turns an existing local directory into the new repository’s initial content instead. gh repo clone is a thin wrapper over git clone that resolves <owner>/<repo> to the right URL using your gh authentication. gh repo fork --clone forks the given repository and clones the fork; run with no argument inside an existing clone to fork the repository you are in and add the fork as a remote. Derived from gh repo create, gh repo clone and gh repo fork.

In IntelliJ IDEA

Cloning: Clone Repository

  1. From the Welcome screen click Clone Repository, or with a project open use Git \| Clone…​ (or File \| New \| Project from Version Control).

  2. Paste the HTTPS or SSH clone URL (if you are signed in to GitHub in the IDE, matching repositories are suggested as you type) and pick a destination directory.

  3. Click Clone. IntelliJ IDEA checks out the default branch, then offers to trust the project and detects its build system (Maven, Gradle, …​) automatically.

Derived from the JetBrains Set up a Git repository and GitHub pages.

Sharing a local project on GitHub

  1. With an existing (uninitialized) local project open, choose Git \| GitHub \| Share Project on GitHub (or VCS \| Share Project on GitHub on older layouts).

  2. Confirm or edit the repository name, description and visibility (public/private), then click Share.

  3. IntelliJ IDEA runs git init if needed, creates the repository on GitHub through your linked account, adds it as the origin remote, stages and commits the current files, and pushes — one action covering what the command line does as git init, gh repo create --source . --push, and git push -u origin main.

  4. For a project that already has a remote, use Git \| Push (Ctrl+Shift+K on Windows/Linux, Cmd+Shift+K on macOS) instead, which opens the Push dialog and offers Define remote if none is set.

In VS Code

Cloning: Git: Clone

  1. Open the Command Palette (Ctrl+Shift+P on Windows/Linux, Cmd+Shift+P on macOS) and run Git: Clone, or click Clone Repository in the Source Control view when no folder is open.

  2. Choose Clone from GitHub to pick a repository from a filterable list (VS Code’s built-in GitHub support asks you to sign in to GitHub the first time), or Clone from URL to paste an HTTPS or SSH clone URL directly.

  3. Pick a local destination folder; VS Code clones the repository and offers to open it.

Initializing a repository and publishing to GitHub

  1. Open a folder that is not yet a Git repository; the Source Control view shows Initialize Repository. Clicking it runs git init; the existing files then show up as untracked changes, ready to stage for a first commit.

  2. After committing at least once, the Source Control view’s …​ menu (or a Publish to GitHub button) offers Publish to GitHub: choose a repository name, public or private visibility, and which files to include. VS Code creates the GitHub repository, adds it as origin, and pushes the current branch — the same outcome as gh repo create --source . --push.

  3. To publish to an existing remote instead, run Git: Add Remote from the Command Palette with the HTTPS or SSH URL, then Git: Push.

Common mistakes / tips

  • Committing before adding a .gitignore. A pattern added after a file is already tracked does not un-track it; run git rm --cached <path> (or -r for a directory) once the ignore rule is in place.

  • Cloning with SSH when only an HTTPS credential is set up, or vice versa. The two clone URL forms are not interchangeable at the network level; switch a remote’s URL with git remote set-url origin <new-url> rather than re-cloning.

  • Pushing straight to someone else’s repository instead of forking. Without write access, git push fails; fork first, push to the fork, then open a pull request.

  • Checking "Add a README" while importing an existing project. GitHub’s initial commit then has no common ancestor with your local history, and the first push is rejected as a non-fast-forward; either start the GitHub repository empty, or pull with --allow-unrelated-histories once and resolve the conflict.

  • Forgetting origin is just a name. Renaming or repointing it (git remote rename, git remote set-url) never changes history; it only changes where git fetch/git push without arguments go.