# Git

## Glossary

**GIT**: an open source, distributed version-control system  
**GITHUB**: a platform for hosting and collaborating on Git repositories

**TREE**: directory that maps names to blobs or trees  
**BLOB**: any file  
**COMMIT**: snapshot of the entire repository + metadata, identified it's SHA-1 hash

**HEAD**: represents the current working directory, the HEAD pointer can be moved to different branches, tags, or commits when using git checkout  
**BRANCH**: a lightweight movable pointer to a commit  
**CLONE**: a local version of a repository, including all commits and branches  
**REMOTE**: a common repository on GitHub that all team member use to exchange their changes

**FORK**: a copy of a repository on GitHub owned by a different user  
**PULL REQUEST**: a place to compare and discuss the differences introduced on a branch with reviews, comments, integrated tests, and more  

**REPOSITORY**: collection of files and folder of a project aka repo  
**STAGING AREA/STASH**: area of temporary snapshots (not yet commits)

## Data Model

Data Model Structure:

```txt
<root> (tree)
|
|_ foo (tree)
|  |_ bar.txt (blob, contents = "hello world")
|
|_ baz.txt (blob, contents = "git is wonderful")
```

Data Model as pseudocode:

```py
# a file is a bunch of bytes
blob = array<byte>

# a directory contains named files and directories
tree = map<string, tree | file>

# a commit has parents, metadata, and the top-level tree
commit = struct {
    parent: array<commit>
    author: string
    message: string
    snapshot: tree
}

# an object is either a blob, tree or commit
object = map<string, blob | tree | commit>

# commit identified by it's hash (unmutable)
def store(object):
    id = sha1(object)  # hash repo
    objects<id> = object  # store repo w/ index hash

# load the commit
def load(id):
    return objects<id>

# human-readable names for SHA-1 hashes (mutable)
references = map<string, string>

# bind a reference to a hash
def update_reference(name, id):
    references<name> = id

def read_reference(name):
    return references<name>

def load_reference(name_or_id):
    if name_or_id in references:
        return load(references<name_or_id>)
    else:
        return load(name_or_id)
```

## Commands

`git <command> -h`: get help for a git command  
`git <command> --help`: get man page for a git command  
`git -C <path> <command>`: execute a git commend in the specified path  
`git <command> <commit>^`: operate on commit *before* the provided commit hash  

### Create Repository

`git init [<project_name>]`: initialize a brand new Git repository and begins tracking  
`.gitignore`: specify intentionally untracked files to ignore  

### Config

`git config --global user.name "<name>"`: set name attached to commits  
`git config --global user.email "<email address>"`: set email attached to commits

### Making Changes

`git status`: shows the status of changes as untracked, modified, or staged  
`git add <filename1 filename2 ...>`: add files to the staging area  
`git add -p <files>`: interactively stage chunks of a file  

`git blame <file>`: show who last edited which line  

`git commit`: save the snapshot to the project history  
`git commit -m "message"`: commit and provide a message  
`git commit -a`: automatically notice any modified (but not new) files and commit  
`git commit -v|--verbose`: show unified diff between the HEAD commit and what would be committed  
`git commit --amend`: modify latest commit with the new changes  
`git commit --no-verify`: commit without executing hooks  
`git commit --fixup <commit>`: mark commit as correction to another  

`git diff <filename>`: show difference since the last commit  
`git diff <commit> <filename>`: show differences in a file since a particular snapshot  
`git diff <reference_1> <reference_2> <filename>`: show differences in a file between two snapshots  
`git diff  --cached`: show what is about to be committed  
`git diff <first-branch>...<second-branch>`: show content diff between two branches  
`git diff -w`: show diff ignoring whitespace differences  

`git bisect`: binary search history (e.g. for regressions)  

### Stashes

`git stash [push -m|--message]`: add all changes to the stash (and provide message)  
`git stash list` list all stashes  
`git stash show [<stash>]`: show changes in the stash  
`git stash pop`: restore last stash  
`git stash drop [<stash>]`: remove a stash from the list  
`git stash clear`: remove all stashes  

### Remotes

`git remote`: list remotes  
`git remote -v`: list remotes names and URLs  
`git remote show <remote>`: inspect the remote  

`git remote add <remote> <url | path>`: add a remote  
`git remote remove <remote>`: remove the specified remote  
`git remote rename <old_name> <new_name>`: rename a remote  
`git branch --set-upstream-to=<remote>/<remote branch>`: set up correspondence between local and remote branch  

`git push`: send objects to default remote on current branch  
`git push <remote> <branch>`: send objects to remote  
`git push <remote> <local branch>:<remote branch>`: send objects to remote, and update remote reference  

`git push --force`: overwrite remote with local version  
`git push --force-with-lease`: overwrite remote with local version if remote has not been modified  
`git push --force-with-lease --force-if--includes`: will verify if updates from the remote that may have been implicitly updated in the background are integrated locally before allowing a forced update  

