Git and GitHub#

This chapter introduces Git and GitHub, two powerful tools that allow developers to track the full history of a project and efficiently manage version control, including the ability to revert to previous versions.


Table of Contents#

  • What is Git?

  • What is GitHub


What is Git?#

Git is a version control system. It helps you:

  • track changes in your files

  • save project history

  • collaborate with others

  • restore older versions when needed

  • make new branches of your project and work there without affecting the main files of project.

In this section we learn about history and snapshots, then later commands such as log, branch, merge, rebase, and revert.These are fundamental commands in daily usage of git. Git runs on your own computer. GitHub is an online platform that hosts Git repositories. In order to turn a floder into a Git repository, we do as follows:

Configure Git; git config#

Git uses configuration settings such as your name, email, editor, pager behavior, and aliases. The following commands uses to setup the configuration.

Note

  • Set your identity

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

These values appear in your commits.

  • View all configuration

git config --list
  • View one specific value

git config user.name
  • Set the default editor

git config --global core.editor "code --wait"
  • Enable color output

git config --global color.ui auto
  • Create aliases

Git aliases let you define shorter names for longer commands.

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.cm commit
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"

Then you can use:

git co main
git br
git cm -m "Fix typo"
git st
git lg
  • Git Configuration Levels

Git supports three configuration levels:

  • --system → applies to the whole system

  • --global → applies to the current user

  • --local → applies only to the current repository

  • Priority

If the same setting exists in multiple places, the order is:

local > global > system

This means local settings override global settings, and global settings override system settings.

  • Show Configuration Sources

To see both the configuration values and where they come from, use:

git config --list --show-origin

Example output

file:/etc/gitconfig        core.editor=vim
file:/home/user/.gitconfig user.name=Ali
file:.git/config           core.repositoryformatversion=0
  • Disabling Git Pagers

Git often uses a pager such as less to display long output. For example, git log may open inside a scrollable interface.

You can disable the pager for a specific command:

git config --global pager.log off

This makes git log print directly in the terminal.

  • Other examples

git config --global pager.diff off
git config --global pager.show off
git config --global pager.config off
git config --global pager.stash off
git config --global pager.help off
git config --global pager.blame off
git config --global pager.branch off
git config --global pager.annotate off
  • A Better Pager Setup

Instead of disabling all pagers, many developers prefer a smarter pager configuration:

git config --global core.pager "less -FRX"

This keeps the benefits of a pager while making it less intrusive.

Initialize a Repository; git init#

To turn a folder into a Git repository, go to the folder path and type the following command in the terminal:

git init

If you want the default branch to be main immediately, use:

git init -b main

The above command:

  1. creates a new Git repository

  2. creates the initial branch with the name main

This is cleaner than using git init first and renaming the branch later.

Stage and Commit Changes; git add, git commit#

A common Git workflow looks like this:

git add .
git commit -m "your message"

Note

  • git add . stages the changes

  • git commit -m "..." records those staged changes into Git history

Example

git add app.js
git commit -m "Fix login bug"

This means the changes in app.js are now stored as a commit with the message Fix login bug.

View Commit History with git log#

To see commit history, use:

git log

What it shows

For each commit, Git usually shows:

  • commit hash

  • author

  • date

  • commit message

There are several ways to show the history of a project as follows:

Note

One-line history

git log --oneline

Graph view

git log --oneline --graph

Last 5 commits

git log -n 5

Show full patch with each commit

git log -p

Show history for one file

git log file.txt

Compare Changes with git diff#

To compare changes, use:

git diff

Note

There are git diff variations and options:

  • Basic meaning; unstaged changes

This shows changes that are not staged yet.

git diff

Example

- console.log("Hello");
+ console.log("Hello World");
  • Staged changes

git diff --staged

or

git diff --cached
  • Compare with the latest commit

git diff HEAD
  • Compare two commits

git diff commit1 commit2
  • Compare one file

git diff file.txt
  • Show only file names

git diff --name-only

Branching; git branch and git switch#

Why Branches Matter A branch gives you a separate line of development. Instead of putting every change directly onto main, you can isolate work.

  • Why Branches Are Important Branches let you:

  • keep main stable

  • work on features or fixes separately

  • experiment without damaging the main line

  • create clean Pull Requests later

  • integrate work step by step

Core Idea A branch is not a second full copy of the project. It is a movable pointer into commit history. Here is the most commons commands for branching and switiching

Note

  • List available branches on your project

git branch
  • Create a new branch

git branch feature/login
  • Switch to another branch

git switch main feature/login
  • Create and switch in one step

git switch -c feature/login

Why This Matters

Branching should be understood before Fork and Pull Request workflows, because PRs are usually built from branches.

A cleane everyday local workflow is as follows:

⭐ Summary

main → create branch → make changes → commit → switch back when needed

Example:

git switch main
git pull
git switch -c fix/readme-typo
git add README.md
git commit -m "Fix typo in README"

Key Takeaway

Before learning collaboration on GitHub, a learner should already be comfortable with:

  • creating a branch

  • switching branches

  • committing on a branch

  • understanding that work stays isolated until integrated

When a Git repository is not connected to a remote (e.g., GitHub), the entire workflow happens locally on your machine. You create or modify files, stage changes using git add, and record them with git commit. You can organize work using branches (git switch -c <branch>), move between them (git switch), and integrate changes via merging (git merge). All history, experimentation, and version control remain isolated within your local repository, and no synchronization commands like git push or git pull are required since there is no external repository involved.

In the next chapter we learn how to create a remote repository and how to communicate with the local version on our machine. A clean workfolw often looks likes this:

git switch main  # Go to the main branch on your local machine
git pull  # Get all changes on the main branch of remote repo and update your main local
git switch -c fix/readme-typo  # make a new branch and switch on it and then make your edition on README.md
git add README.md # stage your changes on the fix/readme-typo branch
git commit -m "Fix typo in README" # save changes with history

What is GitHub#

A local repository is enough for solo experimentation, but collaboration requires a shared remote repository. GitHub provides that shared remote space.

Why Remotes Matter

