DEV Community

Cover image for From Zero to Advanced: The Git Version Control System ☕️
Yasin ATEŞ
Yasin ATEŞ

Posted on

From Zero to Advanced: The Git Version Control System ☕️

What is Git? 🤔

Git is a distributed version control system used to track changes in files over time during software development.

A Brief History of Git 📖

Git was developed by Linus Torvalds, the creator of Linux, in 2005.

The Linux project had been using BitKeeper, a proprietary version control system, under a free license since 2002. In 2005, BitKeeper's company alleged that Linux developer Andrew Tridgell had reverse-engineered their protocol, and subsequently revoked the project's free license. Linus Torvalds then decided to write his own version control system, and Git was born.

Why Version Control? 🧐

When working alone, manual backups may be enough. However, when multiple people work on the same project, tracking who changed what and when becomes difficult. A version control system automates this process and solves the following problems:

  • Maintains different versions of files
  • Enables multiple developers to work on the same codebase concurrently
  • Makes it easy to revert to a previous version when something goes wrong
  • Provides a complete audit trail of changes

How Does Git Work? ⚙️

How Does Git Work

Git consists of three fundamental layers:

  • Working Directory: The local directory where your files reside and where you actively work.
  • Staging Area: An intermediate area where changes are staged before being committed to the repository. Think of it as a preparation area for the next commit.
  • Repository (Repo): The repository where committed changes are stored. It is the .git folder in your project directory.

The basic workflow is:

  1. You make changes in the working directory
  2. You add those changes to the staging area with git add
  3. You create a commit from the staged changes with git commit

Installation 💻

You can download and install the appropriate version for your operating system from git-scm.com.

To verify the installation, run the following command in your terminal:

git --version
Enter fullscreen mode Exit fullscreen mode

Configuration (git config) 🔧

Before using Git, you should configure your user information. This information is recorded with every commit.

★ Global Configuration

The default configuration that applies to all Git repositories on your machine is defined as follows:

git config --global user.name "Yasin"
git config --global user.email "yasinatesim@gmail.com"
Enter fullscreen mode Exit fullscreen mode

★ Local Configuration

The configuration that applies only to the current repository. If a global setting with the same name exists, the local setting overrides it:

git config --local user.name "Beril"
git config --local user.email "beril@yasinates.com"
Enter fullscreen mode Exit fullscreen mode

💡 In this example, Git uses "Yasin" by default for all repositories on this machine, but in one specific project, the local configuration overrides the global one, and Git uses "Beril" for that repository.

★ Useful Additional Settings

# Set default branch name to main
git config --global init.defaultBranch main

# Set VS Code as the default editor
git config --global core.editor "code --wait"

# Enable colored output
git config --global color.ui auto

# Use rebase by default during pull
git config --global pull.rebase true
Enter fullscreen mode Exit fullscreen mode

★ Git Alias (Shortcuts)

You can define shortcuts for frequently used commands:

git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.cm "commit -m"
git config --global alias.lg "log --oneline --graph --decorate --all"
Enter fullscreen mode Exit fullscreen mode

Now you can type git st instead of git status, and git co instead of git checkout.

★ Checking Configurations

To check all configurations:

git config --list
Enter fullscreen mode Exit fullscreen mode

Creating a Repository (git init) 📂

Creating a Git Repository

To turn your working directory into a Git repository, navigate to the target directory in the terminal and run:

git init
Enter fullscreen mode Exit fullscreen mode

This command creates a hidden folder named .git in your directory. All of Git's data (commit history, branch information, configuration, etc.) is stored in this folder. The default branch name will be either master or main, depending on your Git version and configuration. Nowadays, platforms like GitHub default to main. You may also see the branch name displayed in your terminal prompt (for example, (main)), depending on your shell configuration.

💡 The concept of branches is covered in more detail later in this article.

Tracking File Status (git status) 🔍

Git Tracking File Status

To see the current status of the repository:

git status
Enter fullscreen mode Exit fullscreen mode

This command shows which files have changed, which files are in the staging area, and which files are not yet tracked by Git (untracked).

Right after git init, it will report that there are no commits yet and suggest using git add. Think of git status as your compass in Git — when in doubt, run it.

Adding Changes to the Staging Area (git add) ➕

Let's start by creating a file. Create a file named names.txt in your working directory:

Eda
Beyza
Tuvana
Melis
Enter fullscreen mode Exit fullscreen mode

When you run git status, Git will show this file as untracked. To add this file to the staging area, in other words, to tell Git to track it:

Add a single file:

git add names.txt
Enter fullscreen mode Exit fullscreen mode

Add all changes at once:

git add .
Enter fullscreen mode Exit fullscreen mode

⚠️ While git add . is practical, it can accidentally add irrelevant files to the staging area. Especially in large projects, it's a good habit to check git status first.

Reviewing Staging Area Changes 👀

Run git status again after the git add command:

git status
Enter fullscreen mode Exit fullscreen mode

You will now see your file listed under "Changes to be committed." Git automatically classifies the type of changes in the staging area.

★ Change Types

New File (new file): A newly added file that was not previously tracked by Git.

Changes to be committed:
    new file:   names.txt
Enter fullscreen mode Exit fullscreen mode

Modified File (modified): A file previously tracked by Git whose content has changed.

Changes not staged for commit:
    modified:   names.txt
Enter fullscreen mode Exit fullscreen mode

To discard the modification:

git restore names.txt
Enter fullscreen mode Exit fullscreen mode

Deleted File (deleted): A file tracked by Git that has been deleted.

Changes not staged for commit:
    deleted:    names.txt
Enter fullscreen mode Exit fullscreen mode

To restore the deleted file:

git restore names.txt
Enter fullscreen mode Exit fullscreen mode

★ Removing from the Staging Area

To remove a file that was accidentally added to the staging area (unstage it):

