Basic setup

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

This sets your identity. Git uses this info to label your commits. "--global" means it applies to all Git repos on your system. You can override it in a specific repo using the same command without "--global".

git config --list

Shows what info is currently set.

git config user.name

Shows what's set for a specific key.

git config --global core.editor "code --wait" # For VS Code
git config --global core.editor "nano" # For Nano
git config --global core.editor "vim" # For Vim

This sets a default editor (optional).

git config --global init.defaultBranch main

This sets up the default branch name (optional but recommended)

Repository basics

git init

This initializes a new Git repository. It creates a .git/ directory in your project. It doesn't affect your files, it just starts tracking changes from now on. Use "git init -b main" if you want the initial branch to be main instead of master.

git clone https://gitlab.com/davidvarga/it-cheat-sheets.git

Gets a full copy of a remote project (in this example from GitLab). This automatically sets up the remote (usually named origin) and checks out the default branch. You'll now have a working directory and a .git folder to manage changes.

git rev-parse --is-inside-work-tree

Returns true if you are inside a Git repo.

Staging and committing

Think of Git like a three (plus one if you want to use a remote repository) stage process:
working directory → staging area → local repository → remote repository (optional)

git status

Before doing anything, it's good to check the status of the repo. Shows modified, new (untracked) and staged files. It also tells you what Git is seeing and what it's ready to commit.

git add index.html

This stages a specific file. Staging means preparing your changes for commit. Only staged files go into the next commit.

git add .

This stages all changes (new, modified).

git add src/

Stages a folder.

git reset index.html

Unstages a file (useful if you change your mind).

git commit -m "Add feature X"

This records changes to the repository.

A commit includes:

git commit -am "Update tracked files"

This skips staging for already tracked files. It only works for modified files, not new (untracked) ones.

Viewing history

git log

Shows the commit history, including:

git log --oneline

Shows a quick overview of the commit history, including only the commit hash and message.

git log --oneline --graph --all

This adds a visual branch/tree graph. It's useful when working with multiple branches.

git log index.html

Shows commits that affected a specific file.

git diff

Shows unstaged changes.

git diff --staged

Shows staged changes.

git diff <commit-hash>..<commit-hash>

Shows changes between commits. If you put "--name-only" at the end of the command it only shows what files were changed, not what changed in their contents.

git blame index.html

Shows who last changed each line. It annotates each line with the commit and author that last touched it.

git show <commit-hash>

Shows the full diff and metadata for a specific commit.

git log --follow <file-name>

This shows the history of renames and moves.

git log -S "text to search"

This searches the Git history for changes that added or removed a specific string of text. It doesn't just search commit messages, it looks inside the actual code changes. Useful for finding out when a specific piece of code was added or removed.

Branches

git branch

Lists all local branches. Shows the current branch with an asterisk (*).

git branch -a

Shows all branches (local and remote).

git branch <branch-name>

This creates a new branch from the current branch but doesn't switch to it.

// Method 1
git checkout -b <branch-name>

// Method 2
git switch -c <branch-name>

Creates a branch from the current branch and switches to it in one step.

// Method 1
git checkout <branch-name>

// Method 2
git switch <branch-name>

Switches the current branch.

git checkout -

This switches back to the previous branch.

// Rename current branch
git branch -m <new-branch-name>

// Rename another branch
git branch -m <old-branch-name> <new-branch-name>

Renames a branch.

// Safe delete (only if the branch is fully merged)
git branch -d <branch-name>

// Force delete
git branch -D <branch-name>

Deletes a branch.

git merge <branch-name>

Merges another branch into current one. You should make sure you're on the branch you want to merge into. It can cause merge conflicts if changes overlap.

git checkout -b <branch-name> origin/<branch-name>

Tracks a remote branch. It tells Git to connect a local branch to a remote branch, so that it knows where to pull from and push to. This command creates a new local branch: <branch-name> and at the same time it sets it to track the remote origin/<branch-name>. Now when you type "git pull", it should pull from origin/<branch-name> and when you type "git push", it will push to the same remote branch without needing extra info.

git branch -vv