They allow you to:

  • back up your project online

  • collaborate with teammates

  • open Pull Requests

  • fetch and push shared history

Important Distinction

  • local Git = history on your machine

  • remote GitHub repo = shared history online

📌 Goal: Connecting an Existing Local Git Repository to GitHub

Note

You already have a local Git repository and want to:

  1. Create a repository on GitHub

  2. Link (connect) it to your local repo

  3. Push your code to GitHub

Create a GitHub Account

To use GitHub effectively, the first step is to create a user account.

Steps

  1. Go to GitHub.

  2. Click Sign up.

  3. Enter:

    • your email address

    • a password

    • a username

  4. Verify your email address.

  5. Complete the sign-up process.

Connect Git to GitHub#

After creating your account, you can create repositories, upload code, and connect Git on your computer to GitHub. This is the standard pipeline for creating a repository and working on it on a daily basis.

Note

  • 🧱 Step 1 — Create a Repository on GitHub

  1. Go to GitHub

  2. Click New repository

  3. Choose a name (e.g., my-project)

  4. Do NOT initialize with README, .gitignore, or license

  5. Click Create repository

  • 🔗 Step 2 — Add Remote to Your Local Repository

In your terminal (inside your project folder):

git remote add origin git@github.com:USERNAME/REPOSITORY.git

Example:

git remote add origin git@github.com:john/my-project.git

✔ This connects your local repo → GitHub repo

  • 🔍 Step 3 — Verify Remote Connection

git remote -v

Expected output:

origin  git@github.com:USERNAME/REPOSITORY.git (fetch)
origin  git@github.com:USERNAME/REPOSITORY.git (push)
  • 🚀 Step 4 — Push Your Code to GitHub

If your main branch is main:

git push -u origin main

If it’s master:

git push -u origin master

-u sets upstream so future pushes are simpler:

git push

Warning

⚠️ Common Issues

  • Permission denied (publickey) → SSH key not set up

  • Repository not found → Wrong URL or repo name

  • Branch mismatch → Use correct branch (main vs master)

HTTPS vs SSH for GitHub#

There are two common ways to connect Git to GitHub:

  • HTTPS

git clone https://github.com/user/repo.git
  • simpler to start with

  • commonly available by default

  • often uses a token for authentication

  • SSH

git clone git@github.com:user/repo.git
  • very common on Linux and macOS

  • excellent for repeated use

  • avoids typing credentials repeatedly

  • useful beyond Git, for server administration as well

SSH is widely considered worth learning.


What Is SSH?

SSH stands for Secure Shell. It is a secure network protocol used to connect to remote systems over the internet.

Note

Important clarification

SSH is not something separate from the internet. It is a secure way of communicating over the internet.

Why SSH matters

Without encryption, data may be exposed. With SSH, communication is encrypted and authenticated.

Generate SSH Keys

To use SSH with GitHub, you normally create an SSH key pair on your own computer using the following commands:

ssh-keygen -t ed25519 -C "your_email@example.com"

Then your operating system creates two files:

  • private key

  • public key

Usually they are stored in:

~/.ssh/

For example:

  • ~/.ssh/id_ed25519 → private key

  • ~/.ssh/id_ed25519.pub → public key

Which Part Stays Private and Which Part Goes to GitHub?

Private key

  • stays on your computer

  • must never be shared

  • is used to prove your identity

Public key

  • can be shared

  • is copied to GitHub

  • allows GitHub to recognize your computer

You do not copy or upload the private key to GitHub.

How GitHub Verifies That You Have the Correct Private Key

A common question is:

  • If GitHub only has my public key, how does it know I have the right private key?

The answer is cryptographic authentication.

Simplified process

  1. GitHub already has your public key

  2. During login/authentication, GitHub sends a challenge

  3. Your computer uses the private key to sign that challenge

  4. GitHub checks the signature using the public key

  5. If the signature is valid, authentication succeeds

Warning

Key point

The private key never leaves your computer. You do not paste it anywhere. Only the cryptographic proof is sent.

Add the Public Key to GitHub

To display your public key so you can copy it:

cat ~/.ssh/id_ed25519.pub

Copy the output and add it to your GitHub account under SSH keys.

Only the .pub file should be copied to GitHub.

Test the SSH Connection

After adding the public key to GitHub, test the connection with:

ssh -T git@github.com

Meaning of -T

-T means:

  • do not open an interactive shell

  • only test authentication

Successful output

Hi username! You've successfully authenticated, but GitHub does not provide shell access.

This means:

  • SSH is configured correctly

  • GitHub recognizes your key

  • Git operations over SSH should work


Common SSH Test Errors


  • First-time host verification

You may see:

Are you sure you want to continue connecting (yes/no)?

Type:

yes

This stores GitHub’s host fingerprint on your machine.



  • Permission denied

You may see:

Permission denied (publickey).

This usually means one of the following:

  • the public key was not added to GitHub

  • the wrong key is being used

  • the key was not loaded by the SSH agent

  • the file path is wrong



  • Check Whether Your SSH Key Exists

To see files in your SSH directory:

ls ~/.ssh

You should usually see files like:

id_ed25519
id_ed25519.pub


  • Check Whether the Key Is Loaded

To list keys currently loaded in the SSH agent:

ssh-add -l

If your key is not loaded, add it:

ssh-add ~/.ssh/id_ed25519


  • Do You Need to Run ssh -T git@github.com Every Time?

No.

You usually run:

ssh -T git@github.com

only for:

  • initial setup

  • troubleshooting

  • verifying that authentication works

Note

In normal daily use

You do not run the SSH test every time.

Instead, you just use Git normally:

git clone git@github.com:user/repo.git
git pull
git push

SSH authentication happens automatically in the background.

Note

Recommended Mental Model

You can think of the SSH process like this:

  • your computer keeps the private key

  • GitHub stores the public key

  • when needed, your computer proves ownership of the private key

  • GitHub verifies the proof with the public key

This is why SSH is secure and convenient.

Warning

  • never share the private key

  • only upload the public key

  • protect the private key with a passphrase if possible

Why learning SSH is valuable

SSH is useful not only for GitHub, but also for:

  • remote server access

  • cloud systems

  • DevOps workflows

  • secure file transfer