git restore --staged names.txt
Enter fullscreen mode Exit fullscreen mode

💡 In older resources, you may see git reset HEAD <file> for unstaging. In modern Git, git restore --staged is recommended instead.

If you want Git to completely stop tracking a file (the file is not deleted from disk, but Git no longer tracks it):

git rm --cached names.txt
Enter fullscreen mode Exit fullscreen mode

This command is especially useful for removing files from version control after they have been added to .gitignore.

★ More About the "git add" Command

Partial staging (-p): Shows changes in the file hunk by hunk and asks which ones you want to add:

git add -p
Enter fullscreen mode Exit fullscreen mode

This command offers options for each change block:

  • y — Add this block
  • n — Do not add this block
  • s — Split the block into smaller pieces
  • q — Quit

This allows you to add only part of the changes in a file to the staging area.

📌 For all parameters of the git add command: git-scm.com/docs/git-add

Committing Changes (git commit) 📸

Saving to Git

To create a commit from the staged changes:

git commit
Enter fullscreen mode Exit fullscreen mode

Running this command without parameters opens the default text editor (usually Vim) and prompts you to enter a commit message. To exit Vim, type :wq and press Enter.

★ "git commit" Command Parameters

-m parameter — Lets you write the commit message directly in the terminal:

git commit -m "Names file created"
Enter fullscreen mode Exit fullscreen mode

-a parameter — Commits only modified tracked files directly, without using git add. ⚠️ It does not add new (untracked) files:

git commit -am "Names edited"
Enter fullscreen mode Exit fullscreen mode

--amend parameter — Edits the most recent commit message or updates the most recent commit with additional changes:

git commit --amend -m "Corrected commit message"
Enter fullscreen mode Exit fullscreen mode

⚠️ --amend changes the commit hash. If you amend commits that have already been pushed, a force push is required. Use this carefully on shared branches.

📌 For all parameters of the git commit command: git-scm.com/docs/git-commit

★ How to Write a Good Commit Message?

💡 This is one of the most important sections of the article. One of the most common mistakes in Git is writing poor commit messages. A well-written commit message makes the project's history easier to read and makes collaboration much easier.

What makes a good commit message?

  • Short and descriptive (under 50 characters is ideal)
  • Describes what changed
  • Use imperative verbs such as: "fix", "add", "update"

Example:

  • ❌ Bad: changes
  • ✅ Good: Fix validation error in user login form

💡 In large projects, teams, or open source projects, linters are often used to standardize commit messages.

★ Commit Message Standards (Conventional Commits)

In larger projects, commit messages are often written according to a standard format. These standards:

  • Make commit history easier to read
  • Enable automatic changelog generation
  • Can integrate with CI/CD processes
  • Are compatible with semantic versioning

The Conventional Commits standard is widely used for this purpose.

★ Commit Format

feat(auth): add user login system
fix(player): fix audio dropout issue
docs(readme): update installation steps
Enter fullscreen mode Exit fullscreen mode
  • feat — A new feature
  • fix — A bug fix
  • docs — Documentation changes
  • style — Code format changes (lint, whitespace, etc.)
  • refactor — Refactoring that does not change behavior
  • test — Tests added or updated
  • chore — Build, config, or utility tasks
  • ci — CI/CD pipeline changes
  • perf — Performance improvements
  • build — Build system or dependency changes

To automatically check this standard:

  • commitlint → lints commit messages
  • Commitizen → provides interactive help when writing commits

💡 Every commit must be reachable from a branch, tag, or HEAD reference. Commits that are no longer referenced are eventually cleaned up by Git's garbage collection mechanism after a certain amount of time (often around 90 days by default). Garbage collection is covered later in this article.

Viewing Commit History (git log) 📜

Viewing Commit History

To view the commit history:

git log
Enter fullscreen mode Exit fullscreen mode

Example output:

commit a1b2c3d4e5f6 (HEAD -> main)
Author: Rabia Kaya <rabia@example.com>
Date:   Mon Jun 9 2025

    Names file created
Enter fullscreen mode Exit fullscreen mode

★ Frequently Used Parameters

git log --oneline                        # Each commit shown on a single line
git log --graph                          # Branch structure drawn as ASCII art
git log --oneline --graph --decorate --all  # Visual history with branch/tag pointers
git log -n 5                             # Only the last 5 commits
git log --author="Rabia"                 # Commits by a specific author
git log -- names.txt                     # History of a specific file
git log --follow -- names.txt            # Tracks history even if the file was renamed
git log --since="2025-01-01"             # Commits after a specific date
Enter fullscreen mode Exit fullscreen mode

The commit hashes shown in git log output (for example, a1b2c3d) can be used with commands such as git checkout, git reset, and git revert to reference specific commits.

📌 For all parameters of the git log command: git-scm.com/docs/git-log

Viewing Differences (git diff) 🔎

Viewing Differences of Git

To inspect line-by-line changes:

git diff
Enter fullscreen mode Exit fullscreen mode

This command shows the differences between changes in the working directory and the staging area.

★ Different Comparison Scenarios

git diff                         # Working directory vs staging area
git diff --staged                # Staging area vs last commit
git diff --cached                # Equivalent to --staged
git diff HEAD                    # Working directory vs last commit
git diff <commit1> <commit2>     # Differences between two commits
git diff main..new-feature       # Differences between two branches
git diff -- names.txt            # Differences for a specific file only
Enter fullscreen mode Exit fullscreen mode

Reading the output:

- Tuvana       # Deleted line (red)
+ Beril        # Added line (green)
Enter fullscreen mode Exit fullscreen mode

📌 For all parameters of the git diff command: git-scm.com/docs/git-diff

File Deletion and Moving (git rm & git mv) 🗑️

★ git rm

To delete a file tracked by Git from both your filesystem and Git’s index:

git rm names.txt
git commit -m "names.txt deleted"
Enter fullscreen mode Exit fullscreen mode

To remove a file from version control only, without deleting it from disk:

git rm --cached names.txt
Enter fullscreen mode Exit fullscreen mode

★ git mv

To move or rename a file:

git mv old-name.txt new-name.txt
git commit -m "File renamed"
Enter fullscreen mode Exit fullscreen mode

💡 Git actually detects file renames through content similarity. git mv does the same thing as deleting the file and adding it again with a new name behind the scenes, but using a single command is more convenient.

Branching (git branch) 🌿

Git Branching

Branches allow you to create independent workspaces within the same project. A branch is an independent line of development created from the current state of another branch. This lets you develop features or fix bugs without affecting the main codebase.

Historically, Git initialized repositories with master as the default branch name. Today, many teams and platforms use main instead. In this article, the examples use main.

★ Listing Branches

git branch              # Lists local branches
git branch -a           # Lists all, including remote-tracking branches
Enter fullscreen mode Exit fullscreen mode

💡 References such as remotes/origin/main in git branch -a output are not the remote branch itself, but local tracking references updated after the last fetch or pull.

★ Creating a Branch

git branch new-names
Enter fullscreen mode Exit fullscreen mode

After running this command, if you run git branch again, you will see that the new-names branch has been created. However, you are still on the main branch; see the next section for switching branches.

★ Deleting a Branch

git branch -d new-names     # Deletes a merged branch
git branch -D new-names     # Force-deletes it (even if not merged)
Enter fullscreen mode Exit fullscreen mode

★ Renaming a Branch

git branch -m old-name new-name
Enter fullscreen mode Exit fullscreen mode

Switching Between Branches and Commits (git checkout / git switch) 🔄

In Git version 2.23, the two different responsibilities of git checkout (branch switching and file restoration) were separated. git switch is recommended for switching branches, and git restore is recommended for restoring files. However, git checkout still works for both.

★ Switching Between Branches

git checkout new-names
Enter fullscreen mode Exit fullscreen mode

or in Git 2.23+:

git switch new-names
Enter fullscreen mode Exit fullscreen mode

Shortcut — Create and Switch:

To create a new branch and switch to it without running git branch separately:

git checkout -b new-names
# or
git switch -c new-names
Enter fullscreen mode Exit fullscreen mode

★ Switching to a Commit

You can return to the state of any commit using its hash from the git log output:

git checkout a1b2c3d
Enter fullscreen mode Exit fullscreen mode

⚠️ This places you in a detached HEAD state. In this state, commits you make are not attached to any branch, and they can become difficult to recover later if you switch away without creating a branch. If you want to preserve your changes, create a new branch:

git checkout -b rescue-branch
Enter fullscreen mode Exit fullscreen mode

💡 The HEAD and detached HEAD concepts are explained in more detail later in this article.

★ Getting a File from a Specific Commit or Branch

To bring a file's state from a specific commit or branch into your working directory:

# Get the file state from a specific commit
git checkout a1b2c3d -- names.txt

# Get the file state from another branch
git checkout main -- names.txt
Enter fullscreen mode Exit fullscreen mode

This command copies the file into your working directory and staging area. You do not leave your current branch.

💡 In older resources, you may see git checkout -- <file> for file restoration. In modern usage, git restore is recommended instead.

Merging Branches (git merge) 🤝

Git Merging Branches

The git merge command is used to merge changes from one branch into another.

Example scenario:

# Create a new branch and switch to it
git checkout -b new-names

# Add "Rabia" to names.txt and commit the change
git add .
git commit -m "Rabia added"

# Switch back to main branch
git checkout main

# Merge new-names branch into main
git merge new-names
Enter fullscreen mode Exit fullscreen mode

★ Merge Types

Fast-Forward Merge: If there are no new commits on the target branch (main), Git simply moves the branch pointer forward. No new merge commit is created:

main:    A → B → C
                  ↑ (fast-forward)
feature:          C → D → E
Enter fullscreen mode Exit fullscreen mode

To prevent a fast-forward merge and always create a merge commit:

git merge --no-ff new-names
Enter fullscreen mode Exit fullscreen mode

Three-Way Merge: When both branches have new commits, Git finds a common ancestor, performs a three-way comparison, and creates a new merge commit.

💡 To keep your feature branch up to date, you can regularly integrate changes from the main branch. This can be done with either merge or rebase:

git checkout new-names
git merge main
# or
git rebase main
Enter fullscreen mode Exit fullscreen mode

Conflicts (Git Conflicts) ⚡

Conflicts of Git

When the same lines of the same file are modified differently in two branches, a conflict occurs.

Example scenario:

Your names.txt file on the main branch looks like this:

Eda
Beyza
Tuvana
Melis
Enter fullscreen mode Exit fullscreen mode

1. Create a new branch and change Tuvana on line 3 to Beril:

git checkout -b conflict-example
# names.txt → Change the line "Tuvana" to "Beril"
git commit -am "Tuvana replaced with Beril"
Enter fullscreen mode Exit fullscreen mode

2. Switch back to main and change the same line to a different name:

git checkout main
# names.txt → Change the line "Tuvana" to "Rabia"
git commit -am "Tuvana replaced with Rabia"
Enter fullscreen mode Exit fullscreen mode

3. Try to merge:

git checkout conflict-example
git merge main
Enter fullscreen mode Exit fullscreen mode

Git shows the following conflict message:

CONFLICT (content): Merge conflict in names.txt
Automatic merge failed; fix conflicts and then commit the result.
Enter fullscreen mode Exit fullscreen mode

When you open the file, Git marks the conflicting sections:

Eda
Beyza
<<<<<<< HEAD
Beril
=======
Rabia
>>>>>>> main
Melis
Enter fullscreen mode Exit fullscreen mode
  • Between <<<<<<< HEAD and =======: Changes from your branch
  • Between ======= and >>>>>>> main: Changes from the branch being merged