Shows the tracked branches.

Remote repos

git remote add origin https://gitlab.com/davidvarga/it-cheat-sheets.git

Adds a remote repository. "origin" is the default name for the remote, you can change it to anything.

git remote remove origin

Removes a remote.

git remote -v

Shows remote URLs.

git fetch

Fetches remote changes (no merge). It downloads changes but does not apply them. It's useful for reviewing changes before merging

git pull

Pulls changes from remote. Fetches and merges changes from the tracked remote branch.

git clone https://gitlab.com/davidvarga/it-cheat-sheets.git

Clones a remote repository. It copies the whole repo, including history and branches and automatically sets up origin remote

git push origin main

// For first time pushing
git push -u origin main

Pushes local branch to remote. First, "push -u" sets the upstream (tracking relationship). Next time, you can just do "git push".

git push -u origin new-feature

This pushes a new branch to remote. It uploads the branch and sets tracking. Now you can just run "git push" and "git pull" from that branch.

git branch -r

Lists all remote branches.

git remote rename oldname newname

Renames a remote.

git push origin main --force

// Typical usecase
git push -f origin <commit-hash-you-want-to-force>:<branch-name>

Forcibly pushes the local main branch to the remote (e.g., GitLab), overwriting the remote branch, even if the remote has commits that aren't in the local branch. It can be dangerous because if someone else has pushed changes to origin/main that you don't have this will erase their work from the remote repo. It's useful when you pushed a commit that you want to remove like it never existed.

git remote update --prune

This fetches updates from all remotes (like git fetch --all) and then removes (prunes) references to any remote-tracking branches that no longer exist on the remote. It's useful when you developed a feature, pushed it to remote, merged it into the main branch and deleted the branch on the remote repository and now you want to clean up.

Undoing things

// Method 1
git checkout -- index.html

// Method 2
git restore index.html

If you haven't staged a file yet this reverts it to the last committed version. This completely discards changes. They're gone unless saved elsewhere.

// Method 1
git reset index.html

// Method 2
git restore --staged index.html

Once you staged a file you can use this to unstage it. It keeps the changes.

git commit --amend

This way you can modify the last commit. It opens your editor to change the message. Adds new staged changes too

git reset --soft HEAD~1

This undoes the last commit (but keeps changes). It moves HEAD back one commit. Changes go to staging area. It's useful when you committed tol early and want to go back, but till want everything staged and ready to re-commit.

git reset --mixed HEAD~1

Undoes the last commit (and unstage changes). Changes stay in working directory. Useful when you want to undo the last commit and review your changes before you stage again.

git reset --hard HEAD~1

Undoes the last commit completely. This removes commit and file changes as well.

git reset --hard <commit-hash>

This puts the branch back to a specific commit.

git checkout <commit-hash>
git checkout -b feature/from-old-commit

This makes Git move HEAD to point to a specific commit and then creates a new branch from that commit. It's great for safe experimenting, inspecting old versions of the code, testing something in an earlier state, starting a bugfix or new branch from a past point and recovering lost work (combined with "git reflog").

git reflog

Shows all recent HEAD movements (even deleted commits). Its useful to recover things you thought were gone.

git revert <commit-hash>

Creates a new commit that undoes the changes from an earlier one. Safe for public history.

Merge

git checkout main
git merge feature-branch

This is a basic merge. It takes the changes from feature-branch and integrates them into main.

// Merge conflict message
CONFLICT (content): Merge conflict in index.html

// Open index.html and look for
<<<<<<< HEAD
Current changes in main
=======
Changes in feature-branch
>>>>>>> feature-branch

// After manually resolving the conflict:
git add index.html
git commit

Sometimes Git can't decide how to merge files automatically (e.g., same line edited in both branches). So you need to manually resolve the conflicts.

git merge --abort

If you're in the middle of a merge conflict and want to abandon merging you can use this.

git merge feature-branch -m "Merge feature-branch into main"

Adds a custom merge message.

git merge --no-commit --no-ff feature-branch

This applies the merge without committing it. Useful to test for conflicts before finalizing.

git merge --no-ff feature-branch

