Yasin Ateş

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

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 <span class="nt">--version</span>

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 <span class="nt">--global</span> user.name <span class="s2">"Yasin"</span>
git config <span class="nt">--global</span> user.email <span class="s2">"yasinatesim@gmail.com"</span>

★ 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 <span class="nt">--local</span> user.name <span class="s2">"Beril"</span>
git config <span class="nt">--local</span> user.email <span class="s2">"beril@yasinates.com"</span>

💡 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

<span class="c"># Set default branch name to main</span>
git config <span class="nt">--global</span> init.defaultBranch main

<span class="c"># Set VS Code as the default editor</span>
git config <span class="nt">--global</span> core.editor <span class="s2">"code --wait"</span>

<span class="c"># Enable colored output</span>
git config <span class="nt">--global</span> color.ui auto

<span class="c"># Use rebase by default during pull</span>
git config <span class="nt">--global</span> pull.rebase <span class="nb">true</span>

★ Git Alias (Shortcuts)

You can define shortcuts for frequently used commands:

git config <span class="nt">--global</span> alias.st status
git config <span class="nt">--global</span> alias.co checkout
git config <span class="nt">--global</span> alias.br branch
git config <span class="nt">--global</span> alias.cm <span class="s2">"commit -m"</span>
git config <span class="nt">--global</span> alias.lg <span class="s2">"log --oneline --graph --decorate --all"</span>

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 <span class="nt">--list</span>

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

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

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

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

Add all changes at once:

git add <span class="nb">.</span>

⚠️ 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

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

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

Changes not staged for commit:
    modified:   names.txt

To discard the modification:

git restore names.txt

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

Changes not staged for commit:
    deleted:    names.txt

To restore the deleted file:

git restore names.txt

★ Removing from the Staging Area

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

git restore <span class="nt">--staged</span> names.txt

💡 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 <span class="nb">rm</span> <span class="nt">--cached</span> names.txt

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 <span class="nt">-p</span>

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

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 <span class="nt">-m</span> <span class="s2">"Names file created"</span>

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

git commit <span class="nt">-am</span> <span class="s2">"Names edited"</span>

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

git commit <span class="nt">--amend</span> <span class="nt">-m</span> <span class="s2">"Corrected commit message"</span>

⚠️ --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
  • 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

Example output:

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

    Names file created

★ Frequently Used Parameters

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

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

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

★ Different Comparison Scenarios

git diff                         <span class="c"># Working directory vs staging area</span>
git diff <span class="nt">--staged</span>                <span class="c"># Staging area vs last commit</span>
git diff <span class="nt">--cached</span>                <span class="c"># Equivalent to --staged</span>
git diff HEAD                    <span class="c"># Working directory vs last commit</span>
git diff <commit1> <commit2>     <span class="c"># Differences between two commits</span>
git diff main..new-feature       <span class="c"># Differences between two branches</span>
git diff <span class="nt">--</span> names.txt            <span class="c"># Differences for a specific file only</span>

Reading the output:

<span class="gd">- Tuvana       # Deleted line (red)
</span><span class="gi">+ Beril        # Added line (green)
</span>

📌 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 <span class="nb">rm </span>names.txt
git commit <span class="nt">-m</span> <span class="s2">"names.txt deleted"</span>

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

git <span class="nb">rm</span> <span class="nt">--cached</span> names.txt

★ git mv

To move or rename a file:

git <span class="nb">mv </span>old-name.txt new-name.txt
git commit <span class="nt">-m</span> <span class="s2">"File renamed"</span>

💡 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              <span class="c"># Lists local branches</span>
git branch <span class="nt">-a</span>           <span class="c"># Lists all, including remote-tracking branches</span>

💡 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

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 <span class="nt">-d</span> new-names     <span class="c"># Deletes a merged branch</span>
git branch <span class="nt">-D</span> new-names     <span class="c"># Force-deletes it (even if not merged)</span>

★ Renaming a Branch

git branch <span class="nt">-m</span> old-name new-name

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

or in Git 2.23+:

git switch new-names

Shortcut — Create and Switch:

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

git checkout <span class="nt">-b</span> new-names
<span class="c"># or</span>
git switch <span class="nt">-c</span> new-names

★ Switching to a Commit

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

git checkout a1b2c3d

⚠️ 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 <span class="nt">-b</span> rescue-branch

💡 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:

<span class="c"># Get the file state from a specific commit</span>
git checkout a1b2c3d <span class="nt">--</span> names.txt

<span class="c"># Get the file state from another branch</span>
git checkout main <span class="nt">--</span> names.txt

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:

<span class="c"># Create a new branch and switch to it</span>
git checkout <span class="nt">-b</span> new-names

<span class="c"># Add "Rabia" to names.txt and commit the change</span>
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"Rabia added"</span>

<span class="c"># Switch back to main branch</span>
git checkout main

<span class="c"># Merge new-names branch into main</span>
git merge new-names

★ 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

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

git merge <span class="nt">--no-ff</span> new-names

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
<span class="c"># or</span>
git rebase main

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

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

git checkout <span class="nt">-b</span> conflict-example
<span class="c"># names.txt → Change the line "Tuvana" to "Beril"</span>
git commit <span class="nt">-am</span> <span class="s2">"Tuvana replaced with Beril"</span>

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

git checkout main
<span class="c"># names.txt → Change the line "Tuvana" to "Rabia"</span>
git commit <span class="nt">-am</span> <span class="s2">"Tuvana replaced with Rabia"</span>

3. Try to merge:

git checkout conflict-example
git merge main

Git shows the following conflict message:

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

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

Eda
Beyza
<<<<<<< HEAD
Beril
=======
Rabia
>>>>>>> main
Melis
  • 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 <span class="nt">-m</span> <span class="s2">"Conflict resolved, updated as Beril"</span>

★ Quick Fix: Ours or Theirs

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

<span class="c"># Accept your branch's changes</span>
git checkout <span class="nt">--ours</span> names.txt

<span class="c"># Accept the incoming changes</span>
git checkout <span class="nt">--theirs</span> names.txt

git add names.txt
git commit <span class="nt">-m</span> <span class="s2">"Conflict resolved"</span>

★ Aborting the Merge (--abort)

To completely cancel the merge before resolving any conflicts:

git merge <span class="nt">--abort</span>

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 <span class="nt">--continue</span>

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

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 <span class="nt">-i</span> HEAD~3

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

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 <span class="nt">--onto</span> main feature bugfix
<span class="c"># Detach bugfix branch from feature and place it onto main</span>

★ 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:

<span class="c"># Resolve the conflict in the file, then:</span>
git add <span class="nb">.</span>
git rebase <span class="nt">--continue</span>

<span class="c"># or abort the rebase entirely:</span>
git rebase <span class="nt">--abort</span>

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:

<span class="nb">cat</span> .git/HEAD
<span class="c"># Output: ref: refs/heads/main</span>

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

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 <span class="nt">-b</span> new-branch-name

💡 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

★ Annotated Tag

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

git tag <span class="nt">-a</span> v1.0 <span class="nt">-m</span> <span class="s2">"First stable release"</span>

★ Adding a Tag to a Specific Commit

git tag <span class="nt">-a</span> v0.9 a1b2c3d <span class="nt">-m</span> <span class="s2">"Beta release"</span>

★ Tag Operations

git tag                    <span class="c"># List all tags</span>
git show v1.0              <span class="c"># Show tag details</span>
git tag <span class="nt">-d</span> v1.0            <span class="c"># Delete local tag</span>
git push origin v1.0       <span class="c"># Push tag to remote repository</span>
git push origin <span class="nt">--tags</span>     <span class="c"># Push all tags</span>

Undoing Changes ↩️

Undoing Changes of Git

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

<span class="c"># Restore the file to its last committed state (working directory changes are lost)</span>
git restore names.txt

<span class="c"># Remove the file from the staging area, but keep the file unchanged</span>
git restore <span class="nt">--staged</span> names.txt