Note

These are all the commands you have learned so far. Try to explain what each of them does.

# Initialize a repository
git init
git init -b main

# Stage and commit
git add .
git commit -m "message"

# View history
git log
git log --oneline
git log --oneline --graph
git log -n 5
git log -p

# View differences
git diff
git diff --staged
git diff --cached
git diff HEAD
git diff commit1 commit2
git diff file.txt
git diff --name-only

# Git configuration
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --list
git config --list --show-origin

# Disable pagers for selected commands
git config --global pager.log off
git config --global pager.diff off
git config --global pager.show off
git config --global pager.config off
git config --global pager.stash off
git config --global pager.help off
git config --global pager.blame off
git config --global pager.branch off
git config --global pager.annotate off

# Better pager setup
git config --global core.pager "less -FRX"

# Generate SSH keys
ssh-keygen -t ed25519 -C "your_email@example.com"

# Show public key
cat ~/.ssh/id_ed25519.pub

# Test SSH
ssh -T git@github.com

# Check SSH files
ls ~/.ssh

# Check/add SSH key in agent
ssh-add -l
ssh-add ~/.ssh/id_ed25519

Git Clone#

If you have a remote repository on GitHub and want to create a copy of it on your local machine, this process is called cloning.

Cloning downloads the entire repository, including its history, branches, and files, to your local environment. It also automatically connects your local repository to the remote repository.

The basic command to clone a repository is:

git clone git@github.com:OWNER/PROJECT.git

After Cloning

Once you clone a repository, Git automatically sets up a connection to the remote repository. You can verify this using:

git remote -v

What Does origin Mean?

After cloning, Git assigns a default name to the remote repository:

origin
  • origin is simply a conventional name for the remote repository

  • It refers to the source you cloned from

  • You can rename it, but most developers keep it as origin

Note

Common Commands

  • Push (send changes to GitHub)

git push origin main

Sends your local commits to the main branch on the remote repository

  • Pull (get updates from GitHub)

git pull

Fetches and merges changes from the remote repository into your local branch Key Concepts

GitHub Collaboration Models#

There are two major GitHub collaboration models: Fork workflow and collaboration workfolw. Fork Workflow used when you do not have write access to the main repository and Contributor Workflow used when you do have write access to the main repository.


Fork#

A fork is a copy of a GitHub repository that is created under your own GitHub account. This is different from a local copy on your computer. A fork lives on GitHub, not just on your machine.

Mental Model

Suppose there is an original repository:

  • original repository: github.com/original-owner/project

  • your fork: github.com/your-username/project

The original repository is often called:

  • upstream

Your personal copy is usually connected as:

  • origin

So in a typical fork workflow:

  • upstream = the original repository

  • origin = your fork on GitHub


When Do We Use a Fork?

Forks are commonly used when you do not have direct write access to the original repository.

Typical Cases

  1. Open-source contribution You want to improve a public project, but you are not part of the core team.

  2. Safe experimentation You want to try changes freely without affecting the original repository.

  3. Personal customization You want to maintain your own version of a project with custom changes.

  4. External collaboration You are contributing from outside the main organization or team.


When a Fork Is Usually Not Needed

If you are already a member of the project team and have write access, the team may prefer this workflow instead:

  • clone the main repository directly

  • create a branch inside that repository

  • push your branch

  • open a Pull Request from that branch


Note

Fork vs Clone

These two concepts are related, but they are not the same.

  • Fork: A fork creates a copy of a repository on GitHub under your account.

  • Clone: A clone creates a copy of a repository on your local computer.

Standard Order

In a fork-based contribution workflow, the normal order is:

Fork on GitHub → Clone to your computer

So first you create the fork online, and then you clone your fork locally.

Do We Get the Full History After Forking?

Yes. In normal GitHub usage, a fork preserves the repository history. That means your fork contains:

  • commits

  • branches and references relevant to the forked project

  • tags (depending on repository state and hosting behavior)

  • the complete evolution of the project

So conceptually, you inherit the project history. However, having the history does not automatically make you a contributor to the original project.

Are You a Contributor Just Because You Forked?

No. Forking a repository means:

  • you now have your own copy of the project

  • you can work on it independently

  • you can propose changes

But it does not mean that you are already a contributor to the original repository.

When Are You Usually Considered a Contributor?

In practice, you become a contributor when your changes are accepted into the original project, usually by:

  • opening a Pull Request

  • having it reviewed

  • and getting it merged

So this statement is accurate:

You may have the full repository history in your fork, but you are not yet a contributor to the original project until your contribution is accepted there.


The Full Fork Workflow

Here is the standard end-to-end workflow:

Fork → Clone → Add upstream → Create branch → Edit → Commit → Push → Pull Request → Review → Merge

We will now go through each part carefully.

  • Step 1: Fork the Repository on GitHub

On GitHub:

  1. open the original repository

  2. click Fork

  3. GitHub creates a copy under your own account

Example:

  • original: github.com/original-owner/project

  • fork: github.com/your-username/project

At this point, you have your own GitHub-hosted copy of the project.

  • Step 2: Clone Your Fork Locally

After forking, clone your fork, not the upstream repository.

git clone git@github.com:YOUR_USERNAME/PROJECT.git
cd PROJECT

Now your local repository is connected to your fork as origin.

  • Step 3: Add the Original Repository as upstream

This is a very important step. You usually want two remotes:

  • origin → your fork

  • upstream → the original repository

Add upstream like this:

git remote add upstream git@github.com:ORIGINAL_OWNER/PROJECT.git

Then verify your remotes:

git remote -v

Expected result:

origin    git@github.com:YOUR_USERNAME/PROJECT.git
upstream  git@github.com:ORIGINAL_OWNER/PROJECT.git

Why Is upstream Important?

Because the original project keeps moving forward. If you only work with your fork and never sync from upstream:

  1. your fork becomes outdated

  2. your branch may drift away from the current project state

  3. your Pull Request may become harder to review or merge

  • Step 4: Create a New Branch for Your Change

You should not usually work directly on main for a contribution. Instead, create a branch for each separate change.