`git fetch [<remote>]`: retrieve objects/references from a remote  
`git pull`: update the local branch with updates from its remote counterpart, same as `git fetch; git merge`  
`git pull --ff`: when possible resolve the merge as a fast-forward (only update branch pointer, don't create merge commit). Otherwise create a merge commit.  

`git fetch|pull --prune`: remove any remote-tracking references that no longer exist on the remote  
`git fetch|pull --tags`: fetch all tags from the remote

`git clone <url> [<folder_name>]`: download repository and repo history from remote  
`git clone --shallow`: clone only repo files, not history of commits  

> **Note**: for a in depth explanation of `--force-if-includes` see [this][force-if-includes]

### Viewing Project History

`git log`: show history of changes  
`git log -p`: show history of changes and complete differences  
`git log --stat --summary`: show overview of the change  
`git log --follow <file>`: list version history fo file, including renames  
`git log --all --graph --decorate`: visualizes history as a DAG  
`git log  --oneline`: compact log  
`git log <rev>`: Include commits that are reachable from `<rev>`  
`git log ^<rev>`: Exclude commits that are reachable from `<rev>`  
`git log <rev1>..<rev2>`: Include commits that are reachable from `<rev2>` but exclude those that are reachable from `<rev1>`. 
When either `<rev1>` or `<rev2>` is omitted, it defaults to HEAD.  
`git log <rev1>...<rev2>`: Include commits that are reachable from either `<rev1>` or `<rev2>` but exclude those that are reachable from both. When either `<rev1>` or `<rev2>` is omitted, it defaults to HEAD.  
`git log <rev>^@`: Include anything reachable from `<rev>` parents but not the commit itself

`git shortlog`: list commits by author  
`git reflog`: show record of when the tips of branches and other references were updated in the local repository  

`git show <commit>`: output metadata and content changes of commit  
`git cat-file -p <commit>`: output commit metadata  

### Tag

Git supports two types of tags: *lightweight* and *annotated*.  

A lightweight tag is very much like a branch that doesn't change—it's just a pointer to a specific commit.  

Annotated tags, however, are stored as full objects in the Git database.  
They're checksummed;contain the tagger name, email, and date; have a tagging message; and can be signed and verified with GNU Privacy Guard (GPG).  
It's generally recommended creating annotated tags so it's possible to have all this information.  

`git tag`: list existing tags  
`git tag -l|--list <pattern>`: list existing tags matching a wildcard or pattern  

`git tag <tag> [<commit_hash>]`: create a *lightweight* tag on the commit  
`git tag -a <tag> [<commit_hash> -m <message>]`: create am *annotated* tag on the commit  

`git push <remote> <tagname>`: push a tag to the remote  
`git push <remote> --tags`: push commits and their tags (both types) to the remote  

`git tag -d <tagname>`: delete a tag  
`git push <remote> :refs/tags<tagname>:`: remove a tag from the remote  
`git push <remote> --delete <tagname>`: remove a tag from the remote  

`git checkout <tag>`: checkout a tag - **WARNING**: will go into *detached HEAD*  

### Branching And Merging

![branch](../img/git_branches.png "how branches work")

`git branch`: shows branches  
`git branch -vv`: show branch + last commit + remote status  
`git branch --merged [--remote]`: show branches (remote) that have been merged into current one (needs same history, merge squash/rebase break history)

`git branch <branch-name>`: create new branch  
`git checkout -b <branch-name>`: create a branch and switches to it, same as `git branch <name>; git checkout <name>`  
`git branch`: show list of all existing branches  (* indicates current)  
`git checkout <branch-name>`: change current branch (update HEAD) and update working directory  
`git branch -d <branch-name>`: delete specified branch  
`git branch -m <old_name> <new_name>`: rename a branch without affecting the branch's history  

`git merge <branch-name>`: merges into current branch  
`git merge --continue`: continue previous merge after solving a merge conflict  
`git mergetool`: use a fancy tool to help resolve merge conflicts  
`git rebase <branch>`: rebase current branch commits onto another branch  

`git cherry-pick <commit>`: bring in a commit from another branch  
`git cherry-pick <commit>^..<commit>`: bring in a  range of commits from another branch (first included)  
`git cherry-pick <commit>..<commit>`: bring in a  range of commits from another branch (first excluded)  

### Undo & Rewriting History

`git rm -r --cached <file>`: remove a file from being tracked  
`git commit --amend`: replace last commit by creating a new one (can add files or rewrite commit message)  
`git commit --amend -m "amended message"`: replace last commit by creating a new one (can add files or rewrite commit message)  
`git commit --amend  --no-edit`: replace last commit by creating a new one (can add files or rewrite commit message)  

`git reset HEAD <file>`: unstage a file  
`git reset <commit>`: undo all commits after specified commit, preserving changes locally  
`git reset --soft <commit>`: revert to specific commit but keep changes and staged files  
`git reset --hard <commit>`: discard all history and changes back to specified commit  

`git clean`: remove untracked files form the working tree  
`git clean -d`: recurse into untracked directories while cleaning  
`git clean --interactive`: clean files interactively

`git checkout <file>`: discard changes  
`git checkout -- <file>`: discard changes, no output to screen  
`git restore .`: discard uncommitted changes  
`git restore <file>`: discard uncommitted changes to file  
`git restore --source <commit> <file>`: revert file to commit version  
`git restore <deleted-file>`: recover deleted file if previously committed  

`git rebase -i`: modify (reword, edit, drop, squash, merge, ...) current branch commits  
`git rebase -i HEAD~<n>`: modify (reword, edit, drop, squash, merge, ...) *n* commits  
`git rebase -i <commit>`: modify (reword, edit, drop, squash, merge, ...) *from* commit to latest  
`git rebase --autostash`: automatically create a temporary stash entry before rebasing  
`git rebase --autosquash`: automatically apply "squash!" or "fixup!" or "amend!" commits  

> **WARN**: Changing history can have nasty side effects

<!-- links -->
[force-if-includes]: https://stackoverflow.com/a/65839129 "git --force-if-includes explanation"