★ 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 <span class="nt">--soft</span> HEAD~1

--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
<span class="c"># or</span>
git reset <span class="nt">--mixed</span> HEAD~1

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

git reset <span class="nt">--hard</span> HEAD~1

⚠️ 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 <span class="nt">--soft</span> a1b2c3d

★ 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

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 <span class="nt">-m</span> 1 <merge-commit-hash>

★ 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

Example output:

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

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

git reset <span class="nt">--hard</span> f4e5d6c

💡 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                              <span class="c"># Stash changes</span>
git stash push <span class="nt">-m</span> <span class="s2">"Description"</span>        <span class="c"># Stash with a message</span>
git stash <span class="nt">-u</span>                           <span class="c"># Include untracked files</span>
git stash <span class="nt">--all</span>                        <span class="c"># Stash everything, including ignored files</span>
git stash list                         <span class="c"># List stashes</span>
git stash show                         <span class="c"># Show a summary of the latest stash</span>
git stash show <span class="nt">-p</span>                      <span class="c"># Show the detailed diff of the latest stash</span>

💡 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                    <span class="c"># Apply the latest stash and remove it from the stash list</span>
git stash apply                  <span class="c"># Apply the latest stash and keep it in the stash list</span>
git stash apply stash@<span class="o">{</span>2<span class="o">}</span>        <span class="c"># Apply a specific stash</span>

★ Cleanup

git stash drop                   <span class="c"># Drop the latest stash</span>
git stash drop stash@<span class="o">{</span>1<span class="o">}</span>         <span class="c"># Drop a specific stash</span>
git stash clear                  <span class="c"># Clear all stashes</span>

★ Example Scenario

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

<span class="c"># Stash the work in progress</span>
git stash push <span class="nt">-m</span> <span class="s2">"I was updating Tuvana's information"</span>

<span class="c"># Switch to main and make the fix</span>
git checkout main
git commit <span class="nt">-am</span> <span class="s2">"Urgent fix applied"</span>

<span class="c"># Switch back to your feature branch and pick up where you left off</span>
git checkout new-feature
git stash pop

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

★ 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 <span class="nb">rm</span> <span class="nt">--cached</span> filename.txt
  • 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 <span class="nt">--global</span> core.excludesfile ~/.gitignore_global

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:

<span class="c"># HTTPS — Authentication with username/password or token</span>
git remote add origin https://github.com/user/project.git

<span class="c"># SSH — Authentication with SSH key (more practical)</span>
git remote add origin git@github.com:user/project.git

💡 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

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 <span class="nt">-v</span>

Example output:

origin  https://github.com/user/project.git (fetch)
origin  https://github.com/user/project.git (push)

★ Changing a Remote Repository URL

git remote set-url origin https://github.com/user/new-project.git

★ Removing a Remote Repository

git remote remove origin

Cloning (git clone) 📥

To copy a remote repository to your local machine:

git clone https://github.com/user/project.git

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

<span class="c"># Clone a specific branch</span>
git clone <span class="nt">-b</span> branch-name https://github.com/user/project.git

<span class="c"># Clone with a different folder name</span>
git clone https://github.com/user/project.git my-project

<span class="c"># Clone only the last commit (for speed in large projects)</span>
git clone <span class="nt">--depth</span> 1 https://github.com/user/project.git

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

To review changes in the remote branch after fetching:

git diff main origin/main

If the changes look correct, merge them:

git merge origin/main

★ 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

For a cleaner history, pull with rebase:

git pull <span class="nt">--rebase</span> origin main

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

git config <span class="nt">--global</span> pull.rebase <span class="nb">true</span>

★ 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

★ 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 <span class="nt">-u</span> origin new-feature

To change or set the upstream of an existing branch:

git branch <span class="nt">--set-upstream-to</span><span class="o">=</span>origin/main main

★ Other Parameters