Why?

Because branches help you:

  • isolate your work

  • keep main clean

  • open focused Pull Requests

  • revise one change without mixing it with another

Recommended Command

git switch -c fix-readme-typo

This command does two things at once:

  1. creates a new branch named fix-readme-typo

  2. switches to that branch immediately

  • Step 5: Make Your Changes

Now edit the project files.

Examples:

  • fix a typo

  • improve documentation

  • add a small feature

  • refactor a function

  • update tests

At this point, your changes are only on your local branch.

  • Stage and Commit the Changes

After editing, stage and commit your work.

git add .
git commit -m "Fix typo in installation guide"

Good Commit Message Advice

A good commit message is:

  • specific

  • short

  • action-oriented

Examples:

  • Fix typo in README

  • Add validation for email field

  • Improve setup instructions for macOS

Avoid vague messages like:

  • update

  • change stuff

  • fix

  • Step 6: Push the Branch to Your Fork

Now push your branch to origin, which is your fork.

git push origin fix-readme-typo

This sends your branch to GitHub under your fork.

  • Step 7: Open a Pull Request

This is the key step. A Pull Request (PR) is a request asking the maintainers of the original project to review and potentially merge your changes.

Meaning of a Pull Request

A Pull Request is basically saying:

I made these changes in my branch. Please review them and merge them into the main project if they are acceptable.

Typical Direction

your fork / your branch  →  upstream / main

Example:

  • source: your-username:fix-readme-typo

  • target: original-owner:main

How to Create a Pull Request on GitHub

Typical steps:

  1. push your branch to your fork

  2. go to your fork on GitHub

  3. GitHub often shows a Compare & pull request button

  4. click it

  5. confirm the base repository and base branch

  6. confirm your source branch

  7. write a clear title

  8. write a helpful description

  9. submit the Pull Request

Good PR Title

  • Fix typo in installation guide

  • Add missing null check in login flow

Good PR Description Should Explain

  • Problem: What issue are you solving?

  • Change: What did you modify?

  • Reasoning: Why is this the right change?

  • Testing: How did you verify it?

  • Scope: Is this a small focused change or a broader change?

Example PR Description

## Summary
This PR fixes a typo in the installation section of the README.

## Details
The command name was missing a dash, which could confuse new users.

## Testing
No code changes were made. Documentation reviewed manually.

This helps maintainers review quickly and confidently.

What Happens After You Open a PR? After you submit the Pull Request, several things may happen:

  1. Maintainers review it

  2. Automated checks may run

    • tests

    • formatting

    • linting

    • CI pipelines

  3. Reviewers may approve it

  4. Reviewers may request changes

  5. Maintainers may merge it

  6. In some cases, they may close it without merging

This is normal. A closed PR is not necessarily a failure. Sometimes it simply means the project chose a different direction.

What If Reviewers Request Changes?

This is very common and completely normal. You do not usually create a new Pull Request for small requested changes.

Instead, you:

  1. stay on the same branch

  2. make the requested updates

  3. commit the new changes

  4. push again to the same branch

Example:

git add .
git commit -m "Address review comments"
git push origin fix-readme-typo

When you push to the same branch, the existing Pull Request updates automatically.

Why Branches Matter So Much for Pull Requests

Branches are central to PR workflows. A Pull Request is typically tied to:

  • one source branch

  • one target branch

Because of this, branches give you:

  • clean separation of work

  • easier review

  • better rollback options

  • less risk of mixing unrelated changes

Warning

Keeping Your Fork Up to Date The upstream repository changes over time. If you do not sync your fork, you can end up working on an old base.

Common Sync Workflow is

git fetch upstream
git switch main
git merge upstream/main
git push origin main
  • git fetch upstream
    downloads the latest state from the original project

  • git switch main
    returns you to your local main branch

  • git merge upstream/main
    brings upstream changes into your local main

  • git push origin main
    updates your fork’s main on GitHub

Alternative: Rebase Instead of Merge

Some projects prefer a cleaner linear history and may encourage rebase instead of merge.

Example:

git fetch upstream
git switch my-feature
git rebase upstream/main

Why Rebase?

Rebase rewrites your branch so it appears to start from the latest upstream state. This can make history cleaner, but it is conceptually more advanced than merge.

Merge, Rebase, and Conflict Resolution#

This section adresses three critical Git topics:

  • merge

  • rebase

  • conflict resolution

These concepts are essential after learning branching, forks, and Pull Requests, because real collaboration almost always involves integrating changes from multiple branches. By the end of this section, you should be able to:

  • explain what merge does

  • explain what rebase does

  • distinguish the workflows and history produced by each

  • understand when a team may prefer merge or rebase

  • detect and resolve merge conflicts

  • continue or abort a merge or rebase safely

  • understand why conflicts happen

  • use practical commands in real project scenarios

Why These Topics Matter

As soon as more than one branch exists, integration becomes necessary.

Examples:

  • you created a feature branch and now want to bring it into main

  • the upstream repository moved forward while you were working

  • your Pull Request is out of date

  • two developers changed the same file in incompatible ways

At that point, Git must combine histories and file changes. That is where merge, rebase, and conflict resolution become central.

The Big Picture

There are two common ways to integrate branch histories:

  1. Merge: preserves the branching history and adds a merge commit

  2. Rebase: rewrites the branch so it appears to start from a new base

Neither is universally “better”. The right choice depends on workflow, team preference, and whether the branch has already been shared.

Imagine this simplified history.

Before Integration

A---B---C   main
     \
      D---E   feature

After Merge

A---B---C--------M   main
     \          /
      D---E----/    feature

M is a merge commit.

After Rebase

A---B---C---D'---E'   feature

The rebased commits D' and E' are new versions of the original commits D and E.

What Is a Merge?

A merge combines the histories of two branches. Suppose you have:

  • main

  • feature

You worked on feature, and now you want those changes in main. A common workflow is:

git switch main
git merge feature

Git then attempts to combine the histories.

If it succeeds without conflict:

  • your work is integrated into main

  • Git may create a merge commit

  • the branch structure remains visible in history

What Is a Rebase?

