Git journal (Yet, another cheatsheet)
Tags: git, github, webdev
Published: 2021-08-14 17:29:19

Since my early days with Git (2015?), I carried with me a private journal where I'd take note of the most unique git commands I had to use at some point.

Today I'm making this journal public (with some re-touches), hoping it helps someone else. 🤞

**Disclaimer:** If you are not familiar, here's a friendly [video intro to Git](https://www.youtube.com/watch?v=USjZcfj8yxE&t=205s&ab_channel=ColtSteele).
However, most of the flows in this journal are not quite "beginner"...

Nevertheless, feel free to ask questions, suggest different approaches, or simply tell me which command helped you! :)

---

## Entries
  * [1. Git configuration](#1-git-configuration)
  * [2.1. Change commits author/email](#21-change-commits-authoremail)
  * [2.2. Change commits author/email - chapter 2](#22-change-commits-authoremail-chapter-2)
  * [3. Cleanup local branches](#3-cleanup-local-branches)
  * [4. Pretty Commit logs](#4-pretty-commit-logs)
  * [5. Renaming case sensitive files](#5-renaming-case-sensitive-files)
  * [6. Override a file with another branch](#6-override-a-file-with-another-branch)
  * [7. Override a branch with another branch](#7-override-a-branch-with-another-branch)
  * [8. Quicker amend](#8-quicker-amend)
  * [9. Checkout a PR from a forked branch](#9-checkout-a-pr-from-a-forked-branch)
  * [10. Convert a PR branch with merge commits to a clean rebased git history](#10-convert-a-pr-branch-with-merge-commits-to-a-clean-rebased-git-history)
  * [11. Different git configs based on path location](#11-different-git-configs-based-on-path-location)
  * [12. Restart SSH Agent](#12-restart-ssh-agent)
  * [13. Include another repo into your local repo](#13-include-another-repo-into-your-local-repo)

---

### 1. Git configuration

**Problem:** I need to use different git accounts for different projects. To ensure the correct account is being used, you need to setup it:

```bash
# Setting the account globally for all the projects...
git config --global user.name "your-username"
git config --global user.email "name@email.com"

# ... or just to a specific project
# (you need to be in the git project folder)
git config --local user.name "your-username"
git config --local user.email "name@email.com"
```

### 2.1. Change commits author/email
**Problem:** I mistakenly used the wrong e-mail on my last commit. I need to change it to a different author

```bash
git commit --amend --reset-author 
```

### 2.2. Change commits author/email - chapter 2
**Problem:** I realized I had multiple commits with the wrong e-mail. This magical command made the trick:

```bash
  git filter-branch --commit-filter 'if [ "$GIT_AUTHOR_EMAIL" = "old-author@email.com" ];
    then 
        export GIT_COMMITTER_NAME="New Author Name";
        export GIT_COMMITTER_EMAIL=new-author@email.com;
        export GIT_AUTHOR_NAME="New Author Name";
        export GIT_AUTHOR_EMAIL=new-author@email.com;
    fi; git commit-tree "$@"'
```

### 3. Cleanup local branches
**Problem:** When doing `git branch`, I had a lot of local old branches. I wanted to delete them all.

```bash
# delete all branches expect master
git branch | grep -v "master" | xargs git branch -D
```

or

```bash
# Delete all merged branches except the current one
git branch --merged | grep -v '*' | xargs git branch -D
```

### 4. Pretty Commit logs

**Problem:** The output of `git log` is ugly and unreadable. I want something more friendly.

```bash
# Add this to your global .gitconfig
[alias]
lg = log --graph --pretty='format:%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
```

Now I only use `git lg`. The logs are much readable, specially to understand the git history and merged branches.

![screenshot comparing git log with git lg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qb0164eu2kyym6zb2tbo.png)

### 5. Renaming case sensitive files
**Problem:** On Mac, I needed to rename a file already pushed,  eg `Hello.js` -> `hello.js`. Doing a traditional manual rename does not work on MacOS. Solution:

```bash
git mv -f path/to/Hello.js path/to/hello.js
```

### 6. Override a file with another branch
**Problem:** I've changed a file by mistake (it was the devil `package-lock.json`) and wanted to revert it and be the same as in the `master` branch.

```bash
git checkout master package-lock.json
```

The file is now how it was, then you just need to commit it again.


### 7. Override a branch with another branch
**Problem:** I needed to make branch B look like branch A.

```bash
# push branch-a code into branch-b (forced)
git push origin branch-a:branch-b -f
```

_Note in 2021:_ I don't remember why I did this. Today I do this in a different way: 

````bash
# checkout branch-a
git checkout branch-a
# push its code to branch-b (forced)
git push origin branch-b -f
```

### 8. Quicker amend
**Problem:** I do amend my commits, very often. I wanted a faster way to do it.

```bash
# your global .gitconfig
[alias]
sneak = commit --amend --no-edit
```

```bash
# amend a commit right away without being prompt to edit the commit message.
git sneak
```

P.S. I remember stealing this from a Tweet, but can't remember who. :/

### 9. Checkout a PR from a forked branch
**Problem:** I needed to checkout someone's else PR that was created from a forked repo. My local repo was a fork too.

- 1. Add the original repo to your fork .git/.gitconfig file
```bash
[remote "upstream"]
    url = https://github.com/author/original-repo-name.git
    fetch = +refs/heads/*:refs/remotes/upstream/*
```

- 2. Add this 3 magic alias to your global .gitconfig.

````bash
[alias]
# Checkout a PR
pr = "!f() { git fetch -fu ${2:-upstream} refs/pull/$1/head:pr/$1 && git checkout pr/$1; }; f"
pr-clean = "!git checkout master ; git for-each-ref refs/heads/pr/* --format=\"%(refname)\" | while read ref ; do branch=${ref#refs/heads/} ; git branch -D $branch ; done && git branch -D pr"
pr-id = "!f() { git fetch upstream pull/\"$1\"/head:pr && git checkout pr; }; f"
```

- 3. Use this git alias to checkout new PR by id.
```
git pr-id 781 # 781 is the PR id.

# to pull the new commits, run the following:
git pr-clean && git pr-id 781
```

### 10. Convert a PR branch with merge commits to a clean rebased git history
**Problem:** I had a PR branch full of commits and merge commits. I wanted to merge it without squashing the commits, but keeping a clean git history. (without the merge commits).

Flow summary: Create a new branch that contains the diffs. Use that branch to reset my PR branch, rewriting the commit history.

```bash
# - 0. Safety guard: Create a backup of your feature branch just in case things go wrong.
git checkout -b your-branch--backup


# - 1. Get the diffs between your `feature-branch` and `master`.
## 1.1 go to master and pull the latest version 
git checkout master && git fetch

## 1.2 get the diffs between master and your branch
git merge --squash origin/your-branch


# The files were modified to look like `your-branch`.
# verify it by yourself in your code editor...

# - 2. Create a new branch with these diffs
git checkout -b your-branch--clean

# - 3. Commit the changes to the new branch.
#      You can do it in a single commit or multiple commits.
## This commits all the changes at once
git add . && git commit -m "The new code"

# - 4. Rewrite `your-branch` to match the `your-branch--clean`,
    # cleaning all the messy git history
## 4.1 go to your original branch...
git checkout your-branch

## 4.2 ... and rewrite it to be exactly like the cleaned branch
git reset --hard origin/your-branch--clean


# - 5. That's it! Everything should work as before,
    # but now with a clean commit history.
    # Give it a final check. If all good, push it!
gift push -f
```

Go to your PR again. It should look exactly the same:
- Same nº of files changed
- Same nº of diffs added/removed.
But now the commits history is clean now! ✨

P.S. If something went wrong, use the backup branch created at the beginning to start over.

### 11. Different git configs based on path location
**Problem:** I had different git configs for multiple projects - for example, different pull config and different users. I needed a better solution than creating multiple local configs per project.

Reference: [Conditional git configs](https://www.motowilliams.com/conditional-includes-for-git-config)

- 1. Create a gift config per configuration:
```bash
.gitconfig
.gitconfig-configA
.gitconfig-configB
```

```bash
# .gitconfig

[includeIf "gitdir:~/Documents/git/folderA/"]
  path = .gitconfig-configA
[includeIf "gitdir:~/Documents/git/folderB/"]
  path = .gitconfig-configB

[core]
   excludesfile = /Users/your-pc-name/.gitignore
	
[alias]
lg = log --....
# other alias common to everyone...
```

```bash
# .gitconfig-configA.
# project under folderA use userA and pull ff

[user]
  name = Your Name A
  email = your-name-a@email.com

[pull]
  ff = only
```

```bash
# .gitconfig-configB
# projects under folderB use userB and pull rebase

[user]
  name = Your Name B
  email = your-name-b@email.com

[pull]
  rebase = true
```

### 12. Restart SSH Agent
**Problem:** My MacOS (Catalina 10.15.7) does not load correctly all my SSH keys (from different accounts), preventing me from pulling repos with SSH.

Imagine I have a SSH key `git@github.com-eagle` configured.

```bash
# The eagle SSH is list properly in pub key...
cat ~/.ssh/*.pub

# But does not show up in the SSH list...
ssh-add -L
```

The solution is to restart SSH agent to make it work again.

```bash
eval "$(ssh-agent -s)"

# it will output a number (eg 00123). Use it in the next command

kill -9 00123

# finally, verify the SSH connection again.
ssh git@github.com-eagle

# Output: "Hi eagle! You've successfully authenticated"
```

Reference: [Stackoverflow - how to restart ssh-agent](https://unix.stackexchange.com/questions/518568/how-to-restart-ssh-agent-without-relogin/520343#520343)

### 13. Include another repo into your local repo
**Problem:** As part of the hiring process (me as the interviewer), I review multiple code exercises submitted through a repo. These repos are a copy of a [template repo](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/creating-a-template-repository).

To review each exercise repo, instead of cloning them, over and over again, I add them locally to the base template repo. 

```bash
# You can give any unique name to the remote repo. eg "bunny"

# add the repo and checkout its main branch
git remote add bunny https://github.com/author/reponame.git
git fetch bunny
git checkout bunny main
```

The advantages of `remote add` over `clone`:
- Cleaner: Keep my computer cleaner without all the similar folders.
- Faster: No need to `npm i` each repo because the dependencies are already there (based on the main branch)
- Easy to compare: It's easier to compare the exercise to the expect result with some other git commands because it's the same codebase.

---

That's all for now! (2021 August 14th)
Next time I face a problem, I'll update this journal with a new entry :)