Git Basics on Linux: The Commands You Actually Need

Git Basics on Linux: The Commands You Actually Need

Git is a distributed version control system. It records snapshots of a directory over time, lets you move between them, and lets multiple people work on the same files without overwriting each other.

The reputation for difficulty is mostly earned, and it comes from one thing: Git’s command names describe its internal implementation rather than what you are trying to do. Once you understand the three places a file can be, most of the confusion resolves.

The three places a change can live

This is the model everything else depends on.

The working directory is the files on disk, the ones your editor opens. Changes here are not tracked by Git until you tell it about them.

The staging area (also called the index) is a list of changes you have marked for the next commit. It exists so you can commit two of the five files you edited, rather than being forced to commit all or nothing.

The repository is the .git directory, containing the permanent history of every commit.

A change moves working directory, then staging area, then repository:

# edit a file, then
git add file.txt        # working directory -> staging area
git commit -m "message" # staging area -> repository

The staging area is the part beginners find pointless and experienced users find indispensable. It is what lets you split a messy afternoon of work into three coherent commits.

Setting up

Do this once per machine. Git refuses to commit without it, and the name and email are baked into every commit you make.

git config --global user.name "Your Name"
git config --global user.email "you@example.com"

# set the default branch name for new repositories
git config --global init.defaultBranch main

# use your preferred editor for commit messages
git config --global core.editor nano

That last one matters more than it looks. Git’s default editor on most systems is vim, and a beginner who runs git commit without -m and lands in vim with no idea how to exit has had a bad first experience with Git that was really a bad first experience with vim. Our vim basics guide covers the escape route if you end up there anyway.

Settings live in ~/.gitconfig, which is a plain INI file you can edit directly.

Starting a repository

Two ways in. Either you are creating something new:

mkdir myproject && cd myproject
git init

Or you are copying something that exists:

git clone https://github.com/user/repo.git
cd repo

git init creates the .git directory. Everything Git knows about your project lives in there, and deleting it turns the repository back into an ordinary directory with no history.

The daily loop

Most work is a cycle of four commands.

git status

Run this constantly. It tells you what branch you are on, what is staged, what is modified but not staged, and what Git is ignoring entirely.

git status

# the short form, once you know what you are looking at
git status -s

If you are ever unsure what state you are in, git status is the answer, and modern Git includes hints about what to do next.

git add

Stage changes for the next commit.

git add file.txt           # one file
git add src/               # a directory
git add .                  # everything under the current directory
git add -u                 # only files Git already tracks

git add -p is the one worth learning. It walks through each change and asks whether to stage it, which lets you split unrelated edits in the same file into separate commits.

git commit

Record the staged changes.

git commit -m "Fix crash when config file is missing"
git commit                 # opens your editor for a longer message
git commit -am "message"   # stage all tracked modified files and commit

Write messages in the imperative mood, describing what the commit does: “Add rate limiting” rather than “Added rate limiting” or “I added rate limiting”. This reads correctly when Git generates messages on your behalf during merges and reverts.

The -a flag skips the staging area for files Git already tracks. It does not add new files, which catches people out.

git log

See the history.

git log
git log --oneline          # one commit per line
git log --oneline --graph --all   # visual branch structure
git log -p file.txt        # every change to one file
git log --since="2 weeks ago"

git log --oneline --graph --all is worth an alias. It is the clearest picture of branch structure you can get without a GUI.

git config --global alias.lg "log --oneline --graph --all --decorate"
# then just: git lg

Working with a remote

A remote is another copy of the repository, usually on a server.

git remote -v              # list configured remotes
git remote add origin https://github.com/user/repo.git

git push                   # send your commits to the remote
git pull                   # fetch remote commits and merge them
git fetch                  # fetch without merging

origin is just a conventional name for the default remote. There is nothing special about it.

The distinction between fetch and pull matters. fetch downloads and changes nothing in your working directory. pull downloads and immediately merges. Fetching first lets you look before you leap:

git fetch
git log --oneline HEAD..origin/main   # what is on the remote that I lack
git merge origin/main                  # now integrate it

.gitignore

Some files should never be committed: build output, dependency directories, secrets, editor state. List them in a .gitignore file at the repository root.

