Git tracks every change you make to your code, lets you undo mistakes, and makes it possible for multiple people to work on the same project without stepping on each other’s toes. If you write code for a living, or plan to, you’ll use Git nearly every day.
Linus Torvalds built Git in 2005 because he needed a version control system for the Linux kernel that was fast, distributed, and didn’t require a central server to function. Two decades later, it’s the standard. GitHub alone hosts over 200 million repositories. Whether you’re a solo developer or part of a large team, Git is the foundation that everything else sits on.
This guide covers Git from installation through advanced workflows, with commands for both Linux and Windows.
What Git actually does#
At its core, Git is a system that takes snapshots of your project. Every time you commit, Git records the state of every file in your repository at that moment. You can go back to any snapshot, compare changes between snapshots, or branch off into a separate line of development and merge it back later.
A few things make Git different from older version control systems:
It’s distributed. Every developer has a complete copy of the repository, including the full history. You can commit, branch, and view history without any network connection. This also means there’s no single point of failure. If the server goes down, every developer’s local copy is a full backup.
It’s fast. Because most operations happen locally, things like viewing history, creating branches, and diffing files are nearly instantaneous. Git was designed to handle the Linux kernel, which has millions of lines of code and thousands of contributors. Your project is probably smaller.
Branching is cheap. Creating a branch in Git takes milliseconds and costs almost nothing in terms of storage. This makes it practical to create branches for every feature, bug fix, or experiment, something that was painful with older tools like SVN.
Core concepts#
Before touching the command line, it helps to understand three things:
Repository. A repository (repo) is your project directory plus a hidden
.git
folder that contains the entire history. There are local repos on your machine and remote repos on servers like GitHub or GitLab.
Commit. A commit is a snapshot of your project at a specific point in time. Each commit has a unique hash (like
a1b2c3d
), an author, a timestamp, and a message describing what changed. Commits are the atoms of Git. Everything else is built on top of them.
Branch. A branch is a movable pointer to a commit. When you create a branch, you’re just creating a new pointer. When you commit on that branch, the pointer moves forward. The default branch is called
main
(or
master
in older repos).
Installing Git#
Linux
Git is available in every major Linux distribution’s package manager.
Debian / Ubuntu:
sudo apt update
sudo apt install git
RHEL / Rocky Linux / CentOS:
sudo dnf install git
On older RHEL/CentOS versions that don’t have
dnf
:
sudo yum install git
Verify the installation:
git --version
Windows
Download the installer from git-scm.com and run it. The default options work fine for most setups. Once installed, you get Git Bash (a Unix-like terminal) and Git integrates with PowerShell and Command Prompt.
If you use Windows Package Manager:
winget install Git.Git
First-time setup#
Before you start using Git, set your name and email. These are attached to every commit you make.
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
The
--global
flag applies these settings to all repositories on your machine. If you need different settings for a specific project (like a different email for work vs personal), run the same commands without
--global
inside that project directory.
A few other settings worth configuring upfront:
# Set the default branch name for new repos
git config --global init.defaultBranch main
# Use a cleaner output for git log
git config --global log.oneline true
Creating and cloning repositories#
Start a new project:
mkdir my-project
cd my-project
git init
This creates a
.git
directory inside your project. Nothing is tracked yet; you need to add files and make your first commit.
Clone an existing project:
git clone https://github.com/username/repo.git
This downloads the entire repository, including its full history, and sets up a remote called
origin
pointing back to the source.
The basic workflow#
This is the sequence you’ll repeat hundreds of times:
1. Make changes to your files.
2. Check what changed:
git status
This shows which files are modified, which are staged, and which are untracked.
3. Stage the changes you want to commit:
git add filename.js
Or stage everything:
git add .
Staging is Git’s way of letting you choose exactly which changes go into the next commit. You might have modified five files but only want to commit changes to two of them.
4. Commit the staged changes:
git commit -m "Add user login validation"
Write commit messages that explain why, not what. The diff shows what changed. The message should explain the reasoning. “Fix bug” tells your teammates nothing. “Prevent duplicate form submission on slow connections” tells them exactly what problem was solved.
5. View the history:
git log --oneline
This shows a compact list of commits with their hashes and messages.
Undoing things#
Everyone makes mistakes. Git gives you several ways to recover.
Discard uncommitted changes to a file:
git checkout -- filename.js
This restores the file to its last committed state. The changes are gone permanently.
Unstage a file (keep the changes, just remove from staging):
git restore --staged filename.js
Amend the last commit (fix a typo in the message or add a forgotten file):
git add forgotten-file.js
git commit --amend
Revert a commit (create a new commit that undoes a previous one):
git revert a1b2c3d
This is the safe way to undo changes that have already been pushed. It doesn’t rewrite history, it adds a new commit on top.
Reset to a previous commit (destructive, rewrites history):
git reset --hard a1b2c3d
Use this carefully and only on commits that haven’t been pushed to a shared repository. Rewriting shared history causes real problems for your team.
Branching#
Branching is where Git earns its reputation. A branch lets you work on something without affecting anyone else’s work.
Create and switch to a new branch:
git checkout -b feature/user-auth
Or using the newer syntax:
git switch -c feature/user-auth
Switch between existing branches:
git checkout main
List all branches:
git branch
Delete a branch after it’s been merged:
git branch -d feature/user-auth
A practical branching pattern
Most teams follow some variation of this:
-
mainholds production-ready code - Create a branch from
mainfor each feature or fix - Work on the branch, committing as you go
- Open a pull request when the work is ready for review
- Merge the branch into
mainafter approval - Delete the branch
This keeps
main
stable and gives everyone a clean workspace for their own changes.
Merging#
When your feature branch is ready, you merge it back into
main
:
git checkout main
git merge feature/user-auth
If nobody else has changed the same files you did, Git handles the merge automatically. If there are conflicting changes, Git marks the conflicts in the affected files and asks you to resolve them manually.
A conflict looks like this in the file:
<<<<<<< HEAD
const timeout = 3000;
=======
const timeout = 5000;
>>>>>>> feature/user-auth
You decide which version to keep (or combine them), remove the conflict markers, and commit the result. Conflicts sound scary, but they’re usually straightforward. They just mean two people changed the same line, and Git wants a human to make the call.
Working with remote repositories#
Most teams use a hosting service like GitHub or GitLab as their central remote repository.
Push your branch to the remote:
git push origin feature/user-auth
The first time you push a new branch, add the
-u
flag to set up tracking:
git push -u origin feature/user-auth
After that,
git push
and
git pull
work without specifying the remote and branch name.
Pull the latest changes from the remote:
git pull origin main
See which remotes are configured:
git remote -v
Pull requests
On GitHub and GitLab, you don’t merge branches directly on the command line (though you can). Instead, you push your branch and open a pull request (called a “merge request” on GitLab). This gives your team a place to review the code, leave comments, and run automated tests before the changes land in
main
.
Pull requests are where code review happens, and code review is one of the most effective ways to catch bugs, share knowledge, and maintain code quality across a team.
Advanced workflows#
Rebasing
Rebasing replays your branch’s commits on top of another branch, as if you’d started your work from the latest version of that branch. It produces a cleaner, linear history compared to merge commits.
git checkout feature/user-auth
git rebase main
The golden rule: never rebase commits that have already been pushed to a shared branch. Rebasing rewrites commit hashes, which causes problems for anyone who has based work on those commits. Use it for cleaning up your local branch before opening a pull request.
Tagging releases
Tags mark specific points in history, typically used for version releases:
git tag -a v1.0.0 -m "First stable release"
git push --tags
Tags are permanent markers, unlike branches, which move forward with each commit.
Cherry-picking
Sometimes you need a single commit from one branch applied to another without merging the entire branch:
git cherry-pick a1b2c3d
This creates a new commit on your current branch with the same changes. Useful for backporting a bug fix from a development branch to a release branch.
Git hooks
Hooks are scripts that run automatically at certain points in the Git workflow. Common uses:
- pre-commit: Run linters or formatters before allowing a commit
- pre-push: Run tests before pushing to the remote
- commit-msg: Enforce commit message formatting
Hooks live in
.git/hooks/
and are just executable scripts. Many teams use tools like Husky (for JavaScript projects) or pre-commit (Python) to manage hooks across the team.
Keeping things clean#
.gitignore tells Git which files to ignore. Every project should have one. Common entries:
node_modules/
.env
*.log
dist/
.DS_Store
Without a
.gitignore
, you’ll end up with dependency folders, environment files with secrets, and OS-generated junk cluttering your repository.
Delete merged branches. After a branch is merged, delete it. Stale branches accumulate and make it harder to find the ones that matter. Most teams configure GitHub/GitLab to auto-delete branches after merge.
Write meaningful commit messages. A good commit history is documentation that writes itself. Six months from now,
git log
should tell the story of how your project evolved. That only works if the messages are useful.
Git hosting: GitHub vs GitLab#
Both are excellent platforms for hosting Git repositories. GitHub is the larger ecosystem with more open-source projects and a wider community. GitLab offers a more integrated DevOps platform with built-in CI/CD pipelines, container registries, and issue tracking in a single product. Many self-hosted teams prefer GitLab because you can run your own instance.
For most projects, either works well. The important thing is that you’re using one of them as your remote, so your code is backed up, your team can collaborate through pull requests, and you have a web interface for browsing code and reviewing changes.
Check out the official Git documentation for the complete reference.
Host your projects with Hostney#
Every Hostney web hosting plan supports Git-based deployment workflows. Push your code, and your site is live. Combined with our built-in nginx caching, free SSL certificates, and server-side performance optimizations, you get a hosting environment built for developers who care about their workflow.
Try our web hosting free for 14 days with no obligation.