To resolve the conflict:

  1. Edit the file and keep the content you want
  2. Remove the <<<<<<<, =======, >>>>>>> markers
  3. Save and commit:
git add names.txt
git commit -m "Conflict resolved, updated as Beril"
Enter fullscreen mode Exit fullscreen mode

★ Quick Fix: Ours or Theirs

If you want to directly choose one side in a conflict:

# Accept your branch's changes
git checkout --ours names.txt

# Accept the incoming changes
git checkout --theirs names.txt

git add names.txt
git commit -m "Conflict resolved"
Enter fullscreen mode Exit fullscreen mode

★ Aborting the Merge (--abort)

To completely cancel the merge before resolving any conflicts:

git merge --abort
Enter fullscreen mode Exit fullscreen mode

This command cancels the merge and restores your files to their pre-merge state.

★ Continuing the Merge (--continue)

After resolving the conflicts in the file and adding them to the staging area with git add:

git merge --continue
Enter fullscreen mode Exit fullscreen mode

This is similar to running git commit, but it explicitly resumes the in-progress merge.

💡 Editors like VS Code automatically detect conflict markers and simplify resolution with buttons like "Accept Current", "Accept Incoming", and "Accept Both". Alternatively, you can use external tools configured with the git mergetool command. Many IDEs (WebStorm, PhpStorm, PyCharm, etc.) also provide their own merge editors to make conflict resolution easier.

Rebasing Commits onto Another Branch (git rebase) 🔀

Rebasing Commits onto Another Branch

Rebase creates a linear history by re-applying a branch's commits onto the tip of another branch.

git checkout new-feature
git rebase main
Enter fullscreen mode Exit fullscreen mode

This command does the following:

  1. Temporarily rewinds the new-feature branch
  2. Takes the latest commits from the main branch
  3. Re-applies new-feature's commits one by one on top of main's latest commit

★ Interactive Rebase

To edit, combine, reorder, or delete recent commits:

git rebase -i HEAD~3
Enter fullscreen mode Exit fullscreen mode

In the editor that opens, you can change the command at the beginning of each commit:

pick a1b2c3d Names added
pick d4e5f6g New names edited
pick h7i8j9k Beyza added
Enter fullscreen mode Exit fullscreen mode