# build artifacts
build/
dist/
*.o

# dependencies
node_modules/
venv/

# secrets: keep these out of history permanently
.env
*.pem
config/secrets.yml

# editor and OS noise
.vscode/
*.swp
.DS_Store

The important caveat: .gitignore only affects untracked files. If you have already committed a secret, adding it to .gitignore does nothing, because Git is already tracking it. It remains in the history forever, visible to anyone who clones the repository.

# stop tracking a file without deleting it from disk
git rm --cached .env
git commit -m "Remove .env from tracking"

That removes it going forward, and it is still in every previous commit. If the file contained a live credential, the only correct response is to rotate the credential. Removing it from history retroactively requires rewriting every commit since, which breaks every clone of the repository.

Our GPG basics guide covers signing commits, which is worth setting up on anything public.

When things go wrong

These are the commands you will actually reach for under pressure.

Undo changes to a file you have not staged

git restore file.txt              # discard working directory changes
git checkout -- file.txt          # the older syntax, same effect

This is destructive and unrecoverable. The changes were never committed, so Git has no copy.

Unstage something

git restore --staged file.txt
git reset HEAD file.txt           # older syntax

This moves the change back to the working directory without discarding it.

Undo the last commit

git reset --soft HEAD~1    # remove the commit, keep changes staged
git reset --mixed HEAD~1   # remove the commit, keep changes unstaged
git reset --hard HEAD~1    # remove the commit and the changes

--soft is the one you usually want, because it lets you fix the commit and redo it.

If you have already pushed, do not use reset. Use revert instead, which creates a new commit that undoes the old one:

git revert HEAD

The difference matters because reset rewrites history. If someone else has pulled the commit you deleted, their repository and yours now disagree about what happened, and resolving that is considerably more work than a revert commit would have been.

Recover something you thought you destroyed

git reflog

This is the command most people do not know exists, and it has saved a great many afternoons.

reflog records every position HEAD has occupied: every commit, checkout, reset, merge, and rebase. Commits that are no longer reachable from any branch are still listed, and they still exist in the repository until Git garbage collects them, which does not happen quickly.

git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: The work you thought you lost

git reset --hard e4f5g6h

If your work was committed at any point, reflog can probably get it back. If it was never committed, it cannot.

What to learn next

Branching is the other half of Git and is covered in our Git branching and merging guide. For most solo work on a single branch, the eight commands above cover everything.

Two habits worth forming early:

Commit more often than feels necessary. A commit is a save point. Small, frequent commits make reflog and revert useful, and they make git bisect able to find which change broke something.

Read what Git tells you. Modern Git’s error messages include the command that fixes the problem. The output is dense and it is genuinely trying to help.

Frequently Asked Questions

What is the difference between git add and git commit?

git add moves changes into the staging area, which is a list of what will go into the next commit. git commit takes everything currently staged and records it as a permanent snapshot. The two steps exist so you can commit some of your changes and not others.

Do I need a GitHub account to use Git?

No. Git is a local version control system and works entirely on your own machine with no network connection and no account anywhere. GitHub, GitLab, and Codeberg are hosting services that store a copy of your repository so others can access it, which is useful but optional.

What does git push rejected non-fast-forward mean?

It means the remote has commits you do not have locally, so pushing would overwrite them. Run git pull to fetch and integrate the remote changes first, resolve any conflicts, then push again. Do not use git push —force to make the error go away unless you are certain nobody else uses the branch.

How do I undo the last commit?

If you have not pushed it, git reset —soft HEAD1 removes the commit but keeps your changes staged, letting you recommit. git reset —hard HEAD1 removes the commit and the changes permanently. If you have already pushed, use git revert HEAD instead, which creates a new commit undoing the old one without rewriting history.

What is the difference between git fetch and git pull?

git fetch downloads new commits from the remote but does not change your working files. git pull does a fetch followed immediately by a merge into your current branch. Using fetch first lets you see what changed before deciding to integrate it.

Can I recover work I deleted with git reset —hard?

Often yes, if the work was committed at some point. git reflog shows every position HEAD has occupied, including commits that are no longer reachable from any branch, and you can reset back to one of those. Changes that were never committed at all are genuinely gone.