Forces Git to always create a merge commit, even if a fast-forward is possible. It ensures every merge is visible in the history, keeps a clear record of when features were integrated and helps reviewing or reverting grouped changes later.

git merge --squash feature-branch
git commit -m "Add feature in one clean commit"

This combines all commits from a branch into a single commit, but doesn't create a merge commit or record the branch merge. It keeps the history clean and minimal. It's ideal for merging work-in-progress feature branches with messy commit logs and useful in open-source or PR-based workflows where you want 1 commit / 1 feature.

Rebase

git checkout feature
git rebase main

// Or from the main branch
git rebase feature

This is a basic rebase flow.
During this git:

// This is cleaner
git fetch origin
git rebase origin/main

// Than this
git merge origin/main

To keep a branch updated, it's cleaner to do rebase than to do merge.

git rebase -i HEAD~3

This is an interactive rebase. Using this Git temporarily removes the last 3 commits, reapplies them one by one, giving you a chance to change each step, and if you change anything (e.g., squashing commits), the resulting commits will have new hashes, meaning the branch history is now rewritten. Useful for cleaning up the commit history.

// After fixing a conflict
git add index.html
git rebase --continue

// Or you can cancel it
git rebase --abort

During a conflict Git will stop and let you fix the issues. After the issues have been resolved you can add your changes and continue or about the rebase process.

Rebase carefully, especially with shared branches. Because rebase rewrites commit history, you should never rebase commits that have been pushed to shared/public branches unless you're 100% sure everyone is on the same page. If you do, others might get conflicts when they pull.

The difference between merge and rebase is:

Cherry-pick

git cherry-pick <commit-hash>

This applies one commit to the current branch by creating a new commit with the same changes (but a different hash) without merging or rebasing a whole branch.

// Add modified files after conflicts are resolved
git add index.html

// Then continue the process
git cherry-pick --continue

If the commit conflicts with the current code Git will pause and ask you to fix the conflict.

git cherry-pick --abort

If something goes wrong this is how you can abort the process.

Stash

git stash

This saves any changes in tracked files, clears them from the working directory and leaves you with a clean state but keeps changes hidden in the stash list, so you can come back to them later.

git stash list

Lists all stashes.

// Restore all changes
git stash apply

// Restore only a specific change
git stash apply stash@{1}

This restores the changes, but keeps the stash in the list.

git stash pop

Same as apply, but removes it from the stash list.

git stash push -m "styling users page"

Saves with a name.

git stash drop stash@{0}

Deletes a specific stash entry.

git stash clear

Clears all stashes.

git stash -u # or --include-untracked

// Include ignored files
git stash -a # or --all

By default, stash doesn't include untracked files. This saves everything.

Bisect

git bisect start

Start bisecting, meaning that Git will perform a binary search between your commits to find a bug.

git bisect bad

This marks the current commit bad. If the issue exists in the current state of the branch use this.

git bisect bad <commit-hash>

If the issue exists in a commit created previouly, specify the hash of it.

git bisect good <commit-hash>

This sets the last commit where the bug didn't exist yet.

// If the current commit has the bug
git bisect bad

// If the current commit does not have the bug
git bisect good

Test if the bug exists in the commit Git picked and let Git know.

Git keeps narrowing down the range with each answer until it says:
"<commit-hash> is the first bad commit"

git bisect reset

Once you found the bug this will take you back to where you were before.

Hooks

pre-commit.sample
commit-msg.sample
pre-push.sample

Every Git repository has a hidden .git/hooks/ folder, inside of it there are a bunch of sample hook files like these. To activate one, remove the .sample extension and make sure the file is executable. Hooks are just executable scripts, they can be written in Bash, Python, etc.

Common hooks are:

Hook Name When It Runs Typical Use
pre-commit Before a commit is created Run tests, lint code, block bad commits
commit-msg After the commit message is entered Check message format (e.g., require ticket numbers)
pre-push Before pushing to a remote repository Run tests, prevent bad pushes
post-merge After a merge happens Install dependencies, re-run builds
pre-rebase Before a rebase starts Warn about rebasing protected branches