A rebase moves a branch so that it is replayed on top of another base.

Suppose this happened:

  • main moved ahead

  • your feature branch was created earlier

  • you now want your branch to sit on top of the latest main

You might run:

git switch feature
git fetch origin
git rebase origin

Git then takes the commits from feature and reapplies them one by one on top of the newer base.

Typical Rebase Workflow for a Feature Branch

Suppose your branch is behind main and you want to update it before opening or finalizing a PR.

git fetch origin
git switch feature
git rebase origin/main

Then, if the branch was already pushed before, you may need:

git push --force-with-lease origin feature

Because after rebase, the branch history changed. A normal push may be rejected.

--force-with-lease is safer than plain --force because it checks that the remote state is what you expect before overwriting it.

git fetch origin
git switch feature
git rebase origin/main
git push --force-with-lease origin feature

What Is a Conflict?

A conflict happens when Git cannot automatically decide how to combine changes. This usually happens when:

  • two branches changed the same lines

  • one branch deleted a file that another branch edited

  • structural changes overlap in incompatible ways

  • two developers edited the same code block

  • branch A renamed or deleted something that branch B still uses

  • the code evolved in two directions simultaneously

  • a long-lived branch fell far behind main

Git is very good at automatic merging, but it cannot safely guess human intent in every case. The longer a branch lives without syncing, the more likely conflicts become.

Example of a Conflict

Imagine main has:

def greet():
    return "Hello"

And your feature branch changed it to:

def greet():
    return "Hello, user"

But meanwhile main changed it to:

def greet():
    return "Hi"

Git now sees that the same lines were changed differently. It cannot confidently choose one, so it reports a conflict.

When a conflict happens, Git inserts markers into the file, such as:

<<<<<<< HEAD
return "Hi"
=======
return "Hello, user"
>>>>>>> feature/login

That means

  • <<<<<<< HEAD
    the current branch’s version

  • =======
    separator

  • >>>>>>> feature/login
    the incoming branch’s version

You must edit the file manually and remove these markers. If a conflict happens during merge:

git switch main
git merge feature

Git may stop and tell you which files are conflicted.

Then the normal process is:

  1. open conflicted files

  2. decide the correct final content

  3. remove conflict markers

  4. stage the resolved files

  5. complete the merge


Conflict During merge Here is the standard sequence.

  • Step 1:Attempt the merge

git switch main
git merge feature/login
  • Step 2: Check status

git status

Git shows which files are unmerged.

  • Step 3: Open each conflicted file

Look for markers like:

<<<<<<<
=======
>>>>>>>
  • Step 4: Edit to the correct final result

Choose:

  • your version

  • their version

  • or a combination

  • Step 5: Stage resolved files

git add path/to/file
  • Step 6: Finish the merge

If needed:

git commit

Sometimes Git prepares the merge commit automatically after staging, depending on the conflict path and tooling.

Abort a Merge

Sometimes you decide the current integration attempt is not worth continuing right now.

git merge --abort

This attempts to return the repository to the pre-merge state.


Conflict During Rebase

If a conflict happens during rebase:

git switch feature/login
git rebase origin/main

Git stops at the conflicting commit.

Then you:

  1. open the conflicted files

  2. resolve the conflict

  3. stage the fixed files

  4. continue the rebase

Command:

git add .
git rebase --continue

If more conflicts appear, repeat the same process. Optional: Skip a problematic commit

git rebase --skip

Optional: Abort the whole rebase

git rebase --abort

Useful Commands During Conflict Resolution

  • See current status

git status
  • See differences

git diff
  • See staged differences

git diff --staged

Conflict Prevention Strategies

You cannot eliminate all conflicts, but you can reduce them.

Good Practices

  1. Keep branches short-lived Long-lived branches drift and conflict more.

  2. Sync frequently with main or upstream

    • merge from main regularly

    • or rebase regularly if that is the team workflow

  3. Make smaller Pull Requests Small focused changes are easier to integrate.

  4. Communicate with teammates If two people plan to edit the same subsystem, coordinate early.

  5. Avoid giant unrelated commits Big mixed commits make conflict analysis much harder.

What Is Squash Merge?

A related concept is squash merge. Instead of preserving all commits from the feature branch, the branch is merged as one single commit.

This is often offered in GitHub Pull Requests.

Why Teams Use It

  • cleaner history on main

  • less noise from many small “work in progress” commits

  • easier project history reading

Important Distinction

  • squash merge is not the same as rebase

  • rebase rewrites branch history before integration

  • squash merge compresses branch history at integration time

Contributor#

This section is for the case where you are already a contributor or team member and have write access to the main repository. In this situation, the workflow is usually different from the fork-based open-source workflow. Instead of:

Fork → Clone → Branch → Push to your fork → Pull Request

you often use:

Clone main repository → Create branch → Commit → Push branch → Pull Request

or, in some teams:

Clone main repository → Create branch → Commit → Push branch → Merge directly

depending on team policy.

By the end of this section, you should be able to:

  • understand how contributor workflow differs from fork workflow

  • know when a fork is unnecessary

  • clone the main repository directly

  • create and manage contribution branches inside the main repository

  • push branches to the shared repository

  • open Pull Requests from a branch in the same repository

  • understand when direct pushes to main are discouraged

  • work safely in a collaborative team environment

What Changes When You Are a Contributor?

When you are a contributor with write access, you no longer need your own fork in order to propose changes. That is the key difference.

Fork-Based Workflow

Used when:

  • you do not have write access

  • you contribute from outside the core team

  • you submit changes through your own fork

Contributor Workflow

Used when:

  • you do have write access

  • you are part of the organization or trusted team

  • you can push branches directly to the main repository

So if you are already a contributor, the repository itself can usually act as the shared collaboration space.

Do Contributors Still Use Pull Requests?

Very often, yes.

Having write access does not always mean:

  • push directly to main

  • merge without review

  • skip collaboration rules

In many professional teams, even contributors with write access still:

  • create branches

  • push those branches to the shared repository

  • open Pull Requests

  • wait for review

  • merge only after approval

Why?