git push origin <span class="nt">--delete</span> branch-name    <span class="c"># Delete remote branch</span>
git push <span class="nt">--force</span>                       <span class="c"># or -f  ⚠️ be extra careful when using</span>
git push <span class="nt">--force-with-lease</span>            <span class="c"># Safer force push</span>

⚠️ 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

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

To take multiple commits:

git cherry-pick a1b2c3d f4e5d6c

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

git add <span class="nb">.</span>
git cherry-pick <span class="nt">--continue</span>

<span class="c"># or cancel:</span>
git cherry-pick <span class="nt">--abort</span>

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

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

💡 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 <span class="nt">-L</span> 5,10 names.txt     <span class="c"># Only lines 5-10</span>
git blame <span class="nt">-e</span> names.txt           <span class="c"># Show email addresses</span>
git blame <span class="nt">-w</span> names.txt           <span class="c"># Ignore whitespace changes</span>

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 <span class="nb">grep</span> <span class="s2">"Beril"</span>

Example output:

names.txt:Beril
notes.txt:Beril's note added

★ Additional Parameters

git <span class="nb">grep</span> <span class="nt">-n</span> <span class="s2">"Beril"</span>                  <span class="c"># Show line numbers</span>
git <span class="nb">grep</span> <span class="nt">-c</span> <span class="s2">"Beril"</span>                  <span class="c"># Number of matches in each file</span>
git <span class="nb">grep</span> <span class="nt">-i</span> <span class="s2">"beril"</span>                  <span class="c"># Case-insensitive search</span>
git <span class="nb">grep</span> <span class="s2">"Beril"</span> <span class="nt">--</span> <span class="s2">"*.txt"</span>          <span class="c"># Search only in .txt files</span>
git <span class="nb">grep</span> <span class="s2">"Beril"</span> a1b2c3d             <span class="c"># Search in files at a specific commit</span>

💡 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

<span class="c"># Start bisect</span>
git bisect start

<span class="c"># Mark the current commit as buggy</span>
git bisect bad

<span class="c"># Mark an old commit that you know was working</span>
git bisect good a1b2c3d

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

<span class="c"># If the bug exists in this commit too:</span>
git bisect bad

<span class="c"># If the bug does not exist in this commit:</span>
git bisect good

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

When you are done:

git bisect reset

💡 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               <span class="c"># Details of the last commit</span>
git show a1b2c3d       <span class="c"># Details of a specific commit</span>
git show v1.0          <span class="c"># Details of the commit the tag points to</span>

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 <span class="nt">-n</span>           <span class="c"># Show what would be deleted first (dry run)</span>
git clean <span class="nt">-f</span>           <span class="c"># Delete untracked files</span>
git clean <span class="nt">-fd</span>          <span class="c"># Delete files and directories</span>
git clean <span class="nt">-fdx</span>         <span class="c"># Delete everything including files in .gitignore</span>

⚠️ 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:

<span class="c">#!/bin/sh</span>
npm run lint
<span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-ne</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"Lint errors found. Commit cancelled."</span>
  <span class="nb">exit </span>1
<span class="k">fi</span>
<span class="nb">chmod</span> +x .git/hooks/pre-commit

💡 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 <span class="nt">-m</span> <span class="s2">"Library added as submodule"</span>

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

git clone <span class="nt">--recurse-submodules</span> <url>
<span class="c"># or after cloning:</span>
git submodule update <span class="nt">--init</span> <span class="nt">--recursive</span>

★ git subtree

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

git subtree add <span class="nt">--prefix</span><span class="o">=</span>libs/library https://github.com/user/library.git main <span class="nt">--squash</span>

💡 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

★ 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 <span class="nt">--aggressive</span>       <span class="c"># Deeper optimization (slow but effective)</span>
git gc <span class="nt">--prune</span><span class="o">=</span>now        <span class="c"># Delete all unreachable objects immediately</span>

⚠️ 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

★ 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.

<span class="c"># To view an object's content:</span>
git cat-file <span class="nt">-p</span> a1b2c3d

<span class="c"># To view an object's type:</span>
git cat-file <span class="nt">-t</span> a1b2c3d

💡 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