Title: Git Cheatsheet
Slug: git-cheatsheet
Author: Kartones
Lang: en
Description: A cheatsheet of Git commands and notes covering everyday and advanced usage.


## Commands

Notes:

* `origin` is the default remote, you can always specify another remote name
* `^`: "parent of", e.g. `git show HEAD^` means "show me the parent commit of `HEAD`". If followed by a number, is the n-parent, so useful for merge commits only
* `~`: "first parent of", e.g. `git show HEAD~3` means "show me the parent of the parent of the parent of `HEAD`"
* `{x}`: History position, to be used from commands like `reflog`
* `..`: range specification: e.g. `git log A..B` shows changes in `B` not yet in `A`
* `...`: exclusion range specification: commits that are in either of the references but not in both
* `--not`: exclude commits not reachable by a reference, e.g. `git log A B --not C` shows changes either at `A` or `B`, but not present in `C`


### Branch Management

* `git checkout -b <new-branch-name>` : get a new branch and switch to it
* `git checkout -b <branch-name>` : get a local copy of a remote existing branch
* `git checkout <branch-name>` : switch to an existing branch
* `git checkout --orphan <branch-name>` : create branch without full history
* `git fetch`: update your local repo reference list (list of branches and latest commits they point to)
* `git pull origin <branch-name>`: update commits info from a branch
* `git pull --rebase`: Pull changes, and if both your local and the remote had changes, rewrite local history by placing server commits before, and yours after them. Does not add a merge commit
* `git merge --no-ff <source-branch>` : merge into current branch source one without rebase/FF. This will create a new merge commit
* `git push origin <destination-branch>` : push merges/changesets to a branch
* `git push origin <local-branch>:<remote-branch>` : push merges/changesets to a different branch than local
* `git push origin <destination-branch> --force-with-lease` : push merges/changesets to a branch. If remote had different commits not in local, fails and does not push. Safer than `--force` which would always override remote commits
* `git remote -v`: show all remotes configured in the repository
* `git remote show origin` : display the path of the repository
* `git remote set-url origin xxx` : Change remote URI to xxx
* `git remote rm <name>` / `git remote remove <name>` : Remove a remote
* `git remote rename <old> <new>`: Rename a remote (just the name, not the URI)
* `git remote add origin git@github.com:<path>.git`: replace the origin remote URI. Need to first`git remote rm origin` to remove the previous one
* `git remote add <user> git@github.com:<user>/<path>`: add `<user>` fork to a repository as a remote. When pushing changes afterwards to that remote, Github will propose you to create a pull request directly from the fork remote towards the original remote
* `git pull <user>`: fetch branches from remote `<user>`
* `git add xxx` : add files (use `.` for everything, `<folder>/..` for folder recursive children)
* `git add -i`: [interactive staging](https://git-scm.com/book/en/v2/Git-Tools-Interactive-Staging); allows to decide what to do with each local change (often with the `patch` option)
* `git commit` : commit changes
* `git status` : show status of uncommited files
* `git checkout <filename>` : revert a file
* `git checkout <branch-name> <filename>` : Checkout all changes to `<filename>` from branch `<branch-name>` into current
* `git checkout <revision> .` : revert a full branch to specified revision if not commited
* `git revert <commit1> <commit2> ...`: Reverts certain commits if commited, by creating a **new** commit that undoes the changes. Note that you can also do a `git reset` or `git rebase -i`, if your changes are not yet pushed/merged. Be careful with `reset` and `rebase`, as you will need to force push, and that's always dangerous.
* `git reset <revision-hash> .`: revert a full branch to specified revision if commited
* `git reset --soft HEAD~1`: reset to last commit (even if pushed). Changes will be staged, and can be committed again, but if pushed before, you need to push with `--force-with-lease`
* `git reset --hard HEAD^`: forcibly resets to the branch's head, discarding any commit
* `git reset --hard origin/<branch-name>`: forcibly resets your local branch to the remote version. Helpful when gets easy to just discard any local operations and revert to the old state
* `git reset --hard ORIG_HEAD`: Just did a rebase and now regretting it? Then this command undoes it. Only valid if exactly the previous operation was the rebase.
* `git reset HEAD <file>`: reset `<file>` to the version in `HEAD`
* `git reset --soft <new-root-hash> && git commit --amend -m "<new message>" && git push --force`: squash all branch pushed commits previous to the one specified into a single commit with the desired new message
* `git reset 'HEAD@{1}'`: revert to the previous command's commit. Check the `reflog` to see which command was last. This is **very** useful to undo non-trivial commands like a `git reset HEAD~1`
* `git reset HEAD@<commit>`: revert to a specific commit (often from the `reflog`)
* `git clean -n`: dry run to check which uncommited modifications would be removed
* `git clean -fd` : remove all local uncommited modifications
* `git restore --staged .`: unstage all staged changes. Combined with `git checkout .` and `git clean -fd`, leaves your local checkout "clean"
* `git branch` : display local branches, active one is with an `*`
* `git branch -vv`: display additional information. Remember state is only regarding local git repo, not remote(s). Reflects state since last fetch
* `git branch --sort=-committerdate # DESC`: display local branches, ordered by last commit date
* `git branch -r`: list all remote branches
* `git diff` : Show changes in files
* `git diff <branch-name> origin/<remote-branch-name>` : show a diff between a local branch and a remote one from the `origin` remote
* `git --no-pager diff --name-only master...HEAD`: list all modified files in commits from current branch in regards to `master` **up to your last pull**
* `git diff > <filename>`: Save a diff patch file. To apply it, use `git patch <filename>`.
* `git diff | pbcopy`: Copy the diff into the clipboard.
* `git rebase <branch-name>` : rebases current branch with specified branch (fetches remote branch changes and then adds your changes at the tip)
* `git rebase -i <branch-name>`: rebases current branch with specified branch, allowing you to reorder changes
* `git rebase -i HEAD~3`: allows you to check and reorder, mix or remove the last 3 commits from the current branch. **Very** useful to rewrite local commits, squash some of them, etcetera, before preparing a branch for review
* `git rebase --onto <A-branch> <B-branch> <C-branch>`: Rebase commits from `C` not present at `B`, into `A`; assuming that `B` was branched from `A`, and `C` was branched from `B` ("skip the intermediary"). 
* `git rm <filename>` : delete a file from branch and filesystem
* `git branch -d <branch-name>` : delete a local branch
* `git push origin --delete <branch-name>` : delete a remote brach
* `gitk <filename>` : show visual git log
* `git submodule update --init --recursive` : Init and update all submodules
* `git submodule foreach --recursive git reset --hard`: Reset all submodule changes
* Switch from current branch having a submodule to a branch without it:
```
rm -Rf <submodule-folder>
git reset && git checkout 
git checkout <branch-name>
```
* `git pull https://github.com/<username>/<repository-name>.git <branch-name>`: [Merge a pull request to local branch](https://help.github.com/articles/merging-a-pull-request)
* `git checkout --theirs xxxx` `git checkout --ours xxxx`: Keep changes from incoming branch or local one, respectively
* `git cherry-pick <commit>`: merges and commits a specific commit to current branch. Note that SHA of the commit will change (as the date changes)
* `git commit --amend`: Squash a change on previous commit and change the commit message
* `git commit --amend --author='Kartones <your@email.com>' --no-edit`: Change the author of the last commit (note that the email is enclosed inside `<>`)
* `git rebase -i HEAD~3 -x "git commit --amend --author='Kartones <your@email.com>' --no-edit"`: Change the author of the last 3 commits
* `git diff --staged`: Normal `git diff` shows only unstaged changes, with `--staged` you can see staged changes that will be committed
* Undo a commit removing it from history:
```
git reset --hard HEAD~1
# your new commit goes here
# option a:
git push origin <branch> --force-with-lease
# option b: (more dangerous, see below)
git push origin <branch> --force
```
* When pushing, `--force` will destroy any commit added by others to the remote. `--force-with-lease` tries to keep all upstream commits, and if it can't (because there is a conflict), it will abort and notify you.
* Tag any commit of a repo (e.g. with a certain version):
```
git tag <label> <commit-id>
git push origin <label>
```
* Apply a `.diff` file: `git apply <filename>.diff`
* Push to a different remote branch: `git push origin <localb-ranch>:<remote-branch>`
* Rename a branch: `git branch -m <old-name> <new-name>`
* `git commit --no-verify ...`: With great power comes great responsibility. This flag disables all commit hooks, so use it only when really in need
* `git fetch origin master && git rebase origin/master && git push origin HEAD --force-with-lease`: Rebase `master` with any new remote change, and then push, but not forcing if there is any kind conflict with the push. Also works if there's any amended commit


### Repository management

* `git clone <repository-uri> <folder-name>`: Clone a repository with a different folder name instead of the default repository name.

### Stashing

* `git stash`: [Stash](http://git-scm.com/book/en/Git-Tools-Stashing) current changes
* `git stash -u`: Stash **also** the untracked changes
* `git stash apply`: Unstash and merge stored changes
* `git stash list`: check all stashes


### Logs, Search

* `git log -n X -p` : show X last commits with diffs
* `git log --oneline -n X` : show X last commits, as a list of commit messages only (no content)
* `git log <branch-A-name> ^<branch-B-name>` : commits in branch A that aren't in branch B
* `git log -- <path>`: filter to commits including modifications to `<path>`/file
* `git log --pretty=oneline --stat --all <folder-name>` : same as previous, but better presented
* `git log --format='%H' -1 HEAD`: Get last commit hash from current branch
* `git log --pretty=format:"%h %s" --graph`: format the commits log as nice graph with hash & commit message
* `git rev-list origin/master...master`: list all commits that exist on local `master` but not on `origin/master`
* `git ls-remote git@xxxx.git | grep <commit-sha>`: search if a commit exists upstream
* `git reflog`: show a history of changes in all branches
* `git show HEAD@{3}`: show commit details (easiest way to combine with `reflog`)
* `git log origin/<branch-name>..<branch-name>` : Show diff between remote commits and local commits
* `git log <branch-name>..origin/<branch-name>` : Show diff between local commits and remote commits
* `git blame -M`: Blames original commit, not the move commit
* `git blame -CCC`: Looks at all commits in history
* `git log -S <string>`: find all commits that modified occurrences of `<string>`, e.g. `git log -S myFunction` ([+ info]([Title](https://git-scm.com/docs/git-log#Documentation%252Fgit-log.txt--Sltstringgt)))
* `git log --grep="<string>"`: search for a string in git logs 
* `git log -G <regex>`: Search in which commits the regular expression matches
* `git tag -l "v1.*"`: Searching for tags supports wildcard via the `-l` option
* `git grep`: Search patterns in tracked files ([+ info](https://git-scm.com/docs/git-grep))
* `git merge-base <branch-A> <branch-B>`: Find common ancestor commit between two branches
* `git diff --name-only HEAD~2 HEAD`: Show a list of modified files from the last `2` commits

## Configuration

* `git config --list` : List currently setup config values
* `git config --global push.default simple`: Makes git refuse a push if the upstream branch's name is different from the local branch's name, to prevent accidental pushes to the wrong branch.
* `git config --global user.name "<username>"` : Setup global user name to `<username>`
* `git config --global user.email "<email>"` : Setup global user email to `<email>`
* `git config --global credential.helper 'cache --timeout=28800'` : Make git cache credentials for 8 hours
* `git config --global color.ui true` : Activate colors in diffs, etc
* `git config --global core.autocrlf input` : Fix Convert newlines to Unix-style ones (**Unix**)* 
* `git config --global core.autocrlf true` : Fix Convert newlines to Unix-style ones (**Windows**)
* `git config --global core.excludesfile ~/.gitignore` : Instruct git to **always** ignore patterns defined at `~/.gitignore`
* `git config --global pager.log 'diff-highlight | less'`: Better diff highlighting (same for 3 following options)
* `git config --global pager.show 'diff-highlight | less'`
* `git config --global pager.diff 'diff-highlight | less'`
* `git config --global interactive.diffFilter diff-highlight`
* `git config --global core.editor <editor-name>`: change default editor (for commit messages, etcetera)

## Advanced Branch & Repository Management

* [git grafting](https://www.seachess.net/notes/git-grafting/): Technique to pick part of a git repository and add it to another repository.
* [git-filter-repo](https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html): A tool to rewrite a repository's history. Also used when merging multiple repositories into one. Destructive operation, allows to rewrite almost everything.

## Tools

* `git -C <path> <command> ...`: Run git commands as if current folder was `<path>`
* When you push changes, the format of the push output's last line is: `<oldref>..<newref> <from-branch> -> <to-branch>`
* [cli](https://github.com/cli/cli): GitHub’s official command line tool. Augment with the following (third party):
    * [git extras](https://github.com/tj/git-extras/): Many tools, but I use it just because I can see branches ordered by last activity via `git brv`
    * [poi](https://github.com/seachicken/gh-poi): Tool to delete merged branches (checks PRs) via `gh poi`
* Show the current git branch at prompt in **Linux** (add to `~/.bashrc`):
```
parse_git_branch() {
    git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\w\[\033[32m\]\$(parse_git_branch)\[\033[00m\] $ "

# user not shown in normal prompt, so I like to show it when starting a session
echo "User: $USER"
```
* Show the current git branch at prompt in **macOS** (add to `~/.zshrc`):
```
parse_git_branch() {
    git symbolic-ref --quiet --short HEAD 2> /dev/null | sed -e 's/\(.*\)/ %F{240}(%f%F{39}\1%f%F{240})%f/'
}

autoload -Uz add-zsh-hook

function update_prompt() {
    local git_branch=$(parse_git_branch)
    PROMPT='%~'"$git_branch"' $ '
}

add-zsh-hook chpwd update_prompt

zshaddhistory() {
  if [[ $1 == git\ checkout* ]] || [[ $1 == gh\ pr\ checkout* ]]; then
    NEEDS_PROMPT_UPDATE=true
  fi
  return 0  # Ensure the function returns 0 so the history system works correctly
}

precmd() {
  if [[ $NEEDS_PROMPT_UPDATE == true ]]; then
    update_prompt
    NEEDS_PROMPT_UPDATE=false
  fi
}

# user not shown in normal prompt, so I like to show it when starting a session
echo "User: $USER          "

update_prompt

# disable zsh git branch name autocompletion
# uncomment if you have autocompletions installed, else not needed
# compdef -d git
```
- Show the current git branch at prompt in **macOS**, more complete solution: [Pure (fork)](https://github.com/kartones-forks/pure)

### Third-party Tools

* [tig](http://blogs.atlassian.com/2013/05/git-tig/): to navigate commits & branches
* Github:
    * [gh](https://cli.github.com/): Official GitHub CLI tool. Very powerful if you interact with `github.com`. Example usage to fetch and checkout a branch: `gh pr checkout 123`.
    * [Commands for automatically closing tickets when merged to default branch](https://help.github.com/articles/closing-issues-via-commit-messages/)
    * Search to display all issues/PRs of an organization:
    `
    https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+org%3Athemotion+sort%3Aupdated-desc
    `
    * Additionally, filter to things assigned to me or involving me:
    `
    https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+org%3Athemotion+sort%3Aupdated-desc+involves%3Akartones
    `

## Tutorials

![xkcd: Git https://xkcd.com/1597/](https://images.kartones.net/posts/kartonesblog/xkcd-git.png) ![Git conceptual model - https://blog.acolyer.org/2016/10/24/whats-wrong-with-git-a-conceptual-design-analysis/](https://images.kartones.net/posts/kartonesblog/git_conceptual_model.png)


* **[official documentation](https://git-scm.com/docs) + [free book](https://git-scm.com/book/en/v2)** (really good!)
* [Spark Code Hub Git Tutorial](https://www.sparkcodehub.com/git-tutorial): Compact and direct to the point, covers all the relevant parts
* Mental model: Git keeps three trees (note this is local, not factoring in remotes):
    * HEAD: Branch status, last commit reference
    * Stage: staged files that will be sent to HEAD
    * Working directory: local file-system files (unstaged and/or untracked content)
* Commit messages should be in imperative form, e.g. "Fix bug"
* [9 useful tricks of git branch](https://gitbetter.substack.com/p/9-useful-tricks-of-git-branch-you)
* [Most Common Git tips & tricks](https://github.com/git-tips/tips) (awesome list)
* [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) (first [configure remote upstream](https://help.github.com/articles/configuring-a-remote-for-a-fork/))
* `supersedes`: when a branch replaces a previous branch that has been closed without merging.
* My preferred approach to working with forks:
    1. Clone the **main upstream**, not your fork: `git clone git@github.com:<user-or-org>/<repo>.git`
    2. Sync and update submodules: `git submodule sync --recursive; git submodule update --init --recursive`
    3. Setup your fork as another upstream: `git remote add $USER git@github.com:<your-github-user>/<repo>.git`
    4. Disable pushes to main upstream: `git remote set-url --push origin no_push`
    5. Now push always to your upstream: `git push $USER HEAD`
* [Commitizen](https://commitizen-tools.github.io/commitizen/) and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) commit messages syntax (good practice in general):
    * `feat`:     New feature (~ `X.y.z`)
    * `fix`:      Bug fix (~ `x.Y.z`)
    * `chore`:    Changes to auxiliary tools and libraries, tiny changes (~ `x.y.Z`)
    * `build`:    Changes to the build/CI process
    * `docs`:     Documentation only changes
    * `style`:    Linting, formatting, etc. only changes
    * `refactor`: Change that neither fixes bugs or adds new features
    * `perf`:     Change that improves performance
    * `test`:     Change that adds or modifies tests
* [How packfiles work](https://codewords.recurse.com/issues/three/unpacking-git-packfiles): Advanced topic.
* Obtaining the current tip commit is quick via `git rev-parse HEAD`, but you can also ask for shorter versions, e.g. `git rev-parse --short=9 HEAD`
* You can reproduce git file and folder hashes easily:
    * `git ls-files -s <filepath>`: returns, among other data, `<filepath>`'s SHA
    * `git rev-parse HEAD:<filepath>`: Like `ls-files -s`, but only returning the SHA
    * `git ls-tree HEAD <folder>`: returns, among other data, a SHA of inspecting all of `<folder>`'s contents
* Perform a folder case rename on a case-insensitive OS (Windows or macOS):
    * `git mv -f MyFolder MyFolder_temp`: 1st, move using `git mv`, first to a temporary new name
    * `git mv -f MyFolder_temp myFolder`: 2nd, move again, now to the definitive folder
    * `git status`: Now you should see `renamed: ...` items (one per file in the renamed folder)

## Optimizations

Important scalability note: Some operations depend on the size of the repository history (e.g. `git clone`), while others depend on the size and amount of repository files (e.g. `git status`).

* `git config --global checkout.workers 0`: Disable git's serial mode by enabling it to use one thread per CPU core. Should speed up operations in big or long-living repositories.
* `git maintenance start`: Enable `prefetch` and other git operations to happen in background (~hourly) for a repository.
* [sparse checkouts](https://git-scm.com/docs/git-sparse-checkout) + [sparse index](https://github.blog/open-source/git/make-your-monorepo-feel-small-with-gits-sparse-index/): Fetch only a subset of files/folders. [More info](https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/)
* [shallow clone](https://www.git-scm.com/docs/git-clone#Documentation/git-clone.txt---depthltdepthgt): Truncate git history to a specified number of commits.
* [partial clone](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---filterltfilter-specgt): Get only a subset of files given a filter. [More info](https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/) about partial and shallow clones.
* [git scalar](https://git-scm.com/docs/scalar): Tool for large repositories.

## Git Large File Storage (LFS)

* [git-lfs](https://git-lfs.com/) + [install guide](https://github.com/git-lfs/git-lfs/blob/main/INSTALLING.md): Supports large files. Recommended for non-text files, and for non-source code big text files (e.g. big language resource files).
* Linux TL;DR: Follow instructions at `git-lfs.com` + `sudo apt install git-lfs` + `git lfs install` from the target repo.
* `git lfs fetch --all <remote-name>`: Fetches LFS objects from **all** branches from a given remote. `<remote-name>` is optional (else picks your default remote).
* `find .git/lfs/objects -type f | wc -l`: When run inside a git repository, it'll count and display the number of LFS objects downloaded.
* `git lfs push origin --object-id <hash-or-hashes>`: Manually push one or more local-available git LFS objects, given their hashes (filename of `find .git/lfs/objects`).
* Useful LFS-related `git config` properties:
    * `lfs.dialtimeout <seconds>`
    * `lfs.tlstimeout <seconds>`
    * `lfs.activitytimeout <seconds>`
    * `lfs.keepalive <seconds>`
    * `lfs.concurrenttransfers <number>` (default is `8`): This one is good if you need to throttle LFS usage (e.g. for a big upload)
* `git lfs env`: Display all details regarding LFS configuration
* [Git LFS Man](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-config.adoc): Official Documentation .
* [Anchorpoint tutorial](https://www.anchorpoint.app/blog/push-and-pull-files-with-git-lfs) about basic Git LFS usage.
* There are [quite a few](https://github.com/git-lfs/git-lfs/wiki/Implementations) Git LFS server implementations.

## GitHub

* Get a raw diff from a pull request: You can go to a commit or pull request, and append a `.diff` to the URL to get a text diff view of it. e.g. given `https://github.com/Kartones/bazel-web-template/pull/284` then `https://github.com/Kartones/bazel-web-template/pull/284.diff` will show you the raw changes.

### Searches

* [Issues in which you're mentioned](https://github.com/issues/mentioned)
* [Pull requests in which you're mentioned](https://github.com/pulls/mentioned)
* `is:draft` works to filter pull requests in the search page, but there is no UI filter
* [All open issues and pull requests of organization `TEST`](https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+org%3ATEST+sort%3Aupdated-desc+): Change to proper organization name, and for example can filter to all assigned to you
* When comparing tags/branches, in the URL `X..Y` is to show code differences, while `X...Y` shows the differences in commits (which commits are missing).
* Get PR code comments (uses `gh` and `jq`): `gh api repos/<user>/<repository>/pulls/<pull-request-number>/comments --jq '.[] | select(.body != "") | {author: .user.login, body: .body, path: .path, line: .line}' | cat`
* Get PR review comments (uses `gh` and `jq`): `gh api repos/<user>/<repository>/pulls/<pull-request-number>/reviews --jq '.[] | select(.body != "") | {author: .user.login, body: .body}' | cat`

### Security
* [Anyone can Access Deleted and Private Repository Data on GitHub](https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github): Interesting reading. Not trivial, but neither too hard to brute-force, so careful with your deleted forks, deleted repositories and even with private repositories.