Because Pull Requests are not only about permissions. They are also about:

  • review quality

  • design discussion

  • CI checks

  • documentation of why a change happened

  • protecting the stability of main

High-Level Contributor Workflow

A common contributor workflow looks like this:

Clone main repository → Create feature branch → Edit → Commit → Push branch → Open Pull Request → Review → Merge

This is similar to the fork workflow, but the difference is where the branch lives:

  • in fork workflow, the branch lives in your fork

  • in contributor workflow, the branch lives in the main shared repository

  • Step 1: Clone the Main Repository

Since you already have access, you normally clone the main repository directly.

git clone git@github.com:ORGANIZATION/PROJECT.git
cd PROJECT

There is no need to fork first.

Your origin now points directly to the shared repository.

Understanding origin in Contributor Workflow

In a fork-based workflow:

  • origin usually points to your fork

  • upstream points to the original repository

But in a contributor workflow:

  • origin usually points directly to the shared repository

Example:

git remote -v

Possible output:

origin  git@github.com:ORGANIZATION/PROJECT.git

In many cases, there is no need for an upstream remote at all, because you are already working directly with the main repository.

  • Step 2: Update Your Local Main Branch

Before starting new work, make sure your local main is up to date.

git switch main
git pull

This is important because you usually want to branch from the latest project state.

  • Step 3: Create a Feature Branch

Even when you are a contributor, it is usually best not to work directly on main. Instead, create a focused branch:

git switch -c feature/improve-login-validation

or:

git switch -c fix/readme-typo

Why Branches Still Matter

Branches let you:

  • isolate one change

  • keep main stable

  • make review easier

  • avoid mixing unrelated work

  • simplify rollback and debugging

Should Contributors Push Directly to main?

Usually, no.

Even if technically allowed, many teams discourage or forbid direct pushes to main.

Why Direct Pushes Are Risky

They can:

  • bypass code review

  • skip discussion

  • introduce unstable code

  • make auditing harder

  • break CI expectations

  • surprise teammates

Better Practice

Use:

  • short-lived feature branches

  • Pull Requests

  • review gates

  • protected branch rules

In modern team workflows, main is often protected specifically to prevent accidental direct pushes.

Protected Branches

Many GitHub repositories use protected branches.

A protected branch may require:

  • Pull Request before merge

  • at least one approval

  • passing CI checks

  • resolved conversations

  • linear history

  • no force-pushes

  • no direct pushes

This means that even contributors with write access still follow a formal workflow. So contributor status gives you access, but not necessarily unrestricted freedom.

  • Step 4: Make Changes and Commit

After creating your branch, edit the necessary files and commit your work.

git add .
git commit -m "Improve login validation for empty email input"

Good Commit Message Advice

Strong commit messages are:

  • clear

  • focused

  • action-based

  • easy for reviewers to understand

git add .
git commit -m "Improve login validation for empty email input"
  • Step 5: Push Your Branch to the Shared Repository

Now push your branch to the main shared repository:

git push origin feature/improve-login-validation

This is a major difference from the fork workflow.

Note

In Fork Workflow

You push to:

your fork

In Contributor Workflow

You push to:

the main repository itself
  • Step 6: Open a Pull Request from the Same Repository

Now create a Pull Request. In this case, both the source branch and target branch are usually in the same repository.

Example direction:

ORGANIZATION/PROJECT:feature/improve-login-validation
    →
ORGANIZATION/PROJECT:main

This is different from the fork model, where the source branch lives in your fork.

Why PRs Still Matter for Contributors

A Pull Request is useful even when both branches are in the same repository.

PRs provide:

  • peer review

  • architectural feedback

  • CI validation

  • historical record of discussion

  • visibility for teammates

  • a checkpoint before code reaches main

In strong engineering teams, the Pull Request is a collaboration tool, not just a permission workaround.

Note

Same-Repository PR vs Fork PR

Fork PR

your-username/PROJECT:my-branch
    →
ORIGINAL_OWNER/PROJECT:main

Contributor PR

ORGANIZATION/PROJECT:my-branch
    →
ORGANIZATION/PROJECT:main

Key Difference

The branch source lives in a different place:

  • fork PR → branch in your fork

  • contributor PR → branch in the main repository

Team Naming Conventions for Branches

Professional teams often use naming conventions such as:

  • feature/add-export-button

  • fix/login-timeout

  • docs/update-installation-guide

  • refactor/auth-service

  • test/add-user-service-tests

These names help reviewers understand branch intent quickly.


Typical Contributor Workflow Example

Let us imagine you are part of the team and want to fix a bug.

Steps

  1. clone the main repository

  2. switch to main

  3. pull latest changes

  4. create branch fix/login-timeout

  5. make your changes

  6. commit

  7. push branch to origin

  8. open Pull Request

  9. address review comments

  10. merge after approval

git clone git@github.com:ORGANIZATION/PROJECT.git
cd PROJECT
git switch main
git pull
git switch -c fix/login-timeout
git add .
git commit -m "Fix login timeout handling"
git push origin fix/login-timeout

What If Reviewers Request Changes?

Just like in fork workflow, you usually stay on the same branch.

Example:

git add .
git commit -m "Address review comments"
git push origin fix/login-timeout

The Pull Request updates automatically because it is tracking that same branch.

Syncing Your Branch with Main

While your Pull Request is open, main may move forward. You may need to update your branch.

  • Option A: Merge main into your branch

git switch fix/login-timeout
git fetch origin
git merge origin/main
  • Option B: Rebase onto main

git switch fix/login-timeout
git fetch origin
git rebase origin/main

Which option to use depends on team policy.

What If the Team Allows Direct Pushes?

Some small teams or personal team projects do allow direct pushes to main. That workflow might look like:

git switch main
git pull
git add .
git commit -m "Small update"
git push origin main

Warning

Even if allowed, direct pushes are usually best reserved for:

  • tiny low-risk changes

  • emergency hotfixes

  • trusted internal workflows

  • solo-maintainer repositories

For most collaborative work, feature branches and PRs are still safer.

Warning

