Blog

What Is Git? Version Control Basics and Commands

Git is a version control system: a tool that records every change to your project as a snapshot, so you can see what changed, go back to an earlier state, and work on the same code as other people without overwriting their work. It runs on your own computer, and a service such as GitHub or GitLab is optional and only used to share the project. This guide explains the ideas first, then walks through the commands you use every day, in the order you will meet them.

What is version control?

Version control (also called source control or source code management) keeps a history of your files. Without it, you end up with folders named final, final2 and final-really. With it, every saved state is labelled with who changed what, when, and why, and you can compare or restore any of them.

It solves three everyday problems:

  • Undo: a change broke the program, and you want the version from yesterday.
  • Experiments: you want to try a risky idea without touching the working code.
  • Teamwork: two people edit the same project, and their changes need to be combined.

Older systems such as Subversion keep the history on one central server. Git is distributed: every copy of the project (a clone) contains the full history, so you can commit, browse history and create branches offline. Git was created by Linus Torvalds in 2005 for developing the Linux kernel (Pro Git: A Short History of Git) and is free and open source under the GPL version 2. For the wider case for version control, see the importance of version control in software development.

Git vs. GitHub

Git is the program on your machine. GitHub, GitLab and Bitbucket are websites that host Git repositories and add features around them, such as pull requests and issue tracking. You can use Git for years without any of them.

The key terms

  • Repository (repo): a project folder plus its history, stored in a hidden .git folder.
  • Commit: a snapshot of your files with a message. Each commit has an ID (a long hash, usually shown shortened) and points to the commit before it.
  • Working directory: the files you see and edit.
  • Staging area (index): where you collect the changes that will go into the next commit.
  • Branch: a movable name for a line of commits. The default branch is usually called main.
  • Remote: another copy of the repository, usually on a server. The default name is origin.

A change moves through three places: you edit it in the working directory, git add puts it in the staging area, and git commit stores it in the repository. The staging area lets you commit only part of your work, so each commit is one logical change.

Install and configure Git

Download Git from git-scm.com, or use your package manager. Then check it works and tell Git who you are, because the name and email are stored in every commit:

git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main

The last line makes new repositories start on main rather than the older default, master.

Your first repository and commit

Create a folder, turn it into a repository, and look at its state:

mkdir my-project
cd my-project
git init
echo "# My Project" > README.md
git status

Git reports that README.md is untracked:

On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md

nothing added to commit but untracked files present (use "git add" to track)

Stage the file and commit it:

git add README.md
git commit -m "Add README"
git log --oneline
[main (root-commit) 8334533] Add README
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
8334533 Add README

Your hash will be different from 8334533, since it depends on content, author and time. That is normal.

The daily loop: status, diff, add, commit

Most of your Git use is this cycle. Say you add a file, commit it, then edit it:

echo "print('hello')" > app.py
git add app.py
git commit -m "Add app.py"
echo "print('hello, world')" > app.py
git diff
diff --git a/app.py b/app.py
index b376c99..b2204a3 100644
--- a/app.py
+++ b/app.py
@@ -1 +1 @@
-print('hello')
+print('hello, world')

git diff shows changes that are not yet staged. After git add app.py, the same change appears under git diff --staged, and git status tells you which files are modified, staged or untracked. Commit with git commit -m "Greet the world", and git log --oneline lists the history, newest first:

cbac9db Greet the world
6a53071 Add app.py
8334533 Add README

Commands you will use most

Command What it does
git init Start a repository in the current folder
git clone <url> Copy a remote repository, history included
git status Show modified, staged and untracked files
git add <file> Stage a change for the next commit
git commit -m "msg" Save the staged changes as a commit
git diff / git diff --staged Show unstaged / staged changes
git log --oneline Show the history, one line per commit
git switch <branch> Move to another branch
git push / git pull Send your commits to a remote / bring in new ones

Ignore files you should not commit

Build output, logs, virtual environments and secrets do not belong in a repository. List patterns in a file named .gitignore:

__pycache__/
*.log
.env

Git then stops showing matching untracked files in git status. It does not remove files that were already committed; for those, use git rm --cached <file>. If a password or API key was committed, treat it as leaked and change it, because deleting it in a later commit leaves it in the history.

Branching and merging

A branch lets you work on a feature or a fix without disturbing main. Creating one is cheap, because a branch is just a name pointing to a commit.

git switch -c greeting
# edit files, then:
git commit -am "Change greeting"
git switch main
git merge greeting

git switch -c creates a branch and moves to it. (git checkout -b does the same and is older; git switch and git restore were added in Git 2.23 to split up what checkout did.) Note that git commit -am only stages files Git already tracks, so use git add first for new files.

If main has not moved since you branched, the merge just slides main forward. If both branches changed the same lines, Git stops and asks you to decide.

Resolve a merge conflict

