# Git

***

### **Git Reference Guide — From Fundamentals to Everyday Workflow**

#### Sparse Checkout

*Best if you need to contribute code or keep the files updated.*

This method uses the `sparse-checkout` feature (available in Git 2.25+) to clone the "shell" of the repo without downloading the file contents, then **explicitly selects the folder you want to populate**.

The Commands:

```bash
# 1. Clone the repo structure without downloading files (saves bandwidth)
git clone --filter=blob:none --sparse <REPO_URL>

# 2. Enter the repository directory
cd <REPO_NAME>

# 3. Tell Git which directory you actually want
git sparse-checkout set <TARGET_DIRECTORY_PATH>
```

* **Note**: Replace `<TARGET_DIRECTORY_PATH>` with the relative path from the repo root (e.g., `src/components`, not `C:/Users/...`).
* **Result:** Your local folder will contain only the `.git` folder and the specific directory you requested.

#### **Adding a Remote**

In Git, any external repository you connect to is known as a **remote**. Developers typically name the primary remote repository — often hosted on GitHub, GitLab, or another platform — **`origin`**.

The remote is considered your team’s *authoritative source of truth*: the version of code everyone agrees is canonical and up-to-date.

```bash
git remote add <name> <url>
```

Example:

```bash
git remote add origin git@github.com:yourname/yourrepo.git
```

This simply *registers* the remote; no data is pulled yet.

***

#### **Git Log Essentials**

`git log` shows your project’s commit history. Useful variations include:

* `git log -n 10` — show the last 10 commits
* `git log --oneline` — compact one-commit-per-line summary
* `git log --oneline --graph --all` — visualize history as a branching graph
* `git cat-file -p <hash>` — inspect the contents of a commit object

Example:

```bash
git cat-file -p 5ba786f
```

***

#### **Git Configuration**

Git settings exist in four scopes, each overriding the previous:

1. **system** — applies to all users on the device (`/etc/gitconfig`)
2. **global** — applies to your user (`~/.gitconfig`)
3. **local** — applies to the repository (`.git/config`)
4. **worktree** — `.git/config.worktree`, a file that configures Git for part of a project

Most developers use `--global` for identity and editor preferences, and `--local` for project-specific overrides.

<figure><img src="https://2332658533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5fhKjYnbaQlTPTcaO85%2Fuploads%2FOWMu2HKW3izIacPoyTYe%2Fe4S7M9u-1055x600.png?alt=media&#x26;token=36e869c6-7acb-4e3a-8457-814a1479d109" alt=""><figcaption></figcaption></figure>

**Common Commands**

```bash
git config --global user.name "Your Name"
git config --global user.email "your@email.com"
git config --list --global
git config --list --local
git config --get user.name
git config --unset <key>
git config --remove-section <section>
```

Configuration command:

* &#x20;`git config --add --global user.name "John Doe"`&#x20;
* `git config --add --global user.email "john.doe@example.com"`

Let's break down the commands:

* `git config`: The command used to manage your Git configuration settings.
* `--add`: Flag indicating you want to add a new configuration entry.
* `--global`: Flag specifying this configuration should be saved globally in your `~/.gitconfig`. The alternative is `--local`, which saves the configuration only for the current repository.
* `user`: The configuration section.
* `name`: The configuration key within that section.
* `"John Doe"`: The value you're assigning to the key.

Additional commands:

* `git config --get <key>`: retrieve an individual configuration key's value, e.g. user.name
* `git config --list`: display all current global configuration settings.
* `git config --list --local`: display all current local configuration settings
* `git config --remove-section [section]`: delete an entire configuration section, e.g. company.property="some value". This will remove the company section and everything within it.
* `git config --unset company.property`: delete a specific key within a section (removes one occurrence). In this case, one of the "property" keys.
* `git config --unset-all company.property`: delete all occurrences of a key (property) within a section.

***

#### **Branching**

[**https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell**](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)

