# 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?](#git)
- [What is GitHub](#github)

---

# What is Git?
<a name="git"></a>
**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**

``` bash
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
```
These values appear in your commits.

-  **View all configuration**

``` bash
git config --list
```

-  **View one specific value**

``` bash
git config user.name
```

- **Set the default editor**

``` bash
git config --global core.editor "code --wait"
```

- **Enable color output**

``` bash
git config --global color.ui auto
```

- **Create aliases**

Git aliases let you define shorter names for longer commands.

``` bash
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:

```bash
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:

``` text
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:

``` bash
git config --list --show-origin
```
Example output

``` text
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:

``` bash
git config --global pager.log off
```

This makes `git log` print directly in the terminal.

- **Other examples**

``` bash
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:

``` bash
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:

``` bash
git init
```

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

``` bash
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:

``` bash
git add .
git commit -m "your message"
```

:::{note}

-   `git add .` stages the changes
-   `git commit -m "..."` records those staged changes into Git history
  
:::

 **Example**

``` bash
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:

``` bash
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**

``` bash
git log --oneline
```

**Graph view**

``` bash
git log --oneline --graph
```

**Last 5 commits**

``` bash
git log -n 5
```

**Show full patch with each commit**

``` bash
git log -p
```

**Show history for one file**

``` bash
git log file.txt
```
:::

## Compare Changes with `git diff`

To compare changes, use:

``` bash
git diff
```

:::{note}

There are **git diff** variations and options:

- **Basic meaning; unstaged changes**

This shows changes that are **not staged yet**.
``` bash
git diff
```

Example

``` diff
- console.log("Hello");
+ console.log("Hello World");
```


- **Staged changes**

``` bash
git diff --staged

or

git diff --cached
```
- **Compare with the latest commit**

``` bash
git diff HEAD
```

- **Compare two commits**

``` bash
git diff commit1 commit2
```

- **Compare one file**

``` bash
git diff file.txt
```

- **Show only file names**

``` bash
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**
```bash
git branch
```

- **Create a new branch**
```bash
git branch feature/login
```

- **Switch to another branch**
```bash
git switch main feature/login
```

- **Create and switch in one step**
```bash
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:

:::{admonition} ⭐ Summary

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

Example:

```bash
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:


```bash
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 name="github"></a>

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

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

Example:

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

✔ This connects your local repo → GitHub repo


- **🔍 Step 3 — Verify Remote Connection**

```bash
git remote -v
```

Expected output:

```bash
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`:

```bash
git push -u origin main
```

If it's `master`:

```bash
git push -u origin master
```

✔ `-u` sets upstream so future pushes are simpler:

```bash
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`)

:::

:::{summary}

Your workflow now becomes:

```bash
git add .
git commit -m "your message"
git push
```

And to get updates:

```bash
git pull
```
:::

##  HTTPS vs SSH for GitHub 

There are two common ways to connect Git to GitHub:

- **HTTPS**

``` bash
git clone https://github.com/user/repo.git
```
-   simpler to start with
-   commonly available by default
-   often uses a token for authentication

- **SSH**

``` bash
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:


``` bash
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:

``` bash
~/.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:

``` bash
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:

``` bash
ssh -T git@github.com
```

**Meaning of `-T`**

`-T` means:

-   do not open an interactive shell
-   only test authentication

**Successful output**

``` text
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:

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

Type:

``` bash
yes
```

This stores GitHub\'s host fingerprint on your machine.

---

---
- **Permission denied**

You may see:

``` text
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:

``` bash
ls ~/.ssh
```

You should usually see files like:

``` text
id_ed25519
id_ed25519.pub
```

---


---
- **Check Whether the Key Is Loaded**

To list keys currently loaded in the SSH agent:

``` bash
ssh-add -l
```

If your key is not loaded, add it:

``` bash
ssh-add ~/.ssh/id_ed25519
```
---

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

No.

You usually run:

``` bash
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:

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

SSH authentication happens automatically in the background.
:::

::: {summary}
**Workflow Summary**

A typical workflow after setup looks like this:

1.  create or enter your project folder
2.  initialize Git
3.  make changes
4.  stage changes
5.  commit
6.  push to GitHub using SSH

**Example**

``` bash
git init -b main
git add .
git commit -m "Initial commit"
git remote add origin git@github.com:username/repository.git
git push -u origin main
```
:::

::: {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.

``` bash
# 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:
```bash
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:

```bash
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)**

```bash
git push origin main
```

Sends your local commits to the main branch on the remote repository
- **Pull (get updates from GitHub)**

```bash
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:

``` text
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:

``` text
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.

``` bash
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:

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

Then verify your remotes:

``` bash
git remote -v
```

Expected result:

``` text
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**

``` bash
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.

``` bash
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.

``` bash
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**

``` text
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

``` bash
## 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:

``` bash
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

``` bash
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:

``` bash
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:

``` bash
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:

``` bash
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.

``` bash
git fetch origin
git switch feature
git rebase origin/main
```

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

``` bash
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.

``` bash
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:

``` bash
def greet():
    return "Hello"
```

And your feature branch changed it to:

``` bash
def greet():
    return "Hello, user"
```

But meanwhile `main` changed it to:

``` python
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:

``` text
<<<<<<< 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:

``` bash
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**

``` bash
git switch main
git merge feature/login
```

- **Step 2: Check status**

``` bash
git status
```

Git shows which files are unmerged.

- **Step 3: Open each conflicted file**

Look for markers like:

``` text
<<<<<<<
=======
>>>>>>>
```

- **Step 4: Edit to the correct final result**

Choose:

-   your version
-   their version
-   or a combination

- **Step 5: Stage resolved files**

``` bash
git add path/to/file
```

- **Step 6: Finish the merge**

If needed:

``` bash
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.


``` bash
git merge --abort
```

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

---
**Conflict During Rebase**

If a conflict happens during rebase:

``` bash
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:

``` bash
git add .
git rebase --continue
```

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

``` bash
git rebase --skip
```

**Optional: Abort the whole rebase**

``` bash
git rebase --abort
```
---
**Useful Commands During Conflict Resolution**

- **See current status**

``` bash
git status
```

- **See differences**

``` bash
git diff
```

- **See staged differences**

``` bash
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:

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

you often use:

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

or, in some teams:

``` text
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:

``` text
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.

``` bash
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:

``` bash
git remote -v
```

Possible output:

``` text
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.

``` bash
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:

``` bash
git switch -c feature/improve-login-validation
```

or:

``` bash
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.

``` bash
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

``` bash
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:

``` bash
git push origin feature/improve-login-validation
```

This is a major difference from the fork workflow.

:::{note}

**In Fork Workflow**

You push to:

``` text
your fork
```

**In Contributor Workflow**

You push to:

``` text
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:

``` text
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**

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

**Contributor PR**

``` text
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

``` bash
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:

``` bash
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

``` bash
git switch fix/login-timeout
git fetch origin
git merge origin/main
```

- Option B: Rebase onto `main`

``` bash
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:

``` bash
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.

:::

:::{summary}

**Suggested Best Practices for Contributors** 
1.  always update `main` before branching
2.  create one branch per topic or fix
3.  use clear names for branches
4.  commit logically, not randomly
5.  open Pull Requests early enough for review
6.  keep PRs focused and readable
7.  merge or rebase from `main` regularly if the branch stays open
8.  follow repository rules and templates
9.  do not treat write access as permission to skip discipline
10. protect `main` whenever possible

:::

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

``` bash
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.

2. **`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:

``` bash
git reset HEAD~1
```

or explicitly:

``` bash
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.

3. **`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:

``` bash
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:

``` bash
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`

:::{summarry}

**`reset` vs `revert`**

This distinction is fundamental.

**`reset`:**

-   rewrites branch position
-   can rewrite visible history
-   best for local cleanup
-   can be dangerous on shared branches

**`revert`:**

-   does not remove the original commit
-   adds a new undo commit
-   safer for shared history
-   leaves a clear record of what happened


**reset** = "pretend the branch pointer moved back"

**revert** = "add a new commit that cancels an old one"
:::

---

### `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:

``` bash
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:

``` bash
git restore --staged app.py
```

This means:

-   remove `app.py` from the staging area
-   keep the file changes in the working tree

2. **Restore from Another Source**
   
You can also restore a file from a particular commit.

Example:

``` bash
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:

``` bash
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:

``` bash
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.

:::

2. **Amend to Add a Forgotten File**
   
Suppose you made a commit but forgot one file.


``` bash
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:

``` bash
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:

``` bash
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:

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

Or, if needed:

``` bash
git cherry-pick --abort
```
:::

---
:::{summary}
**Choosing the Right Tool**

A professional Git user learns to choose based on intent.

1. Use `reset` when:

-   you want to rewrite recent local history
-   you want to undo local commits before sharing
-   you want to unstage or discard local state

2. Use `revert` when:

-   a bad commit is already shared
-   you want a safe undo with history preserved

3. Use `restore` when:

-   you want to restore one file
-   you want to unstage a file
-   you want file-level correction without branch history surgery

4. Use `amend` when:

-   you only need to fix the last commit

5. Use `cherry-pick` when:

-   you want one specific commit on another branch
  
:::

:::{warning}
**Common Mistakes**

1. Using `reset --hard` too casually

This can destroy local work.

2. Using `reset` instead of `revert` on shared branches
   
This can make collaboration messy.

3. Forgetting that `amend` rewrites history
   
Even a message-only change creates a new commit hash.

4. Cherry-picking large sequences without thinking
   
This can create duplicated or confusing history if done carelessly.

5. 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:

``` bash
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.

2. **Undo a Shared Commit Safely** 

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

A safer option is:

``` bash
git revert abc1234
```

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

3. **Remove a File from Staging** 

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

Use:

``` bash
git restore --staged config.local.json
```

Now:

-   the file is unstaged
-   your local edits remain

4. **Fix the Last Commit** 

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

``` bash
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.

5. **Backport a Fix to Another Branch**
   
Suppose `main` has a bug fix, but you also need it on `release/1.2`.


``` bash
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`.


:::{summary}
**A Useful Safety Principle**

Before running an undo-related command, ask:

**Question 1**

Do I want to **rewrite history**, or do I want to **preserve history**?

-   rewrite history → maybe `reset` or `amend`
-   preserve history → maybe `revert`

**Question 2**

Am I changing a **file state**, or am I changing **commit history**?

-   file state → maybe `restore`
-   commit history → maybe `reset`, `revert`, `amend`, or `cherry-pick`

**Question 3**

Has this already been shared with others?

-   not shared → more freedom
-   shared → prefer safer history-preserving choices

:::


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

``` bash
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**

``` bash
git bisect start
```

- **Step 2: Mark the current state as bad**

``` bash
git bisect bad
```

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

- **Step 3: Mark an older known-good commit**

``` bash
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:

    ``` bash
    git bisect good
    ```

-   If it fails:

    ``` bash
    git bisect bad
    ```

Git keeps narrowing the search until it prints something like:

``` text
<commit-id> is the first bad commit
```
---

**Schematic example of `git bisect`**

Suppose your history is:

``` text
A - B - C - D - E - F
```

You know:

-   `B` was still good
-   `F` is bad

You run:

``` bash
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:

``` bash
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:

``` bash
git revert <bad-commit>
```

- If the bad commit is only local and you want to rewrite history

Use one of:

``` bash
git reset --soft <target>
git reset --mixed <target>
git reset --hard <target>
```

- If the problem is just one file

Use:

``` bash
git restore path/to/file
```

- If the issue is only in the latest commit message or contents

Use:

``` bash
git commit --amend
```

- If you want to bring a good fix from another branch

Use:

``` bash
git cherry-pick <good-commit>
```

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

:::{summary}

**Practical workflow you can follow in real projects**

When you suspect a regression:

1.  Inspect history

    ``` bash
    git log --oneline --graph --all
    ```

2.  Identify:

    -   one known good commit
    -   one known bad commit

3.  Use `git bisect`

    ``` bash
    git bisect start
    git bisect bad
    git bisect good <known-good-commit>
    ```

4.  Test each checked-out revision

5.  Finish with:

    ``` bash
    git bisect reset
    ```

6.  Then choose the appropriate fix:

    -   `revert`
    -   `reset`
    -   `restore`
    -   `amend`
    -   `cherry-pick`

:::


:::{summary}
**Quick command summary **
**Find commit IDs**

``` bash
git log --oneline
git log --oneline --graph --all
```

**Refer to recent commits**

``` bash
HEAD
HEAD~1
HEAD~2
HEAD~3
```

**Move to a specific commit temporarily**

``` bash
git checkout <commit-id>
```

**Find the first bad commit**

``` bash
git bisect start
git bisect bad
git bisect good <commit-id>
git bisect good
git bisect bad
git bisect reset
```

**Automated bisect**

``` bash
git bisect run pytest
```
:::