Suppose both main and greeting changed the one line in app.py. Merging gives:

Auto-merging app.py
CONFLICT (content): Merge conflict in app.py
Automatic merge failed; fix conflicts and then commit the result.

git status -sb marks the file as UU (unmerged on both sides), and the file now contains markers:

<<<<<<< HEAD
print('hi from main')
=======
print('hi from greeting')
>>>>>>> greeting

Between <<<<<<< HEAD and ======= is your current branch’s version; below it is the incoming one. Edit the file to the result you want and delete all three marker lines, then finish the merge:

git add app.py
git commit -m "Merge greeting"
git log --oneline --graph
*   bd0af22 Merge greeting
|\  
| * 43801a3 Change greeting
* | 1d66bd6 Change greeting on main
|/  
* cbac9db Greet the world
* 6a53071 Add app.py
* 8334533 Add README

Changed your mind halfway? git merge --abort returns you to the state before the merge. Once a branch is merged, delete it with git branch -d greeting; Git refuses if the branch has unmerged work, which protects you.

Work with a remote

A remote is a copy of the repository somewhere else. To publish a local project, create an empty repository on your host, add it as origin, and push:

git remote add origin https://github.com/you/my-project.git
git push -u origin main

The -u flag remembers that your local main tracks origin/main, so later a plain git push or git pull is enough. To get an existing project, run git clone <url>, which downloads the full history and sets up origin for you. git remote -v lists your remotes.

Two commands bring in other people’s work. git fetch downloads new commits without touching your files; git pull is a fetch followed by a merge. If you have local commits and the remote has new ones too, recent Git versions ask you to choose how to combine them:

hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint: 
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase

Either is fine for a beginner. git pull --no-rebase merges, as shown earlier, and git pull --rebase replays your commits on top of the new ones for a straighter history. Never rebase commits that other people already have.

On a team, you rarely push straight to main. The usual pattern is: create a branch, push it, and open a pull request on the host so others can review it. See understanding pull requests in GitHub for that part.

Undo things safely

Which undo command you need depends on how far the change has travelled.

Situation Command
Discard unstaged edits to a file git restore app.py (the edits are gone for good)
Unstage a file, keep the edits git restore --staged f.txt
Undo the last commit, keep the changes staged git reset --soft HEAD~1
Undo a commit that is already pushed git revert <commit>
Set work aside for a moment git stash, later git stash pop

git revert adds a new commit that cancels an earlier one, so shared history is never rewritten:

git revert --no-edit HEAD
git log --oneline | head -3
[main 265fd43] Revert "Bad change"
 Date: Wed Oct 21 23:13:35 2026 +0200
 1 file changed, 1 deletion(-)
265fd43 Revert "Bad change"
1af0978 Bad change
bd0af22 Merge greeting

git reset instead moves the branch back and rewrites history, so use it only on commits you have not pushed. git reset --hard also throws away your working changes, so check git status first.

git stash saves your uncommitted changes and gives you a clean working directory, which is handy when you must switch branches mid-task:

Saved working directory and index state WIP on main: 265fd43 Revert "Bad change"

Run git stash pop to bring the changes back.

Recover a deleted branch or lost commit

Committed work is hard to lose for good. Git keeps a log of where HEAD has been, called the reflog. If you force-delete a branch by mistake, find its last commit and name it again:

git branch -D scratch
git reflog | head -4
265fd43 HEAD@{0}: checkout: moving from scratch to main
fd4f3e8 HEAD@{1}: commit: Scratch work
265fd43 HEAD@{2}: checkout: moving from main to scratch
git branch scratch fd4f3e8

The branch is back with its commit. Reflog entries expire after a while (by default at least 30 days for unreachable commits), so recover soon.

A short list of habits that pay off

  • Commit one logical change at a time with a message that says what and why: a short summary line of about 50 characters, then a blank line and details if needed.
  • Run git status and git diff before every commit to see what you are about to save.
  • Pull before you push, and keep branches short-lived, so conflicts stay small.
  • Branch for every feature or fix and keep main working.
  • Practise in a throwaway repository. Everything in this guide can be tried in a new folder, and deleting the folder resets it.

Where to go next

Once the basics feel natural, three topics are worth learning: git rebase for tidying a branch before sharing it, git cherry-pick for copying a single commit to another branch, and git tags for marking releases. For more learning material, see the best Git tutorials for beginner developers. Git is also easier to use once you are comfortable writing code, since you will have real projects to track; you can build that skill with the step-by-step lessons at AlgoCademy.

A lesson in AlgoCademy: the step to write, the line typed in the editor and checked, Step 3 passed; then a typo, the error pointing at its line, the fix, and Step 4 passed.

Before you go

Start thinking like a software engineer.

Learn to code and solve interview problems, one small, checked step at a time.

Try Printing to the Console free

No account needed. You write real code in your browser.