Common Mistakes Contributors Make

  1. Working directly on main. This increases risk and reduces review quality.

  2. Pushing half-finished work to shared branches without clarity. Teammates may review unstable code too early.

  3. Opening giant PRs. Smaller changes are easier to review and merge.

  4. Ignoring branch naming conventions. This makes the shared repository harder to navigate.

  5. Forgetting to pull before creating a branch. You may branch from outdated main.

  6. Rebasing shared team branches carelessly. If others use the same branch, history rewriting becomes dangerous.

Correction using Git reset, revert, restore, amend, and cherry-pick#

This section help to answer real-world questions such as:

  • How do I undo something safely?

  • How do I fix the last commit?

  • How do I restore a file?

  • How do I move one specific commit to another branch?

  • When should I rewrite history, and when should I preserve it?

By the end of this section, you should be able to:

  • explain the difference between reset and revert

  • understand the purpose of restore

  • amend the most recent commit safely

  • move a specific commit with cherry-pick

  • choose the correct tool depending on whether history should be rewritten or preserved

  • avoid common mistakes when undoing or reusing work

Why These Commands Matter

In real Git usage, people often need to correct mistakes.

Examples:

  • you committed too early

  • you forgot to include one file in the last commit

  • you want to undo a bad commit without deleting history

  • you accidentally staged the wrong file

  • you want to bring one bug fix from one branch into another

  • you want to discard local edits and go back to the committed state

These are not rare situations. They are part of normal Git work. That is why reset, revert, restore, amend, and cherry-pick are core professional tools. A useful mental model is this:

  • reset → move branch pointers and optionally unstage or discard changes

  • revert → create a new commit that undoes an earlier commit

  • restore → restore file contents or unstage changes

  • amend → modify the most recent commit

  • cherry-pick → apply one specific commit onto another branch

Before using above undo commands well, remember that:

  1. Working Tree is the actual files in your project directory.

  2. Staging Area is the set of changes prepared for the next commit.

  3. Commit History is the already-recorded snapshots in Git.


Git reset#

git reset is one of the most powerful and potentially dangerous Git commands. Its core purpose is to move HEAD and sometimes also affect:

  • the staging area

  • the working tree

reset says: Move my current branch to a different commit, and possibly adjust staged or working changes to match.

Because of that, reset can be:

  • very helpful

  • very destructive if used carelessly

Three common forms of git reset are:

  • --soft

  • --mixed

  • --hard

  1. git reset --soft: Moves the branch pointer, but keeps changes staged.

Example:

git reset --soft HEAD~1

This means:

  • move the current branch back by one commit

  • keep the changes from that commit staged

Use Case

You made a commit, but now want to:

  • rewrite the message

  • split the work differently

  • recommit in a cleaner way

This is useful when the commit itself was premature, but the content is still correct.

  1. git reset --mixed: Moves the branch pointer and unstages changes, but keeps file modifications in the working tree. This is the default if you do not specify a mode.

Example:

git reset HEAD~1

or explicitly:

git reset --mixed HEAD~1

This means:

  • move back one commit

  • keep file changes in your working directory

  • unstage them

Use Case

You want to undo a commit, but then re-stage the files more selectively.

  1. git reset --hard: Moves the branch pointer, unstages changes, and resets files in the working tree. This is the most dangerous form because it can discard local work.

Example:

git reset --hard HEAD~1

This means:

  • move back one commit

  • reset the staging area

  • reset the working tree

  • discard local changes that were part of that state transition

Warning

--hard can permanently destroy local work that is not otherwise recoverable through reflog or other advanced recovery methods. Use it only when you are sure.

Note

  • When Is reset Appropriate?

reset is most appropriate when:

  • you are cleaning up local history

  • you have not safely shared the commits yet

  • you want to rewrite or remove recent local commits

  • you want to unstage files

  • you want to discard local changes intentionally

reset is usually best for local correction. It is more dangerous when the commits were already pushed and other people may rely on them.

  • Why reset Can Be Dangerous on Shared Branches

If you use reset on commits that were already pushed to a shared branch:

  • history changes

  • commit IDs may disappear from the visible branch

  • collaborators may still have the old history

  • force-pushing may become necessary

  • confusion and integration problems can follow

Use reset freely on your own local mistakes. Use it very carefully on anything already shared.


git revert#

git revert is different from reset. Instead of moving history backward, revert creates a new commit that undoes the effect of an earlier commit. Actually, it keeps the history, but add a new commit that reverses the change. This makes revert much safer for shared history.

Example:

git revert abc1234

This tells Git:

  • find commit abc1234

  • compute the inverse of its effect

  • create a new commit applying that inverse

Result:

The original commit remains in history. The new revert commit records that its effects were undone.

Note

When Should You Prefer revert?

Prefer revert when:

  • the bad commit was already pushed

  • the branch is shared

  • you want a clear audit trail

  • you need to undo a change safely without rewriting history

Professional Rule:

  • local mistake not yet shared → often reset

  • shared bad commit → often revert


git restore#

git restore was introduced to make certain file-level operations clearer.

It is mainly used to:

  • restore file content in the working tree

  • unstage files from the index

It helps separate these actions from older overloaded checkout behavior.

Example:

git restore app.py

This means:

  • discard local modifications in app.py

  • restore it from the current HEAD state

Use Case

You edited a file locally and want to abandon those uncommitted edits. You accidentally staged a file and want to unstage it without losing the edit.

  1. Unstage a File with restore

Example:

git restore --staged app.py

This means:

  • remove app.py from the staging area

  • keep the file changes in the working tree

  1. Restore from Another Source

You can also restore a file from a particular commit.

Example:

git restore --source=abc1234 README.md

This means:

  • take README.md as it existed in commit abc1234

  • restore it into the working tree

This is useful when you want one file from an earlier point without changing the whole branch history.

Note

Why restore Is Useful

restore is useful because it lets you think in terms of file state, not only commit history.

It is especially helpful for:

  • undoing accidental local edits

  • unstaging files cleanly

  • restoring a single file from a chosen commit

It is often safer and clearer than reaching for reset when your goal is only file-level correction.


amend#

git commit --amend: Changes the most recent commit. It is commonly used when:

  • the last commit message is wrong

  • you forgot to include one file

  • you want to slightly adjust the most recent commit