Available commands:

  • pick — Keep the commit as is
  • reword — Change the commit message
  • squash — Combine with the previous commit (merges messages)
  • fixup — Combine with the previous commit (discards this commit's message)
  • drop — Completely remove the commit
  • edit — Stop to edit the commit

★ Rebase --onto

To detach a branch from its source branch and move it onto another branch:

git rebase --onto main feature bugfix
# Detach bugfix branch from feature and place it onto main
Enter fullscreen mode Exit fullscreen mode

★ Rebase vs Merge

Merge

  • Merges branches and creates a merge commit
  • Does not rewrite commit history (safe)
  • If there is a conflict, it is resolved all at once

Rebase

  • Re-applies commits onto another branch's tip
  • Creates a cleaner history
  • Rewrites commit hashes
  • If conflicts occur, they are resolved for each commit separately
  • Typically used on personal branches

⚠️ This can cause problems on shared branches. Avoid rebasing branches that other people are already using.

If a conflict occurs during rebase:

# Resolve the conflict in the file, then:
git add .
git rebase --continue

# or abort the rebase entirely:
git rebase --abort
Enter fullscreen mode Exit fullscreen mode

The HEAD Concept 🎯

Git HEAD

HEAD is the answer to the question "where are you right now?" in Git. It points to the commit you are currently working on.

Normally, HEAD points to a branch and corresponds to that branch's latest commit:

cat .git/HEAD
# Output: ref: refs/heads/main
Enter fullscreen mode Exit fullscreen mode

This means "HEAD → main → latest commit." When you make a new commit, HEAD moves forward to the new commit automatically.

★ Detached HEAD

When you switch directly to a commit with git checkout <commit-hash>, HEAD no longer points to a branch, but directly to that commit:

git checkout a1b2c3d
Enter fullscreen mode Exit fullscreen mode

In this state, since any commits you make are not attached to a branch, they can become difficult to recover later when you switch to another branch. If you want to preserve your changes:

git checkout -b new-branch-name
Enter fullscreen mode Exit fullscreen mode

💡 Commits made in detached HEAD do not disappear immediately. You can find their hashes with git reflog and recover them. However, after the reflog retention period expires (90 days by default), they may be cleaned up by garbage collection.

Tagging (git tag) 🏷️

git tag

Tags are used to add permanent labels (bookmarks) to specific commits. They are typically used to mark version numbers. Unlike branches, tags are fixed and do not move.

★ Lightweight Tag

A simple pointer that stores no additional information:

git tag v1.0
Enter fullscreen mode Exit fullscreen mode

★ Annotated Tag

A tag that includes author, date, and message information. Recommended for version tags:

git tag -a v1.0 -m "First stable release"
Enter fullscreen mode Exit fullscreen mode

★ Adding a Tag to a Specific Commit

git tag -a v0.9 a1b2c3d -m "Beta release"
Enter fullscreen mode Exit fullscreen mode

★ Tag Operations

git tag                    # List all tags
git show v1.0              # Show tag details
git tag -d v1.0            # Delete local tag
git push origin v1.0       # Push tag to remote repository
git push origin --tags     # Push all tags
Enter fullscreen mode Exit fullscreen mode

Undoing Changes ↩️

Undoing Changes of Git

Use git restore to discard changes in the working directory or unstage files from the index:

# Restore the file to its last committed state (working directory changes are lost)
git restore names.txt

# Remove the file from the staging area, but keep the file unchanged
git restore --staged names.txt
Enter fullscreen mode Exit fullscreen mode

★ git reset

Used to undo commit history. git reset moves the current branch reference backward to an earlier commit; depending on the mode used, the staging area and working directory are adjusted accordingly.

It has three modes:

--soft: The commit is undone, but the changes remain in the staging area. Useful when you want to correct the commit message or adjust the contents of the commit:

git reset --soft HEAD~1
Enter fullscreen mode Exit fullscreen mode

--mixed (default): The commit is undone, the changes remain in the working directory, but they are removed from the staging area:

git reset HEAD~1
# or
git reset --mixed HEAD~1
Enter fullscreen mode Exit fullscreen mode

--hard: The commit and all changes are completely deleted:

git reset --hard HEAD~1
Enter fullscreen mode Exit fullscreen mode

⚠️ The --hard parameter permanently deletes changes. Be careful.

Commits removed by git reset --hard can often still be recovered with git reflog, as long as they have not been pruned by garbage collection. By default, unreachable commits are typically recoverable for some time (often around 90 days, depending on reflog and GC settings). Good commit messages also make lost commits much easier to identify in the reflog. If your commit messages are all the same, finding the right one becomes much harder 🥲

HEAD~1 means the previous commit. HEAD~3 means three commits back. You can also use a specific commit hash:

git reset --soft a1b2c3d
Enter fullscreen mode Exit fullscreen mode

★ git revert

Creates a new commit that undoes the changes introduced by a specific commit. It does not erase history, so it can be used safely on shared branches:

git revert a1b2c3d
Enter fullscreen mode Exit fullscreen mode

Reverting a merge commit:

When reverting merge commits, you need to specify which parent to use as the base with the -m parameter:

git revert -m 1 <merge-commit-hash>
Enter fullscreen mode Exit fullscreen mode

★ Which one should you use?

  • File changes you have not committed yetgit restore
  • Commits you have not pushed yetgit reset
  • Safely undo pushed commitsgit revert

Reference History (git reflog) 🕵️

Reference History of Git

git reflog is a safety net that records updates to HEAD and other references locally. Even after running a dangerous command like git reset --hard, you can often find lost commits here:

git reflog
Enter fullscreen mode Exit fullscreen mode

Example output:

a1b2c3d HEAD@{0}: reset: moving to HEAD~1
f4e5d6c HEAD@{1}: commit: Beril added
b7a8c9d HEAD@{2}: commit: Names file created
Enter fullscreen mode Exit fullscreen mode

You can find the hash of the lost commit and go back to it:

git reset --hard f4e5d6c
Enter fullscreen mode Exit fullscreen mode

💡 Reflog entries are kept for 90 days by default.

Stashing Changes (git stash) 📦

Shelving Changes of Git

Used to temporarily save changes you are working on without committing them. This is very useful when an urgent task comes up and you need to switch branches.

★ Basic Commands

git stash                              # Stash changes
git stash push -m "Description"        # Stash with a message
git stash -u                           # Include untracked files
git stash --all                        # Stash everything, including ignored files
git stash list                         # List stashes
git stash show                         # Show a summary of the latest stash
git stash show -p                      # Show the detailed diff of the latest stash
Enter fullscreen mode Exit fullscreen mode

💡 In older resources, you may see git stash save "Description". In modern Git, git stash push -m "Description" is recommended instead.

⚠️ The default git stash command does not include untracked files. To save those as well, use the -u parameter.

★ Restoring

git stash pop                    # Apply the latest stash and remove it from the stash list
git stash apply                  # Apply the latest stash and keep it in the stash list
git stash apply stash@{2}        # Apply a specific stash
Enter fullscreen mode Exit fullscreen mode

★ Cleanup

git stash drop                   # Drop the latest stash
git stash drop stash@{1}         # Drop a specific stash
git stash clear                  # Clear all stashes
Enter fullscreen mode Exit fullscreen mode

★ Example Scenario

You are adding new names to names.txt. Suddenly, an urgent fix is needed:

# Stash the work in progress
git stash push -m "I was updating Tuvana's information"

# Switch to main and make the fix
git checkout main
git commit -am "Urgent fix applied"

# Switch back to your feature branch and pick up where you left off
git checkout new-feature
git stash pop
Enter fullscreen mode Exit fullscreen mode

Ignoring Files (.gitignore) 🙈

Create a .gitignore file in the project root directory to prevent Git from tracking certain files and folders:

# Dependency folders
node_modules/
vendor/

# Build outputs
dist/
build/
*.class

# Environment variables (passwords, API keys, etc.)
.env
.env.local

# IDE files
.idea/
.vscode/
*.swp

# Operating system files
.DS_Store
Thumbs.db

# Log files
*.log

# Compiled files
*.o
*.exe
Enter fullscreen mode Exit fullscreen mode

★ Important Notes

  • Files that were already being tracked by Git before the .gitignore file was created will not automatically stop being tracked when added to .gitignore. To remove these files from tracking:
git rm --cached filename.txt
Enter fullscreen mode Exit fullscreen mode
  • Empty folders are not tracked by Git. If you want to keep an otherwise empty directory in the repository, you can place an empty file named .gitkeep inside it. .gitkeep is not a Git-specific feature; it is a community convention. The file name can be anything, but .gitkeep is commonly used.

★ Global .gitignore

You can define a global .gitignore file that applies to all your projects. This is useful for system-specific files like .DS_Store, Thumbs.db, and temporary editor files:

git config --global core.excludesfile ~/.gitignore_global
Enter fullscreen mode Exit fullscreen mode

Then add your global rules to the ~/.gitignore_global file.

📌 Ready-made .gitignore templates for different project types: github.com/github/gitignore

Remote Repository (Remote) 🌐

Remote Repository of Git

So far, all operations have been local. To host the project on platforms such as GitHub, GitLab, or Bitbucket and share it with others, you use a remote repository.

★ HTTPS vs SSH

There are two common ways to connect to a remote repository:

# HTTPS — Authentication with username/password or token
git remote add origin https://github.com/user/project.git

# SSH — Authentication with SSH key (more practical)
git remote add origin git@github.com:user/project.git
Enter fullscreen mode Exit fullscreen mode

💡 To use SSH, you first need to create an SSH key and add it to your platform (GitHub, GitLab, etc.). For detailed steps: docs.github.com/en/authentication/connecting-to-github-with-ssh

★ Adding a Remote Repository

git remote add origin https://github.com/user/project.git
Enter fullscreen mode Exit fullscreen mode

Here, origin is the local alias for the remote repository. By convention, origin is commonly used, but you can choose any name you like.

★ Remote-Tracking Branch Concept

References like origin/main are local references that reflect the last fetched state of remote branches. This is not the live state of the remote server itself; it is a local reference updated after the last fetch or pull. This lets you inspect the last known state of the remote branch even while offline.

★ Listing Remote Repositories

git remote -v
Enter fullscreen mode Exit fullscreen mode

Example output:

origin  https://github.com/user/project.git (fetch)
origin  https://github.com/user/project.git (push)
Enter fullscreen mode Exit fullscreen mode

★ Changing a Remote Repository URL

git remote set-url origin https://github.com/user/new-project.git
Enter fullscreen mode Exit fullscreen mode

★ Removing a Remote Repository

git remote remove origin
Enter fullscreen mode Exit fullscreen mode

Cloning (git clone) 📥

To copy a remote repository to your local machine:

git clone https://github.com/user/project.git
Enter fullscreen mode Exit fullscreen mode

This command:

  1. Creates a folder with the project name
  2. Creates the .git folder
  3. Downloads the repository history and working tree
  4. Automatically sets up the origin remote

★ Additional Parameters

# Clone a specific branch
git clone -b branch-name https://github.com/user/project.git

# Clone with a different folder name
git clone https://github.com/user/project.git my-project

# Clone only the last commit (for speed in large projects)
git clone --depth 1 https://github.com/user/project.git
Enter fullscreen mode Exit fullscreen mode

Pulling Changes (git fetch & git pull) ⬇️

★ git fetch

Downloads changes from the remote repository but does not merge them into your local branch. This is useful when you want to review changes first:

git fetch origin
Enter fullscreen mode Exit fullscreen mode

To review changes in the remote branch after fetching:

git diff main origin/main
Enter fullscreen mode Exit fullscreen mode

If the changes look correct, merge them:

git merge origin/main
Enter fullscreen mode Exit fullscreen mode

★ git pull

Performs fetch + merge with a single command. It downloads changes from the remote repository and merges them into your local branch:

git pull origin main
Enter fullscreen mode Exit fullscreen mode

For a cleaner history, pull with rebase:

git pull --rebase origin main
Enter fullscreen mode Exit fullscreen mode

💡 To make git pull --rebase the default behavior:

git config --global pull.rebase true
Enter fullscreen mode Exit fullscreen mode

★ Pull vs Fetch

  • git fetch: Downloads changes but does not merge them. Safer because it gives you a chance to review changes first. Use it when you want to inspect changes before integrating them.
  • git pull: Downloads changes and automatically merges them. Conflicts may occur. Use it when you want to update quickly.

💡 git pull = git fetch + git merge

Pushing Changes (git push) ⬆️

To send your local commits to the remote repository:

git push origin main
Enter fullscreen mode Exit fullscreen mode

★ Setting Upstream for First Push

The -u (upstream) parameter is used when pushing a new branch for the first time. This sets the upstream tracking relationship between the local branch and the remote branch. After that, simply running git push is enough for future pushes:

git push -u origin new-feature
Enter fullscreen mode Exit fullscreen mode

To change or set the upstream of an existing branch:

git branch --set-upstream-to=origin/main main
Enter fullscreen mode Exit fullscreen mode

★ Other Parameters

git push origin --delete branch-name    # Delete remote branch
git push --force                       # or -f  ⚠️ be extra careful when using
git push --force-with-lease            # Safer force push
Enter fullscreen mode Exit fullscreen mode

⚠️ The --force parameter rewrites history on the remote repository. It can cause teammates to lose their work. If you use --force-with-lease instead, Git rejects the push if the remote branch contains commits you do not know about.

Fork and Pull Request 🍴

Contributing to open source projects

★ Fork

Copying someone else's repository into your own GitHub/GitLab account. This is done through the Fork button in the platform interface. A fork remains associated with the original repository on the hosting platform.

★ Pull Request (PR) / Merge Request (MR)

A request to have changes from your fork or branch accepted into the main repository. It is called a Pull Request on GitHub and a Merge Request on GitLab.

Typical workflow:

  1. Fork the repository
  2. Clone it to your local machine
  3. Create a new branch
  4. Make your changes and commit them
  5. Push to your fork
  6. Open a Pull Request on the platform
  7. Code review is performed
  8. If approved, it is merged into the main branch

Branching Strategies 🗺️

Common strategies for how teams use Git:

★ Git Flow

A comprehensive branching strategy, often used in long-lived projects:

  • main — Published stable version
  • develop — Development branch
  • feature/* — For new features
  • release/* — For release preparation
  • hotfix/* — For urgent fixes

★ GitHub Flow

Well suited to teams that deploy frequently:

  • main — Always in a deployable state
  • feature branch — New feature or fix
  • Pull Request — Code review and merging

★ Trunk-Based Development

Short-lived branches are used and continuously merged into the main branch (trunk). It is commonly used with continuous integration (CI) in large teams.

💡 The best strategy depends on team size, project type, and deployment frequency. For small teams, GitHub Flow is generally enough.

Cherry Picking (git cherry-pick) 🍒

Cherry Pick of Git

To take only a specific commit from another branch instead of merging the entire branch:

git cherry-pick a1b2c3d
Enter fullscreen mode Exit fullscreen mode

Example scenario:

The new-feature branch has 5 commits, but you only want to bring the bug fix from one of them into main:

git checkout main
git cherry-pick f4e5d6c
Enter fullscreen mode Exit fullscreen mode

To take multiple commits:

git cherry-pick a1b2c3d f4e5d6c
Enter fullscreen mode Exit fullscreen mode

If a conflict occurs during cherry-pick, resolve it like a normal merge conflict and continue:

git add .
git cherry-pick --continue

# or cancel:
git cherry-pick --abort
Enter fullscreen mode Exit fullscreen mode

Line-by-Line History (git blame) 🔬

Line-by-Line History of Git

To see who last changed each line of a file and when:

git blame names.txt
Enter fullscreen mode Exit fullscreen mode

Example output:

a1b2c3d4 (Beril Dumanlı     2025-01-10) Eda
f4e5d6c7 (Sitare Demir      2025-02-15) Beyza
b7a8c9d0 (Arya Çelik        2025-03-22) Tuvana
a1b2c3d4 (Beril Dumanlı     2025-01-10) Melis
Enter fullscreen mode Exit fullscreen mode

💡 In the example above, the first and last lines were changed by the same person at different positions in the file. Lines 2 and 3 in between were changed by different people.

★ Additional Parameters

git blame -L 5,10 names.txt     # Only lines 5-10
git blame -e names.txt           # Show email addresses
git blame -w names.txt           # Ignore whitespace changes
Enter fullscreen mode Exit fullscreen mode

Useful for tracing when and by whom a line was last modified.

Searching (git grep) 🔎

To search for text within the contents of files in the repository:

git grep "Beril"
Enter fullscreen mode Exit fullscreen mode

Example output:

names.txt:Beril
notes.txt:Beril's note added
Enter fullscreen mode Exit fullscreen mode

★ Additional Parameters

git grep -n "Beril"                  # Show line numbers
git grep -c "Beril"                  # Number of matches in each file
git grep -i "beril"                  # Case-insensitive search
git grep "Beril" -- "*.txt"          # Search only in .txt files
git grep "Beril" a1b2c3d             # Search in files at a specific commit
Enter fullscreen mode Exit fullscreen mode

💡 Unlike regular grep, git grep searches only files tracked by Git. It skips files listed in .gitignore, so it does not waste time scanning directories like node_modules. This makes it much faster in large projects.

Finding Bugs with Binary Search (git bisect) 🐛

Finding Bugs with Binary Search of Git

There is a bug in your project, but you do not know which commit introduced it. git bisect uses a binary search algorithm to quickly find the problematic commit among hundreds of commits.

★ Usage

# Start bisect
git bisect start

# Mark the current commit as buggy
git bisect bad

# Mark an old commit that you know was working
git bisect good a1b2c3d
Enter fullscreen mode Exit fullscreen mode

Git automatically checks out a commit in the middle. Test the project:

# If the bug exists in this commit too:
git bisect bad

# If the bug does not exist in this commit:
git bisect good
Enter fullscreen mode Exit fullscreen mode

At each step, Git cuts the search space in half. It finds the buggy commit in just a few steps:

f4e5d6c is the first bad commit
Enter fullscreen mode Exit fullscreen mode

When you are done:

git bisect reset
Enter fullscreen mode Exit fullscreen mode

💡 If there are 128 commits, git bisect finds the buggy commit in at most 7 steps. (log₂128 = 7)

Viewing Commit Details (git show) 🔍

To see the details of a single commit (author, date, message, and changes made):

git show               # Details of the last commit
git show a1b2c3d       # Details of a specific commit
git show v1.0          # Details of the commit the tag points to
Enter fullscreen mode Exit fullscreen mode

While git log shows commit history, git show displays the full details of a single commit, including the diff.

Cleaning Untracked Files (git clean) 🧹

To delete untracked files from the working directory:

git clean -n           # Show what would be deleted first (dry run)
git clean -f           # Delete untracked files
git clean -fd          # Delete files and directories
git clean -fdx         # Delete everything including files in .gitignore
Enter fullscreen mode Exit fullscreen mode

⚠️ git clean is irreversible. Always check with -n (dry run) before deleting.

Git Hooks 🪝

Hooks are scripts that run automatically on specific Git events. They are located in the .git/hooks/ folder.

★ Commonly Used Hooks

  • pre-commit — Runs before a commit (lint, format check)
  • commit-msg — Runs after the commit message is entered, before the commit is finalized (message format check)
  • pre-push — Runs before a push (for example, running tests)
  • post-merge — Runs after a merge (for example, updating dependencies)

★ Example: Lint Check Before Commit

Create a .git/hooks/pre-commit file and make it executable:

#!/bin/sh
npm run lint
if [ $? -ne 0 ]; then
  echo "Lint errors found. Commit cancelled."
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode
chmod +x .git/hooks/pre-commit
Enter fullscreen mode Exit fullscreen mode

💡 Hooks in the .git/hooks/ folder are not shared with the repository. For team-wide hook sharing, tools like Husky can be used.

Managing External Repositories (Submodule & Subtree) 📦

In large projects, you may need to include other repositories in your own project.

★ git submodule

Embeds another repository inside your repository as a reference to a specific commit. The subproject remains independent; only a specific commit is referenced:

git submodule add https://github.com/user/library.git libs/library
git commit -m "Library added as submodule"
Enter fullscreen mode Exit fullscreen mode

When a repository with submodules is cloned, submodule contents are not checked out automatically:

git clone --recurse-submodules <url>
# or after cloning:
git submodule update --init --recursive
Enter fullscreen mode Exit fullscreen mode

★ git subtree

Copies another repository's code directly into your project. It is simpler than submodule, but it increases repository size:

git subtree add --prefix=libs/library https://github.com/user/library.git main --squash
Enter fullscreen mode Exit fullscreen mode

💡 Submodule is more widely used, but managing it is more complex. Subtree is a simpler alternative. Choose based on your needs.

Git Garbage Collection (git gc) 🗑️

Over time, Git accumulates unreachable and unnecessary objects (commits, blobs, trees) in its internal structure. git gc cleans up these objects and optimizes the repository:

git gc
Enter fullscreen mode Exit fullscreen mode

★ When Does It Run?

  • Git automatically triggers gc during some commands (git merge, git rebase, etc.)
  • You can also run it manually

★ What Gets Cleaned?

  • Orphan commits not referenced by any branch, tag, or HEAD
  • Reflog entries past their retention period (default 90 days)
  • Unused Git objects
git gc --aggressive       # Deeper optimization (slow but effective)
git gc --prune=now        # Delete all unreachable objects immediately
Enter fullscreen mode Exit fullscreen mode

⚠️ Under normal circumstances, you do not need to run git gc manually. Git handles this automatically in the background.

📌 Detailed information: git-scm.com/docs/git-gc

Git Internals: A Brief Look 🧬

Git Internals

Let's take a brief look at what is inside Git's .git folder:

.git/
├── HEAD              # Reference to current branch
├── config            # Repository configuration
├── objects/          # All Git objects (commit, tree, blob)
├── refs/             # Branch and tag references
│   ├── heads/        # Local branches
│   └── tags/         # Tags
├── hooks/            # Automatically triggered scripts
└── index             # Staging area data
Enter fullscreen mode Exit fullscreen mode

★ Git's Three Fundamental Objects

  • Blob — Stores file content (does not store the file name)
  • Tree — Stores the structure of a directory (which blob corresponds to which file name)
  • Commit — Represents a snapshot; contains tree, author, date, and message information

Git traditionally identifies each object with a SHA-1 hash. This hash is generated from the object's content. Newer versions also support SHA-256.

# To view an object's content:
git cat-file -p a1b2c3d

# To view an object's type:
git cat-file -t a1b2c3d
Enter fullscreen mode Exit fullscreen mode

💡 Git internals is a large topic on its own. It will be covered in more detail in a separate article.

Git Commands Summary 📋

Git Commands Summary Table

★ Basic Commands

  • git init — Creates a new repository
  • git clone <url> — Clones a remote repository
  • git status — Shows repository status
  • git add <file> — Adds a file to the staging area
  • git add . — Adds all changes
  • git commit -m "message" — Creates a commit
  • git commit -am "message" — Commits modified tracked files
  • git show <hash> — Shows commit details

★ File Operations

  • git rm <file> — Deletes a file and stages the removal
  • git rm --cached <file> — Removes a file from version control (does not delete it from disk)
  • git mv <old> <new> — Moves/renames a file
  • git clean -fd — Deletes untracked files and directories

★ History and Diffs

  • git log — Shows commit history
  • git log --oneline --graph --decorate --all — Visual commit history
  • git diff — Shows file differences
  • git blame <file> — Shows who changed each line
  • git reflog — Shows HEAD/reference update history
  • git shortlog -sn — Commit counts by contributor

★ Branching and Merging

  • git branch <name> — Creates a new branch
  • git branch -d <name> — Deletes a branch
  • git checkout <branch> — Switches to a branch
  • git checkout -b <name> — Creates a branch and switches to it
  • git switch <branch> — Switches to a branch (modern)
  • git merge <branch> — Merges branches
  • git merge --no-ff <branch> — Always creates a merge commit
  • git merge --abort — Cancels the merge
  • git rebase <branch> — Rebases commits
  • git rebase --onto <new-base> <old-base> <branch> — Moves a branch to a different base
  • git cherry-pick <hash> — Applies a specific commit

★ Undoing

  • git restore <file> — Discards file changes
  • git restore --staged <file> — Removes a file from staging
  • git reset --soft HEAD~1 — Undoes a commit, keeps changes staged
  • git reset --hard HEAD~1 — Completely deletes a commit and its changes
  • git revert <hash> — Creates a new commit that undoes another commit

★ Temporary Storage

  • git stash — Temporarily saves changes
  • git stash push -m "message" — Saves changes with a message
  • git stash -u — Saves changes including untracked files
  • git stash pop — Restores the latest stash
  • git stash list — Lists stashes

★ Remote Repository

  • git remote add <name> <url> — Adds a remote repository
  • git remote -v — Lists remote repositories
  • git fetch — Downloads remote changes
  • git pull — Downloads and merges changes
  • git pull --rebase — Downloads and rebases changes
  • git push — Pushes commits to a remote repository
  • git push -u origin <branch> — Pushes a branch for the first time
  • git push --force-with-lease — Safer force push

★ Tagging and Searching

  • git tag <name> — Adds a tag
  • git tag -a <name> -m "message" — Adds an annotated tag
  • git grep "text" — Searches file contents
  • git bisect start — Starts bug hunting with binary search

★ Configuration Shortcuts

  • git config --global alias.st statusgit st shortcut
  • git config --global alias.co checkoutgit co shortcut
  • git config --global alias.br branchgit br shortcut
  • git config --global alias.lg "log --oneline --graph --decorate --all"git lg shortcut

📬 Feedback

While writing this article, I relied on my own notes for source selection and research, and used the "Claude Opus 4" model for proofreading and additional research. Images were generated using the "Gemini 3 Pro Preview 2k (Nano Banana Pro)" model.

I welcome feedback, suggestions, and constructive criticism on this article. If you'd like to get in touch, you can reach me through the social links on my website or via LinkedIn.

Best, Yasin 🤗

📚 Sources Used While Writing This Article

Top comments (0)