#!/bin/bash

echo "Running pre-commit checks..."
npm run lint
if [ $? -ne 0 ]; then
  echo "Linting failed! Commit aborted."   
  exit 1   
fi

This is an example of a pre-commit hook. You need to create a ".git/hooks/pre-commit" file with this content, make it executable and Git will run it automatically. Git blocks the commit if the code doesn't pass formatting.

// Create a directory in the repo for shared hook scripts
mkdir .githooks

// Configure Git to use the custom hooks path
git config core.hooksPath .githooks

// Make sure scripts are executable
chmod +x .githooks/*

// Commit the hooks
git add .githooks
git commit -m "Add shared Git hooks"

// When someone else clones the repo, they also need to configure Git to use the custom hooks
git config core.hooksPath .githooks

Hooks are local meaning that they are not shared automatically when someone clones the repo. This issue can be solved by using shared hooks directories or tools like Husky (for JavaScript projects).

Hooks can slow down workflows if overused (for example running 5-minute tests on every commit). Since they're local, make sure to document the right hook usage in README.md unless the setup is automated.

Submodules

git submodule add https://github.com/example/library.git libs/library

This clones the external repo into libs/library directory and records the submodule info into the .gitmodules file and the main repo's index. This way you can embed one repository as a folder inside another one, but manage it independently.

git clone https://gitlab.com/davidvarga/it-cheat-sheets.git
git submodule update --init --recursive

When you clone a repo that has submodules, you need to initialize and fetch them. "--recursive" pulls nested submodules if there's any.

// From the submodule
cd libs/library
git pull origin main

// From the parent repo
git submodule update --remote

When the submodule repo has new changes this pulls and updates submodules.

git add libs/library
git commit -m "Update submodule to latest commit"

If you update a submodule (e.g., checkout a newer commit), you need to commit the new reference in the main repo. The main repo only tracks the commit pointer and not the full history.

git submodule deinit libs/library
git rm libs/library
rm -rf .git/modules/libs/library

Removes a submodule.

Subtrees

git subtree add --prefix=libs/library https://github.com/example/library.git main --squash

This downloads the code and commits it directly into your project. It lets you embed the full history of one repository inside another repository, no extra management, no separate clone, no special commands needed for users. It's a full copy, not just a pointer like submodules. "--prefix" defines where the repo's files go to. "--squash" condenses the external repo's history into a single commit (optional, you can omit "--squash" if you want the full history).

git subtree pull --prefix=libs/library https://github.com/example/library.git main --squash

Merges the new changes into the folder.

git subtree push --prefix=libs/library https://github.com/example/library.git main

This pushes local changes back to the original repo.

Subtrees are simpler and more robust for many use cases than submodules, especially if you want teams to just "git clone" and start working immediately.

Worktrees

git worktree add ../feature-x feature-x

This checks out branch "feature-x" into a new folder "../feature-x". If "feature-x" doesn't exist yet Git creates it. Now there are two folders, the original repo (maybe on main) and the new worktree (on feature-x). This is useful for creating additional worktrees from a repository instead of having multiple clones.

This way you can:

git worktree list

Lists all worktrees.

git worktree remove ../feature-x

This unregisters the worktree from git, but you have to manually delete the folder if you want to.

Reflog

git reflog

This shows a log of where the HEAD and branch references have been, including commits that might have been "lost" due to resets, rebases, or checkouts. Even if a commit is no longer part of the visible history (e.g., you did "git reset" or deleted a branch), Git still knows it existed and reflog is how to find it. It's like a private undo history for your local repo only.

Each entry shows:

git reflog
git checkout <commit-hash>

// Another Method
git reset --hard HEAD@{2}

After a "git reset --hard" lost changes can be recovered with reflog like this.

git reflog
git reset --hard HEAD@{before-rebase}

This undoes a mistaken rebase.

git reflog
git checkout -b recovered-branch <commit-hash>

This restores a deleted branch.

git reflog expire --expire=now --all
git gc --prune=now

Reflog is local and temporary. It eventually expires (default: 90 days). This clears it manually.

Ignoring files

# Ignore OS files
.DS_Store
Thumbs.db

# Ignore logs
*.log

# Ignore compiled output
/dist
/build
*.o
*.class

# Ignore environment files
.env
*.key

# Ignore dependencies
node_modules/
vendor/

# Ignore IDEs/editors
.vscode/
.idea/
*.swp

This is an example of a .gitignore file. It tells Git which files and folders to ignore, meaning that Git will not show these files as untracked, and won't let you accidentally commit them. These files can be placed to the root of the repository and also in the subfolders.

git rm --cached index.html

If a file is already committed Git won't ignore it just because now it's added to .gitignore, but this command will make Git stop tracking it and .gitignore will prevent it from being re-added.

Pattern rules of .gitignore files:

# Ignore everything in wp-content
wp-content/*
# Allow plugins directory
!wp-content/plugins/
# Ignore everything inside plugins
wp-content/plugins/*
# Allow my-custom-* plugins
!wp-content/plugins/my-custom-*/