Example:

git commit --amend

Git opens the commit message editor and lets you replace the last commit with a new version.

  1. Amend the Last Commit Message

Example:

git commit --amend -m "Fix login validation for empty input"

This replaces the latest commit message with a new one.

Note

Even changing only the message rewrites the commit. That means the commit hash changes.

  1. Amend to Add a Forgotten File

Suppose you made a commit but forgot one file.

git add missing_file.py
git commit --amend

Now Git rebuilds the latest commit to include that file. This is a very common and useful correction pattern.

Note

When Is Amend Safe?

Amend is safest when:

  • the commit is still local

  • you have not pushed it yet

  • nobody else is depending on that exact commit hash

Warning

If you amend a commit that was already pushed:

  • the commit hash changes

  • force-push may be needed

  • collaborators can be confused if they already used the old commit


git cherry-pick#

git cherry-pick applies the effect of one specific commit onto your current branch. Take that commit over there, and replay its effect here. This is very useful when you do not want to merge a whole branch, but only want one particular change.

Example:

Suppose commit abc1234 on another branch contains an important bug fix. You are on your current branch and run:

git cherry-pick abc1234

Git then attempts to apply that commit here as a new commit.

Result:

You get the effect of the original commit, but the new branch history remains separate.

Note

When Is cherry-pick Useful?

Cherry-pick is useful when:

  • one branch contains a bug fix you need elsewhere

  • you want a single commit, not the whole branch history

  • you are backporting a fix to a release branch

  • you want to reuse a precise change selectively

A bug is fixed on main, and the same fix is needed on a maintenance branch:

git switch release/1.2
git cherry-pick abc1234

Warning

Cherry-Pick Can Also Conflict

Cherry-pick is not magic. If the target branch differs too much, the commit may not apply cleanly. Then Git may stop with conflicts, and you resolve them similarly to merge or rebase conflicts:

git status
git add .
git cherry-pick --continue

Or, if needed:

git cherry-pick --abort

Warning

Common Mistakes

  1. Using reset --hard too casually

This can destroy local work.

  1. Using reset instead of revert on shared branches

This can make collaboration messy.

  1. Forgetting that amend rewrites history

Even a message-only change creates a new commit hash.

  1. Cherry-picking large sequences without thinking

This can create duplicated or confusing history if done carelessly.

  1. Using history-rewriting commands after public sharing without coordination

This is one of the most common professional Git mistakes.


Practical Scenarios#

  1. Undo the Last Local Commit, Keep the Changes

You committed too early, but want to keep working. A good option is:

git reset --mixed HEAD~1

Now:

  • the commit is removed from visible branch history

  • the file changes remain in your working tree

  • nothing is staged

You can now stage files more selectively and recommit properly.

  1. Undo a Shared Commit Safely

A bad commit is already on main and must be undone.

A safer option is:

git revert abc1234

This creates a new commit that reverses the earlier one. This is the professional-safe pattern for shared history.

  1. Remove a File from Staging

You accidentally staged config.local.json, but you do not want it in the next commit.

Use:

git restore --staged config.local.json

Now:

  • the file is unstaged

  • your local edits remain

  1. Fix the Last Commit

You committed, then realized the message is weak or one file is missing. A common correction is:

git add forgotten_file.py
git commit --amend -m "Add validation and tests for empty email input"

This replaces the previous final commit with a better version.

  1. Backport a Fix to Another Branch

Suppose main has a bug fix, but you also need it on release/1.2.

git switch release/1.2
git cherry-pick abc1234

This applies only that selected fix to the release branch, without merging unrelated work from main.

Exercises:

  1. make two commits in a test repository

  2. use git reset --soft HEAD~1 and observe the staged state

  3. repeat with git reset --mixed HEAD~1

  4. create a new commit and undo it with git revert

  5. modify a file and discard changes using git restore

  6. stage a file and unstage it using git restore --staged

  7. make a commit with a bad message and fix it using git commit --amend

  8. create two branches and use git cherry-pick to move one fix from one branch to another

  9. compare histories using:

git log --oneline --graph --all

These exercises build intuition much faster than memorizing definitions.

git bisect#

This is the professional way when you know:

  • one commit that is definitely good

  • one commit that is definitely bad

Git then performs a binary search through history to find the first bad commit much faster.

  • Step 1: Start bisect

git bisect start
  • Step 2: Mark the current state as bad

git bisect bad

This usually means: “the version I am on right now is broken.”

  • Step 3: Mark an older known-good commit

git bisect good <commit-id>

Now Git chooses a commit in the middle.

  • Step 4: Test that commit

Run your program or your tests.

  • If it works:

    git bisect good
    
  • If it fails:

    git bisect bad
    

Git keeps narrowing the search until it prints something like:

<commit-id> is the first bad commit

Schematic example of git bisect

Suppose your history is:

A - B - C - D - E - F

You know:

  • B was still good

  • F is bad

You run:

git bisect start
git bisect bad
git bisect good B

Git may test D first.

  • If D is good, then the bug must be in E or F

  • If D is bad, then the bug must be in C or D

So instead of checking every commit one by one, Git cuts the search space in half each time.


If you already have automated tests, bisect becomes much stronger

If your project has a command that returns success/failure, Git can automate the search.

Examples:

git bisect run pytest
git bisect run npm test
git bisect run python -m unittest

In this mode:

  • test passes → Git treats commit as good

  • test fails → Git treats commit as bad

This is one of the best tools for tracking down regressions.


How this connects to reset, revert, restore, amend, and cherry-pick

After you find the problematic commit, you can decide what to do:

  • If the bad commit was already shared

Use:

git revert <bad-commit>
  • If the bad commit is only local and you want to rewrite history

Use one of:

git reset --soft <target>
git reset --mixed <target>
git reset --hard <target>
  • If the problem is just one file

Use:

git restore path/to/file
  • If the issue is only in the latest commit message or contents

Use:

git commit --amend
  • If you want to bring a good fix from another branch

Use:

git cherry-pick <good-commit>

So the missing skill is often not the command itself, but finding the correct commit first.