A branch represents an independent line of development. Instead of modifying `main` directly, you create branches to isolate work. For example, you might want to create a feature that changes the color schema without editing the main/master branch directly. Instead, you could work on that new branch and merge it into the main branch (or choose to delete if you don't need it).

<figure><img src="https://2332658533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5fhKjYnbaQlTPTcaO85%2Fuploads%2FElFD8xrqVKgUPk2pTR1v%2FiH1kl8l-539x588.png?alt=media&#x26;token=024026b7-b988-42ed-a8ff-1880c1920c78" alt=""><figcaption></figcaption></figure>

**Check all branches including the active one**

```bash
git branch
```

**Creating and Switching Branches**

```bash
git branch new-feature               # create branch
git switch new-feature               # switch to it
git switch -c new-feature            # create + switch
git switch -c fix-bug abc1234        # branch from a specific commit
# This command creates a new branch and 
# specifies which commit you want it to be branched off of.
```

**Renaming and Deleting Branches**

```bash
git branch -m oldname newname
git branch -D unwanted-branch
```

**Setting Default Branch Name**

```bash
git config --global init.defaultBranch main # set default branch as "main" for new repositories
# --global option to make it global
```

Branches are simply references — stored as files under `.git/refs/heads/`.

Which commits are part of “Joe\_branch”?

```bash
	    G - H    Joe_branch
	   /
A - B - C - D   main
\\
E - F        Kate_branch
```

Answer: A-B-G-H

When you create a new branch, it uses the current commit you are on as the branch base. For example, if you're on your main branch with 3 commits, A, B, and C, and then you run git switch -c my\_new\_branch, your new branch will look like this:

<figure><img src="https://2332658533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5fhKjYnbaQlTPTcaO85%2Fuploads%2F0xkDMCgdsfzNbld9IKQP%2Foah2FRD-1228x682.png?alt=media&#x26;token=43ce48bb-93a0-4698-9be2-b9819aea4b6f" alt=""><figcaption></figcaption></figure>

So, if you create a new branch my\_new\_branch and run git log, you will have the same commits as the main branch.

***

#### **Reading Commit History**

Useful enhancements to `git log`:

**Decorations**

```bash
git log --decorate=short   # default
git log --decorate=full
git log --decorate=no
```

Run `git log --decorate=full`. You should see that instead of just using your branch name, it will show the full ref name. A [ref](https://git-scm.com/book/en/v2/Git-Internals-Git-References) is just a pointer to a commit. All branches are refs, but not all refs are branches.

Run `git log --decorate=no`. You should see that the branch names are no longer shown at all.

The second is [`--oneline`](https://git-scm.com/docs/git-log#Documentation/git-log.txt---oneline). This flag will show you a more compact view of the log. I use this one all the time, it just makes it so much easier to see what's going on.

* `git log --oneline`
* Or check a branch’s logs by name: `git log --oneline main`

**Graph view**

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

```bash
* 8a9c1f3 (HEAD -> main) E: README.md edited
| * 2d5e7b4 (add_classics) D: committed authors.csv
|/
* 6f2a8c9 C: add references
* 4b3d9e1 C: add references
* 7c5f2a8 B: add headers
* 9e1a4d6 A: add index.md
```

**During merges**

```bash
git log --oneline --decorate --graph --parents
```

The `--parents` flag shows each commit along with its parent(s), which is especially useful when examining merge commits (which have **two parents**).

Example to the above command:

If you’re conducting a merge operation, such a command might be useful:

* `git log --oneline --decorate --graph --parents`
  * Each asterisk `*` represents a commit in the repository. There are multiple commit hashes on each line because the `--parents` flag logs the parent hash(es) as well.

Let’s say you’ve just conducted a merge and run the above command. This is an output you might get (similar to this).

<figure><img src="https://2332658533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5fhKjYnbaQlTPTcaO85%2Fuploads%2FBSlwa4p0lBXMBgpBjlam%2FBildschirmfoto%202025-08-18%20um%2020.12.09.png?alt=media&#x26;token=8085ba2e-b49a-451d-ada1-3ca4470e5afd" alt=""><figcaption></figcaption></figure>

* The first line, with these three hashes: `89629a9 d234104 b8dfd64` is our recent merge commit. The first hash, `89629a9` is the merge commit's hash, and the other two are the parent commits.
* The next section is a visual representation of the branch structure. It shows the commits on the `add_classics` branch and the `main` branch before the merge. Notice that they both share a common parent.
* The next two lines are just "normal" commits, each pointing to their parent.
* The last line is the initial commit and therefore has no parent.

***

#### **Git Internals: Refs and Files**

Git stores all metadata under `.git/`.

* The "heads" (or "tips") of branches are stored in the `.git/refs/heads` directory. If you cat one of the files in that directory, you should be able to see the commit hash that the branch points to.
  * → `.git/refs/heads/*`
* Remote branch refs → `.git/refs/remotes/*`
* Current position → `.git/HEAD`

You can inspect a branch pointer:

```bash
cat .git/refs/heads/main
```

This prints the commit hash the branch it currently references.

***

#### **Merging**

[**https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging**](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging)

**Standard Merge**

A regular merge finds the **merge base** (best common ancestor), combines changes, and creates a **merge commit** with two parents.

Example:

Before:

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

After:

```bash
A - B - C ---- F   main
       \      /
        D - E      feature
```

The merge will:

1. Find the "merge base" commit, or "best common ancestor" of the two branches. In this case, `B`.
2. Replay the changes from `main`, starting from the best common ancestor, into a new commit.
3. Replay the changes from `feature` onto `main`, starting from the best common ancestor.
4. Records the result as a new commit, in our case, `F`.
5. `F` is special because it has *two parents*, `C` and `E`.

**Fast-Forward Merge**

[**https://git-scm.com/docs/git-merge#\_fast\_forward\_merge**](https://git-scm.com/docs/git-merge#_fast_forward_merge)

If your branch contains all commits from `main`, Git can simply *move the pointer* without creating a merge commit. A "fast-forward" – it's like simply fast-forwarding your branch's history to include the new commits from the merged branch. Let's say we start with this:

Before:

```bash
A - B       main
     \
      C     feature
```

And we run this while on `main`:

* `git merge feature`

After:

```bash
A - B - C   main
```

Fast-forward merges keep history linear.

Because `feature` has *all the commits* that `main` has, Git automatically does a fast-forward merge. It just moves the pointer of the "base" branch to the tip of the "feature" branch:

```bash
		        feature
A - B - C   main
```

Notice that with a fast-forward merge, **no** [**merge commit**](https://git-scm.com/docs/git-merge#_true_merge) **is created**.

This is a common workflow when working with Git on a team of developers:

1. Create a branch for a new change
2. Make the change
3. Merge the branch back into `main` (or whatever branch your team dubs the "default" branch)
4. Remove the branch
5. Repeat

***

#### **Rebase**

Rebasing replays your commits on top of a new base, producing a linear history.

*Before:*

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

We're working on `feature`, and want to bring in the changes our team added to main so we're not working with a stale branch. We could merge main into `feature`, but that would create an additional merge commit. Rebase avoids a merge commit by replaying the commits from `feature` on top of main. After a rebase, the history will look like this:

*After:*

```bash
A - B - C - D' - E'   feature (rebased)
```

Rebase is ideal for cleaning up or updating your in-progress branches.

**Important Rule**

Never rebase shared/public branches like `main`. Rewriting history breaks other developers’ clones.

**When to Rebase**

[git rebase](https://git-scm.com/docs/git-rebase) and [git merge](https://git-scm.com/docs/git-merge) are different tools.

An advantage of merge is that it preserves **the true history of the project**. It shows when branches were merged and where. One disadvantage is that it can create *a lot of merge commits*, which can make the history harder to read and understand.

A linear history is generally easier to read, understand, and work with. Some teams enforce the usage of one or the other on their `main` branch, but generally speaking, you'll be able to do whatever you want with your own branches.

**Warning**

You should *never* rebase a public branch (like `main`) onto anything else. Other developers have it checked out, and if you change its history, you'll cause a lot of problems for them.

However, with your own branch, you can rebase onto other branches (including a public branch like `main`) as much as you want.

***

#### **Reset**

`git reset` moves the current branch pointer to a different commit. The [git reset](https://git-scm.com/docs/git-reset) command can be used to undo the last commit(s) or any changes in the index (staged but not committed changes) and the worktree (unstaged and not committed changes).

**Soft Reset**

```bash
git reset --soft <hash>
```

Moves HEAD but **keeps your changes staged**.

The `--soft` option is useful if you just want to **go back to a previous commit, but keep all of your changes**. Committed changes will be uncommitted and staged, while uncommitted changes will remain staged or unstaged as before.

The files will remain as they were, only the commit history will be modified.

**Hard Reset (Dangerous)**

```bash
git reset --hard <hash>
```

Moves HEAD and **discards all working directory and staging changes**.\
This is irreversible unless recovered via reflog.

I want to stress how **dangerous** this command can be. If you were to simply delete a *committed* file, it would be trivially easy to recover because it is tracked in Git. However, if you used `git reset --hard` to undo committing that file, it would be deleted for good. The changes within that file will be deleted forever.

Always be careful when using `git reset --hard`. It's a powerful tool, but it's also a dangerous one.

***

#### **Working With Remotes**

A remote is another repository with (usually) shared history.

We can have "remotes", which are just external repos with *mostly* the same Git history as our local repo.

When it comes to Git (the CLI tool), there really isn't a "central" repo. GitHub is just someone else's repo. Only by convention and convenience have we, as developers, started to use GitHub as a "source of truth" for our code.

**Add a Remote**

```bash
git remote add origin <uri>
```

In git, another repo is called a "remote." The standard convention is that when you're treating the remote as the "authoritative source of truth" (such as GitHub) you would name it the **"origin"**.

By "authoritative source of truth" we mean that it's the one you and your team treat as the "true" repo. It's the one that contains the most up-to-date version of the accepted code.

The URI is probably either a Github/Gitlab URI or your local path if you’re using a local repo as the remote.

**Fetch Remote Metadata**

Adding a remote to our Git repo does *not* mean that we automagically have all the contents of the remote. First, we need to fetch the contents.

However, just because we fetched all of the metadata from the remote repo doesn't mean we have all of the files.

```bash
git fetch origin
```

**Inspect Remote Branch Logs**

The `git log` command isn't only useful for your *local* repo. You can log the commits of a *remote* repo as well!

* `git log remote/branch`
  * For example, if you wanted to see the commits of a branch named `remote_branch` from a remote named `origin` you would run:: `git log origin/remote_branch`
  * Or: `git log origin/update_dune --oneline`

```bash
git log origin/main
git log origin/feature --oneline
```

**Merge a Remote Branch**

```bash
git merge origin/feature
```

For example, if you wanted to merge the `remote_branch` branch of the remote `origin` into your local `main` branch, you would run this inside the local repo while on the `main` branch:

* `git merge origin/remote_branch`

***

#### **Git Pull**

Fetching is nice, but most of the time we want the *actual file changes* from a remote repo, not just the metadata.

`git pull` is essentially:

```bash
git fetch + git merge (or rebase)
```

Syntax:

```bash
git pull [remote] [branch]
```

The \[...] syntax means that the bracketed remote and branch are optional. If you execute git pull without anything specified it will pull your current branch from the remote repo.

**Prefer Rebase on Pull (cleaner history)**

**Keep your serious projects on GitHub.** If your computer explodes, you'll have a backup, and if you're ever working from another computer, you can simply clone the repo and get back to work.

**Consider using rebase instead of merge.** Configure Git to rebase by default on pull rather than merge—this keeps your history linear and easier to follow. To set this up, add the following to your global Git config:

```bash
git config --global pull.rebase true
```

***

#### **.gitignore**

**You can have `.gitignore` files in multiple locations**

A `.gitignore` file doesn't have to live only at the top level of your project. You can place `.gitignore` files in various folders throughout your codebase, and each one will control what gets ignored in its own folder and any folders beneath it, the patterns apply recursively within their directories.

**Example scenario:**

```bash
project/
├── resources/
│   ├── .gitignore
│   ├── banner.jpg
│   └── internal_use.png
├── app.py
├── test.py
├── virtualenv/
│   └── scripts/
│       ├── init
│       └── interpreter
.gitignore
```

The `.gitignore` inside the `resources/` folder lists:

* `internal_use.png`
* `app.py`

The root `.gitignore` lists:

* `virtualenv/scripts/init`

**What actually gets ignored:**

Only `internal_use.png` from the resources folder will be ignored. Even though `app.py` appears in the nested ignore file, it won't be ignored because that rule only applies within the `resources/` directory and its subdirectories—but `app.py` actually lives in the parent directory, outside the scope of that nested ignore file.

**Common Patterns**

**Wildcards**

```bash
*.log
```

**Rooted patterns**

```bash
/main.py
```

**Negation**

```bash
*.txt
!important.txt
```

**Directory rules**

```bash
temp/*
!temp/readme.md
```

Order matters — later patterns override earlier ones.

***

#### **HEAD**

`HEAD` is simply “where you are now.”\
Usually it points to the current branch, which in turn points to a commit.

Like all things in `.git`'s internals, `HEAD` is just stored in a file.

```bash
cat .git/HEAD
```

***

#### **Reflog — Your Emergency Undo Button**

[**https://git-scm.com/docs/git-reflog**](https://git-scm.com/docs/git-reflog)

`git reflog` tracks where `HEAD` and branches have pointed over time. It's pronounced ref-log, not re-flog and is kinda like `git log` but stands for "reference log" and specifically logs the changes to a "reference" that have happened over time.\
Even commits from deleted branches still appear here (temporarily).

Reflog uses a different format to show the history of a branch or HEAD: one that's more concerned with the number of steps back in time. For example:

```bash
git reflog
```

| reflog   | meaning                    |
| -------- | -------------------------- |
| HEAD@{0} | where HEAD is now          |
| HEAD@{1} | where HEAD was 1 move ago  |
| HEAD@{2} | where HEAD was 2 moves ago |
| HEAD@{3} | where HEAD was 3 moves ago |
| ...      | ...                        |

**Recovering a Deleted Commit or File**

1. Find the commit in the reflog.
2. Inspect it using `git cat-file -p <hash>`.
3. Use the tree → blob hashes to locate the file.
4. Restore the file manually.
5. Commit the recovered file.

Reflog is often the **only** way to recover work after a destructive reset.

***

### **Data Recovery**

You've just deleted a branch that contained a one-of-a-kind commit. Everything's gone forever, right?

Not quite.

Remember how `git reflog` tracks the history of where all your references have pointed? Now's the moment to use that feature.

**Follow these steps to retrieve lost data:**

1. Run `git reflog` to see the history of where your `HEAD` has been positioned. Locate the commit hash that matches the "drafts" modification you lost.
2. Recall the `git cat-file -p` command from the initial course? Pass it the abbreviated commit hash from the reflog output.
3. Extract the `tree` hash from that commit to locate the `blob` hash for the `notes.txt` document.
4. Run the command one final time to display the content of the removed document.
5. Rebuild the `notes.txt` document at the repository root with identical content.
6. Save that document with the commit message: `B: restoration`

Alternative to the above method:

**Merging Made Simple**

Working with Git's low-level commands is incredibly cumbersome. We had to manually copy values and execute the `cat-file` operation three separate times!

I wouldn't suggest using that approach in practice, but I wanted to emphasize that the underlying plumbing commands are always available when you need them.

Fortunately, there's a much simpler method. The `git merge` command accepts what's called a "commit-like reference":

`git merge <commit-like reference>`

A "commit-like reference" is anything that points to a commit (such as a branch name, tag, commit hash, or HEAD@{2})

Put another way, instead of this lengthy process:

```bash
git reflog
(locate the commit hash at HEAD@{2})
git cat-file -p <commit hash>
git cat-file -p <tree hash>
git cat-file -p <blob hash> > notes.txt
git add .
git commit -m "B: restoration"
```

We could have simply executed:

```bash
git merge HEAD@{2}
```

***

### **What "Commit-like References" Mean in Git**

In Git, a "commit-like reference" (or "commitish") refers to any expression that Git can interpret as pointing to a commit. Examples include:

* A full commit hash (e.g. `f8e9d2a`)
* A branch identifier (e.g. `develop`, `bugfix-auth`)
* A tag label (e.g. `v2.1`)
* A reflog pointer (e.g. `HEAD@{3}`)
* Relative position indicators (e.g. `HEAD^`, `develop~2`)

In other words, a "commit-like reference" isn't limited to the actual commit hash itself—it encompasses any notation that allows Git to locate a particular commit object, whether directly through its hash or indirectly through a named reference or relative position.

***

**Understanding Merge Conflicts**

Conflicts occur when two incompatible modifications lack a shared ancestor commit.

Fixing conflicts requires manual intervention. When they arise, Git flags the problematic files and prompts you to resolve them by editing the content directly. Here's what it looks like:

```bash
username,email,department,role
<<<<<<< HEAD
alice,wonderland@example.com,engineering,lead
=======
bob,builder@example.com,operations,manager
>>>>>>> develop
```

Your text editor may color-code these conflict markers for visibility, but fundamentally, you're just working with plain text. The upper portion, between `<<<<<<< HEAD` and `=======`, shows your current branch's version. The lower portion, between `=======` and `>>>>>>> develop`, displays the incoming branch's version. HEAD refers to the most recent commit on your active branch.

Single-file conflicts are fairly straightforward to handle. But what about scenarios with conflicts across multiple files?

**Handling Multiple Conflicts**

**Using Checkout for Conflict Resolution**

We've been manually editing files to fix conflicts, but Git actually provides built-in utilities to streamline this process.

The `git checkout` command can select specific versions during a conflict using the `--theirs` or `--ours` options:

* `--ours` replaces the file with the version from your current branch (the one you're merging into)
* `--theirs` replaces the file with the version from the incoming branch (the one being merged in)

**To clarify:**

```bash
git checkout --theirs path/to/file
```

If you want to keep your current branch's (develop's) version, use:

```bash
--ours
```

If you want to accept the incoming branch's (remove\_users) version, use:

```bash
--theirs
```

You can specify individual file paths for which version to keep.

**About Conflict Commit Messages**

You might have noticed that when resolving merge conflicts, you don't automatically get the typical merge commit message format like:

> Merge branch 'develop' into add\_products

Instead, you must manually write a message. Yes, more typing. This actually makes sense: Git can't know how you resolved the conflict, so it encourages you to document your resolution approach through a descriptive custom commit message.

***

### **Handling Rebase Conflicts**

Let's be honest: much of `rebase`'s negative reputation stems from conflicts! *Don't worry*. With a little practice, you'll handle them just fine.

Rebasing can feel intimidating because it modifies Git's history, meaning careless actions could result in irreversible data loss. However, once you grasp the underlying mechanics, you'll find it creates a cleaner, more comprehensible Git timeline for both you and your team.

Here's the typical real-world scenario:

1. You create a new branch, let's call it `update_styles`, branched from `develop`.
2. While you're working, a teammate merges *their* modifications into `develop`.
3. You finish your work, and it turns out you modified the same files (and specific lines) that your teammate changed.
4. You submit a Pull Request to merge (or rebase) `update_styles` into `develop`, and Git reports a conflict.
5. You address the conflict on your branch.
6. You finalize the Pull Request with the conflict now resolved.

***

**When you encounter a rebase conflict**

While you'll definitely see a conflict, you might notice something... *unexpected*. This time, the `HEAD` pointer contains `develop`'s modifications rather than `update_styles`'s! That's because `rebase` switches to the *target* branch—in this case `develop`—so it can replay the modifications from `update_styles` on top of it.

If you had merged `develop` instead of rebasing, `HEAD` would still reference `update_styles` because Git doesn't perform branch switching behind the scenes during a merge. With `rebase`, however, you need to think somewhat backwards... `--theirs` represents the `update_styles` branch which, paradoxically, contains what we typically consider "our" modifications.

Running `git branch` might show:

```bash
(no branch, rebasing update_styles) update_styles develop
```

During an active rebase, you're in a special mode called "detached HEAD." This temporary state lets you handle the conflict before proceeding with the rebase.

***

**Addressing the Conflict**

The same `git checkout --theirs` and `git checkout --ours` commands we used with `merge` work for resolving rebase conflicts.

You may also notice that your editor (depending on which one you use) provides UI elements to assist with conflict resolution. For instance, in VS Code you'll see options like:

"Accept incoming change" is equivalent to `git checkout --theirs`, and "Accept current change" is equivalent to `git checkout --ours`.

* During a merge, `--ours` means your active branch, but during a rebase, `--ours` means the branch you're rebasing *onto*. So, if you're rebasing onto develop while working from another branch, select `--ours` to preserve the "develop" branch's modifications.

Here's roughly how it should work when you have conflicting modifications to the same file across both branches (in this scenario "develop" and "update\_styles"):

**Example**

1. Execute the appropriate `git checkout` command to replace the `update_styles` branch's modifications with those from `develop`.
2. After editing the file, `add` it, but *don't commit* the modifications
   * With `rebase` conflicts, unlike merge conflicts, we don't `commit` to resolve the issue. Instead, we `--continue` the rebase.
3. Execute `git rebase --continue` to proceed with the rebase.
4. Run `git log` to view the updated commit timeline. Notice that the `J` commit (the one containing the modifications we discarded) has vanished... we'll explore why shortly.

**What just happened??? Why did our `J:` commit simply&#x20;*****disappear*****!?!**

Let's examine what occurred during our rebase conflict resolution:

1. We initiated a rebase of `update_styles` onto `develop`, which means we're rewriting the timeline of `update_styles` to incorporate all modifications from `develop`.
2. We essentially eliminated all modifications from the `J` commit by choosing to preserve the modifications from `develop` instead of `update_styles`.
3. We continued the rebase, and Git determined that the `J` commit served no purpose, and since we're already rewriting history, it simply removed it.

If our modifications had been more complex—say we had preserved *some* modifications from the `J` commit while overwriting others—then Git would have retained the `J` commit in the timeline.

***

### **Repeated Conflict Resolution Setup**

A frequent criticism of rebase is that you might need to manually fix identical conflicts repeatedly. This becomes particularly noticeable when working with a lengthy feature branch, or even more commonly, when managing several feature branches that are all being rebased onto `develop`.

**RERERE Comes to the Rescue**

The `git rerere` feature is somewhat obscure. The acronym stands for "reuse recorded resolution" and, true to its name, it enables Git to memorize how you've handled a specific conflict chunk so that when it encounters the identical conflict again, Git can automatically apply your previous solution.

Put simply, when activated, `rerere` remembers your conflict resolution approach (applicable to both rebasing and merging) and will automatically implement that same resolution upon encountering the identical conflict again. Quite handy, isn't it?

**Activating RERERE:**

```bash
git config --local rerere.enabled true
```

**Example scenario with 3 branches:**

* 2 branches containing modified files and 1 primary branch that we'll rebase onto.

**Task**

1. Activate the `rerere` feature by executing this command in your repository: `git config --local rerere.enabled true`
2. Switch to `preferences` and rebase it onto `develop`. You should observe something like:

```bash
user@laptop project % git rebase develop
Auto-merging clients/preferences.txt
CONFLICT (add/add): Merge conflict in clients/preferences.txt
error: could not apply f3e8a51... M
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Recorded preimage for 'clients/preferences.txt' # <--- NOTE THIS LINE
Could not apply f3e8a51... M
```

See how it's documenting our actions as we handle the conflict? This demonstrates `rerere` working!

3. Accept "both" modifications for the conflict, resulting in a file like:

```bash
# Top Clients
* Sarah Connor, Tech Industries
* John Connor, Future Systems, Engineer
```

4. Stage the changes and proceed with the rebase. Keep the commit message unchanged (`M: ...`). Note that Git might automatically launch your editor to modify the commit message—simply save and exit to continue. Upon completion you should see:

```bash
user@laptop project % git add .
user@laptop project % git rebase --continue
Recorded resolution for 'clients/preferences.txt'. # <--- NOTE THIS LINE
[detached HEAD a7d29f4] M
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/preferences.
```

Notice Git is storing how we handled the conflict!

5. Finally, switch to the `preferences2` branch and rebase it onto `develop`. It should automatically resolve the conflict and display a message like:

```bash
user@laptop project % git rebase develop
Auto-merging clients/preferences.txt
CONFLICT (add/add): Merge conflict in clients/preferences.txt
error: could not apply f3e8a51... M
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Resolved 'clients/preferences.txt' using previous resolution. # <--- NOTE THIS LINE
Could not apply f3e8a51... M
```

Simply stage the changes and continue the rebase, once more keeping the commit message unchanged (`M: ...`).

***

### Accidental **Commit**

Remember how with `merge` conflicts you commit the fix, but with `rebase` conflicts you use `--continue` to proceed?

For the cases where you've mistakenly committed when resolving a rebase conflict instead of continuing...

* **How to Fix the Mistake**

If you accidentally commit while resolving a rebase conflict, simply run:

`git reset --soft HEAD~1`

The `--soft` option preserves your modifications while reversing the commit. Afterward, you can proceed with `--continue` for the rebase as intended.

***

### Git Squashing

Every dev team will have different standards and opinions on how to use Git. Some teams require all pull requests to contain a single commit, while others prefer to see a series of small, focused commits.

If you join a team that prefers a single commit, you will need to know how to "squash" your commits together. To be fair, even if you don't *need* a single commit, squashing is useful to keep your commit history clean.

#### **What Is Squashing?**

It's exactly what it sounds like. We take all the changes from a series of commits and squash them into a single commit.

<figure><img src="https://2332658533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5fhKjYnbaQlTPTcaO85%2Fuploads%2FJEeXFcfFKFNSz8NaDSkR%2FEeSnkk5-1280x629.png?alt=media&#x26;token=c41ec573-aed4-42f4-bad4-da749bf20130" alt=""><figcaption></figcaption></figure>

#### **How to Squash**

Perhaps confusingly, squashing is done with the `git rebase` command! Here are the steps to squash the last `n` commits:

1. Start an interactive rebase with the command `git rebase -i HEAD~n`, where `n` is the number of commits you want to squash.
2. Git will open your default editor with a list of commits. Change the word `pick` to `squash` for all but the first commit.
3. Save and close the editor.

The `-i` flag stands for "interactive," and it allows us to edit the commit history before Git applies the changes. `HEAD~n` is how we reference the last `n` commits. `HEAD` points to the current commit (as long as we're in a clean state) and `~n` means "n commits before HEAD."

#### **Why Does Rebase Squash?**

Remember, `rebase` is all about replaying changes. When we rebase onto a specific *commit* (`HEAD~n`), we're telling Git to replay all the changes from the *current* branch on top of that commit. Then we use the interactive flag to "squash" those changes so that Rebase will apply them all in a single commit.

***

### **Force Push**

So we did some naughty stuff. We squashed `main` which means that because our *remote* `main` branch on GitHub has commits that we removed, `git` won't allow us to push our changes because they're totally out of sync.

The [`git push`](https://git-scm.com/docs/git-push) command has a `--force` flag that allows us to overwrite the remote branch with our local branch. It's a very dangerous (but useful) flag that overrides the safeguards and just says "make that branch the same as this branch."

`git push origin main --force`

***

#### **Squashing PRs**

So we did a weird thing (squash commits on `main`), now let's do the common thing: squash all the commits on a feature branch for a pull request. If your team prefers single-commit-pull-requests, this will likely be your workflow:

1. Create a new branch off of `main`.
2. Go about your work on the feature branch making commits as you go.
3. When you're ready to get your code into `main`, squash all your commits into a single commit.
4. Push your branch to the remote repository.
5. Open a pull request from the feature branch into `main`.
6. Merge the pull request once it's approved.

***

### **Git Stash**

The [`git stash` command](https://git-scm.com/docs/git-stash) records the current state of your working directory *and the index* (staging area). It's kinda like your computer's copy/paste clipboard. It records those changes in a safe place and reverts the working directory to match the `HEAD` commit (the last commit on your current branch).

To stash your current changes and revert the working directory to match `HEAD`:

```bash
git stash
```

To list your stashes:

```bash
git stash list
```

#### **Pop**

Stash has [a few options](https://git-scm.com/docs/git-stash), but the ones that you will use most are:

```bash
git stash
git stash pop
git stash list
```

The `pop` command will (by default) apply your *most recent* stash entry to your working directory and remove it from the stash list. It effectively undoes the `git stash` command. It gets you back to where you were.

**What is the Stash?**

The `git stash` command stores your changes in a [stack (LIFO) data structure](https://www.youtube.com/watch?v=SD45xbKReT4). That means that when you retrieve your changes from the stash, you'll always get the most recent changes first.

```bash
# 'git stash' pushes a change onto the stash
git stash
```

```bash
# 'git stash pop' pops a change off the stash
# and applies it to the working directory
git stash pop
```

<figure><img src="https://2332658533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5fhKjYnbaQlTPTcaO85%2Fuploads%2FbKpgdQox4PMoeVUowpkD%2FFbkjBrY-1280x511.png?alt=media&#x26;token=45e4c1a1-1c73-46f9-a586-abc48c3f77d6" alt=""><figcaption></figcaption></figure>

A "stash" is a collection of changes that you've not yet committed. They might just be "raw" working directory changes, or they might be *staged* changes. **Both can be stashed.** So, for example, you can:

1. Make some changes to your working directory
2. Stage those changes
3. Make some more changes without staging them
4. Stash all of that

When you do, the "stash entry" will contain both the staged and unstaged changes and both your working directory and index will be reverted to the state of the last commit. It's a very convenient way to "pause" your work and come back to it later.

#### **Multiple Stashes**

You can stash changes with a message. I rarely use a stash message personally because I rarely have more than one stash entry. I imagine if you keep a crazy deep stash then messages are useful, but I find that I usually stash, and then just a few minutes or hours later, I pop it back out. That said, here's the syntax to provide a message:

```bash
git stash -m "jdsl v0 almost working"
```

#### Additional stashing techniques

**Apply Without Removing from Stash** This will apply the most recent stash changes, just like `pop`, but it will keep the stash in the stash list.

```bash
git stash apply
```

**Remove a Stash Without Applying** This will remove the most recent stash from the stash list without applying it to your working directory.

```bash
git stash drop
```

**Reference a Specific Stash** Most stash commands allow you to reference a specific stash by its index.

```bash
# Apply the third (0, 1, 2) most recent stash
git stash apply stash@{2}

# Remove the third most recent stash
git stash drop stash@{2}
```

***

***

### **Git Revert**

Where [`git reset`](https://git-scm.com/docs/git-reset) is a sledgehammer, [`git revert`](https://git-scm.com/docs/git-revert) is a scalpel.

A revert is effectively an *anti* commit. It does not *remove* the commit (like `reset`), but instead creates a new commit that does the exact opposite of the commit being reverted. It undoes the change but keeps a full history of the change and its undoing.

* **Using Revert**

  To revert a commit, you need to know the commit hash of the commit you want to revert. You can find this hash using `git log`.

  `git log`

  Once you have the hash, you can revert the commit using `git revert`.

  `git revert <commit-hash>`

***

### **Git Diff**

The [`git diff` command](https://git-scm.com/docs/git-diff) shows you the differences between... stuff. Differences between commits, the working tree, etc.

I frequently use it to look at the changes between the current state of my code and the last commit. For example:

```bash
# show the changes between the working tree and the last commit
git diff

# show the differences between the previous commit and the current state, including the last commit and uncommitted changes
git diff HEAD~1

# show the change between two commits
git diff COMMIT_HASH_1 COMMIT_HASH_2
```

***

### **Revert vs. Reset**

* `git reset --soft`: Undo *commits* but keep changes staged
* `git reset --hard`: Undo *commits* and discard changes
* `git revert`: Create a *new commit* that undoes a previous commit
* **When to Reset**

  If you're working on your own branch, and you're just undoing something you've already committed, say you're cleaning everything up so you can open a pull request, then `git reset` is probably what you want.
* **When to Revert**

  However, if you want to undo a change that's already on a shared branch (especially if it's an older change), then `git revert` is the safer option. It won't rewrite any history, and therefore won't step on your coworkers' toes.

***

### Cherrypick

There comes a time in every developer's life when you want to yoink a commit from a branch, but you don't want to merge or rebase because you don't want *all* the commits.

The [`git cherry-pick` command](https://git-scm.com/docs/git-cherry-pick) solves this.

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

#### **How to Cherry Pick**

1. First, you need a clean working tree (no uncommitted changes).
2. Identify the commit you want to cherry-pick, typically by `git log`ing the branch it's on.
3. Run:

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

***

### **Bisect**

So we know how to fix problems in our code. We can either:

1. Revert the commit with the bug (this is more common on large teams)
2. "Fail forward" by just writing a new commit that fixes the bug (this is more common on small teams)

But there's another question:

> How do we find out when a bug was introduced?

That's where the [`git bisect` command](https://git-scm.com/docs/git-bisect) comes in. Instead of manually checking *all* the commits (`O(n)` for Big O nerds), `git bisect` allows us to do a binary search (`O(log n)` for Big O nerds) to find the commit that introduced the bug.

For example, if you have 100 commits that *might* contain the bug, with `git bisect` you only need to check 7 commits to find the one that introduced the bug.

| commits to check | max checks to find   |
| ---------------- | -------------------- |
| 1                | 1                    |
| ---------------- | -------------------- |
| 2                | 1                    |
| ---------------- | -------------------- |
| 10               | 4                    |
| ---------------- | -------------------- |
| 100              | 7                    |
| ---------------- | -------------------- |
| 1000             | 10                   |
| ---------------- | -------------------- |
| 10000            | 14                   |

`git bisect` isn't **just** for bugs, it can be used to find the commit that introduced **any** change, but issues like bugs and performance regressions are a common use case.

***

#### **How to Bisect**

There are effectively 7 steps to bisecting:

1. Start the bisect with `git bisect start`
2. Select a "good" commit with `git bisect good <commitish>` (a commit where you're sure the bug wasn't present)
3. Select a bad commit via `git bisect bad <commitish>` (a commit where you're sure the bug was present)
4. Git will checkout a commit between the good and bad commits for you to test to see if the bug is present
5. Execute `git bisect good` or `git bisect bad` to say the current commit is good or bad
6. Loop back to step 4 (until `git bisect` completes)
7. Exit the bisect mode with `git bisect reset`

If you're interested, the [`git blame`](https://git-scm.com/docs/git-blame) command can be used to see who made the change, not just **when** it was made.

From `man git-bisect`:

```bash
   Bisect run
       If you have a script that can tell if the current source code is good or bad, you can
       bisect by issuing the command:

           $ git bisect run my_script arguments

       Note that the script (my_script in the above example) should exit with code 0 if the
       current source code is good/old, and exit with a code between 1 and 127 (inclusive),
       except 125, if the current source code is bad/new.
```

***

### **Worktrees**

We've been saying "worktree" all throughout this course but I've been misusing it... I am sorry `:(`

I've been saying "worktree" when I meant "main worktree", which is more precise because you can have more than one working tree.

* **What Is a Worktree?**

  A worktree (or "working tree" or "working directory") is just the directory on your filesystem where the code you're tracking with Git lives. Usually, it's just the root of your Git repo (where the `.git` directory is). It contains:

  * Tracked files (files that Git knows about)
  * Untracked files (files that Git doesn't know about)
  * Modified files (files that Git knows about that have been changed since the last commit)
* **The Worktree Command**

  Git has the [`git worktree` command](https://git-scm.com/docs/git-worktree) that allows us to work with worktrees. The first subcommand we'll worry about is simple:

  `git worktree list`

  It lists all the worktrees you created.

List all your worktrees.

**Copy/paste the output into the textbox and submit.**

#### **Linked Worktrees**

We've talked about:

1. Stash (temporary storage for changes)
2. Branches (parallel lines of development)
3. Clone (copying an entire repo)

Worktrees accomplish a similar goal (allow you to work on different changes without losing work), but are particularly useful when:

1. You want to switch back and forth between the two change sets without having to run a bunch of `git` commands (not branches or stash)
2. You want to keep a light footprint on your machine that's still connected to the main repo (not clone)

**The Main Worktree**

* Contains the `.git` directory with the entire state of the repo
* Heavy (lots of data in there!). To get a new main working tree requires a `git clone` or `git init`

**A Linked Worktree**

* Contains a `.git` *file* with a *path* to the main working tree
* Light (essentially no data in there!), about as light as a branch
* Can be complicated to work with when it comes to env files and secrets

**Create a Linked Worktree**

To [create a new worktree](https://git-scm.com/docs/git-worktree#Documentation/git-worktree.txt-addltpathgtltcommit-ishgt) at a given path:

```bash
git worktree add <path> [<branch>]
```

Adding `<branch>` is optional. It will use the last part of the path as the branch name.

1. Create a linked worktree as a *sister* directory to your main working tree called `ultracorp`. Use the default `ultracorp` branch.
2. `cat` the contents of the linked worktree's `.git` file: notice that it's just a path to the main working tree!
3. List all the worktrees from the root of the main working tree to make sure you see the new `ultracorp` worktree.

**Delete Worktrees**

You may never need to stash again! Okay, stash is still useful for tiny stuff, but worktrees are so much better for long-lived changes.

However, at some point you will need to clean up your worktrees. The simplest way is the [`remove` subcommand](https://git-scm.com/docs/git-worktree#Documentation/git-worktree.txt-remove):

```bash
git worktree remove WORKTREE_NAME
```

An alternative is to delete the directory manually, then [`prune`](https://git-scm.com/docs/git-worktree#Documentation/git-worktree.txt-prune) all the worktrees (removing the references to deleted directories):

```bash
git worktree prune
```

***

### **Tags**

We're going to wrap this course up on an easy one: [tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging).

A tag is a name linked to a commit that doesn't move between commits, unlike a branch. Tags can be created and deleted, but not modified.

* **How to Tag**

  To list all current tags:

  `git tag`

  To create a tag on the current commit:

  `git tag -a "tag name" -m "tag message"`

MegaCorp™ is getting ready to release a new version of their enterprise software!

1. Create a [tag](https://git-scm.com/docs/git-tag) on the latest commit (P) with the name `candidate` and a descriptive message.
2. List the tags to verify it was created.
3. Use `git log` to see how tags show up in the logs.

***

### **Semver**

It's kinda weird to just name tags any old thing. We're developers, we like structure, sameness, and sometimes even bike-shedding.

["Semver"](https://semver.org/), or "Semantic Versioning", is a naming convention for versioning software. You've probably seen it around, it looks like this:

<figure><img src="https://2332658533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5fhKjYnbaQlTPTcaO85%2Fuploads%2FBQk5TaUKrzx3N1tmrPJB%2Fl9sosco-1280x591.png?alt=media&#x26;token=011f8402-773e-458f-a27c-0b4da7383359" alt=""><figcaption></figcaption></figure>

The "v" isn't technically part of "semver", but it's often there to say "this is a version".

It has two primary purposes:

1. To give us a standard convention for versioning software
2. To help us understand the impact of a version change and if it's safe (how hard it will be) to upgrade to

Each part is a number that starts at 0 and increments upward forever. The rules are simple:

* MAJOR increments when we make "breaking" changes (this is typically a big release, for example, Python 2 -> Python 3)
* MINOR increments when we add new features in a backward-compatible manner
* PATCH increments when we make backward-compatible bug fixes

To sort them from highest to lowest, you first compare the major versions, then the minor versions, and finally the patch versions. For example, a major version of `2` is always greater than a major version of `1`, regardless of the minor and patch versions.

*As a special case, major version `0` is typically considered to be pre-release software and thus the rules are more relaxed.*

#### **Conventional Tags**

Tags are used for all sorts of reasons, but sometimes they're used to denote releases. In that case, tags that follow [semver](https://semver.org/) are common.

To [tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging):

```bash
git tag -a v3.10.2 -m "Fixed a lil bug"
```

Pretty much anywhere you can use a commit hash, you can use a tag name when working with the `git` CLI. It's a ["commitish"](https://git-scm.com/docs/gitglossary.html#Documentation/gitglossary.txt-aiddefcommit-ishacommit-ishalsocommittish).

*Optionally, you can push your tags up to your remote GitHub repo with `git push origin --tags`.*

***