The rules in this example are going to ignore everything in the wp-content directory except all the directories in "wp-content/plugins/" with names starting as "my-custom-".

git config --global core.excludesfile ~/.gitignore_global

This creates a global .gitignore file for the whole system (e.g., ignore .DS_Store everywhere).

git status --ignored

// Or to check a specific file
git check-ignore -v directory1/directory2/index.html

This shows what's being ignored in the repository.

Git also supports a local-only ignore file located at ".git/info/exclude" that behaves like .gitignore, but it's not committed. It's great for local custom ignore rules.

Tags

git tag v1.0.0

This tags the current commit, no extra metadata. Tags point to a specific commit, just like branches, but they don't move. They're typically used for things like versioning (e.g., v1.0.0), marking milestones (beta, release-candidate, production), bookmarking stable commits.

git tag -a v1.0.0 -m "Release version 1.0.0"

Stores metadata: tagger's name, date, message, and GPG signature (optional). Recommended for releases.

git tag

// View a specific tag
git show v1.0.0

Lists tags.

git tag -a v0.9.0 <commit-hash> -m "Older release"

This tags a previous commit.

git push origin --tags

// Push a specific tag
git push origin v1.0.0

By default "git push" doesn't push tags, so it needs to be manually defined.

// Locally
git tag -d v1.0.0

// Remotely
git push origin --delete tag v1.0.0

Deletes tags.

git checkout v1.0.0

This moves the HEAD to the commit pointed to by the tag v1.0.0, but you're not on a branch anymore. This puts you in what's called a "detached HEAD" state, which means that normally, HEAD points to the tip of a branch (like main, dev, etc.). But when you checkout a tag, HEAD points directly to a commit, not a branch. It's like saying: "Show me what the repo looked like at that exact moment in history." It lets you view the code, build or test it and even make changes and commit (temporarily) but any new commits won't belong to any branch unless you explicitly create one.

git checkout v1.0.0
git checkout -b hotfix/v1.0.1

In this example you checkout to tag v1.0.0, so you can inspect or debug it and create a hotfix based on it. It creates a proper branch that you can safely commit.

Cleaning up

git clean -n

Shows what would be deleted (safe preview).

// Remove untracked files
git clean -f

// Also remove untracked directories
git clean -fd

// Also remove ignored files
git clean -xfd

This removes files and directories that are not tracked by Git (i.e., not staged or committed).

git reset --hard
git clean -fd

Discards all changes to tracked files, reset to the last commit and cleans the workspace.

// List them
git stash list

// Remove one
git stash drop stash@{0}

// Remove all stashes
git stash clear

Cleans up stashes.

git gc

Git stores all kinds of history and metadata. Over time your repo can get bloated. gc (garbage collection) compresses and cleans internal data. It's worth to run it occasionally or after deleting branches, large files, or rebasing.

This document's home with other cheat sheets you might be interested in:
https://gitlab.com/davidvarga/it-cheat-sheets

Sources:
https://www.wikipedia.org/
https://stackoverflow.com/
https://dev.to/
https://www.w3schools.com/
https://git-scm.com/docs

License:
GNU General Public License v3.0 or later