Advanced Git cheatsheet

Git is the most widely used modern version control system in the world. It is a mature, actively maintained open source project originally developed by Linus Torvalds.

While every team is different, Git is the best choice for most of the software teams. The following git cheat sheet could be used to enhance the development workflow.

Configuration

There are three-level configuration in Git (the configuration precedence is decreasing in the following list):

  1. Repository-specific configuration settings (--file options). Location: .git/config
  2. User-specific configuration settings (--global options). Location: ~/.gitconfig
  3. System-wide configuration settings (--system options). Location: /etc/gitconfig

List the settings

git config -l

Config user

git config user.name "Akos Szoke"
git config user.email "aszoke@example.com"

Config alias

git config --global alias.<name> <complex git command>

Initial Setup

Initialize a repo

Create an empty git repo or reinitialize an existing one in specified directory. Run with no arguments to initialize the current directory as a git repository.

git init <directory>

Clone a repo

Clone the existing `foo` remote repo into a new directory called `foo`:

git clone https://<url>/<username>/foo.git foo

Making a copy of a repo

Once you clone a repository, you are able to modify the cloned version, make new commits and so on. It is a complete repository with full history.

git clone <source-repo-dir> <target-repo-dir>

Note on Fork vs. Clone

Clone: A fork is a kind of copy of a repository. When you clone a repository, the repository is copied on to your local machine. If changes were made on the local machine (cloned repository) they can be pushed to the (original) upstream repository directly.

Fork: A fork is an other kind of copy of a repository. When you fork a repository, you create a copy of the original repository (upstream repository) but the repository remains on your remote repository (e.g. Github). Actually, fork is a clone on the server side but has a connection with the source repository. Changes made to the forked repository can be merged with the original repository via a pull request.
Typical usages of forks:

  • Propose changes to someone else's project (e.g. bug fixes), then:
    • Fork the repository
    • Make the fix
    • Submit a pull request to the project owner
    • If the project owner likes the work, he / she might pull the fix to the original repo
  • Use someone else's project as a starting point for out own idea 

Setup Remotes

Remotes are simply aliases that store the URL of repositories. See a list of the remote repositories whose branches you track in a local repository (-v or --verbose option)

git remote -v

If we haven't setup upstream (the previous command provides empty output), then we can set it up as follows:

git remote add upstream https://<url>/<upstream_username>/<repo_name>.git
git fetch upstream

The remote configuration is achieved using the `remote.origin.url` and `remote.origin.fetch` configuration variables.

    Note on Remote vs. Upstream vs. Origin 

    Remote: A remote in Git, also called a remote repository, is a Git repository that’s hosted on the Internet or another network.

    Upstream: generally refers to the original remote repository that we have forked.

    Origin: is our own remote repository, a clone of the original repository (i.e. upstream).
    When a repo is cloned, it has a default remote called origin that points to your fork. To keep track of the original repo, you need to add another remote named upstream (i.e
    git remote add upstream https://<url>/<upstream_username>/<repo_name>.git).

    Note:
    • We use the upstream to fetch from the original repository (in order to keep our local copy in sync with the project we want to contribute to).
    • We use the origin to pull and push since we can contribute to our own repository. (i.e. `git push origin https://<url>/<user_name>/<repo_name>.git`)
    • We contribute back to the upstream repository by making a pull request.
    • When we are cloning a GitHub repo on our local workstation, we cannot contribute back to the upstream repository unless we are explicitly declared as "contributor". So when we clone a repository to our local workstation, we're not doing a fork. It is just a clone.

    Every-day Workflow

    Staging Changes

    Adding a file to repository's index (i.e. staging files).

    git add <file>    # add single file
    git add .         # add files of the current directory

    Note: Git index (or staging area) is used to collect changes (i.e. the changeset) to the next commit to the local repository.

    Unstaging Changes / Restoring Files

    Maybe you accidentally staged some files that you don't want to commit than to restore workig directory files:

    git restore foo.js
    git restore .

    Remove file

    Remove files from the working tree and from the index

    git rm <file>		# removes a file from both the repo and the working directory
    git rm -f <file>	# force the removal of the file even if is is altered
    git rm --cached <file> 	# removes the file from both the index and the working directory

    Move or rename a file, a directory, or a symlink:

    git mv <file 1> <file 2>

    Note: The followings are equivalent with the previous:

    mv <file 1> <file 2>
    git rm <file 1>
    git add <file 2>

    Viewing commits and differences

    Display the commit history

    Display the entire commits using the default format:

    git log

    Listing a given branch:

    git log <branch name>

    Listing only commits backwards:

    git log -<number>

    Details the changes that was introduced by the commit:

    git log -p <commit ID>

    Enumerates the changed files and changes only:

    git log --stat

    Printing textual graphs:

    git log --graph

    Show commit as a nice graph:

    git log --graph --abbrev-commit --pretty=oneline
    Note: Formatting information could be --pretty={short, oneline, full}

    Show commits in range:

    git log --after="2021-02-09" --before="2021-02-09" # commits after and before dates
    git log --author="aszoke"     # limits the author (pattern matching is used)
    git log --grep=".*rofil.*"    # limits text from the log messages
    git log <branch name>         # limits branches
    git log --max-count=3	      # limits the number of commits to output

    Status

    List which files are untracked, unstaged (are in the working directory), staged (with `git add`) on the branch you are on:

    git status

    Diff

    Show what is changed but unstaged:

    git diff

    Show what is staged but not yet commited:

    git diff --staged

    Show the diff of what is in branchA that is not in branchB:

    git diff branchB...branchA

    Show the diff between two commits:

    git diff <commit-id1> <commit-id2>

    Show changes between the working directory and a given commit:

    git diff <commit-id>

    Show changes between the stage area and a given commit:

    git diff --cached <commit-id>

    Branching

      Note on Branches 

      Git branches are effectively a pointer to a snapshot of your changes - it's not a container for commits. The history for a branch is extrapolated through the commit relationships.

      List Branches

      Show all the branches that exist in the local repo, in the remote repo or all repos:

      git branch
      git branch -r
      git branch -a

      Switching Branches

      Note on Checkout vs. Switch

      The new git switch branch command is meant to provide a better interface by having a clear separation, which helps to alleviate confusion with the usage of git checkout.

      Switch to a specified branch with git switch :
      git switch master
      git switch develop
      git switch feature_x

      Switch to previous branch:

      git switch @{-1}

      Create a new branch

      Create a new branch named <new-branch-name>. As such, you generally want to stay off the master branch and work on your own feature branches so that master is always clean and you can base new branches off of it (-c or --create option).

      git switch -c <new-branch-name>

      This command is equivalent to the next two commands:

      git branch <new-branch-name>
      git switch <new-branch-name>

      If upstream has a special develop branch, you can checkout that branch separately, but you could setup tracking so you can sync it up from time to time. Like the master branch, don't work directly on this one.

      git switch -c <new-branch-name> --track upstream/<remote-branch-to-track>

      If you made some progress on a branch at work, but now you want to continue the work at home then . In that case, you're dealing with your own fork's branch, so you'll checkout from origin.

      git switch -c <new-branch-name> --track origin/<remote-branch-to-track>

      If you're using the same name as a remote branch name, this can be simplified:

      git switch -c <branch-name>

      Use the -C (i.e. capital c) option flag to force it.

      Commits

      Recording changes to the local repository. You can always squash down your commits before a push to be more condensed.

      git commit -m "Updated README"

      Want to automatically stage files that have been modified and deleted, but new files you haven't told git about will be unaffected? Pass the -a or --all option flag:

      git commit -am "Updated README"

      Add an empty directory to the repository (Git doesn't store directories so a little hack is needed):

      mkdir foo
      touch foo/.gitignore
      git add foo

      Undoing Commits

      The following command will undo your most recent commit and put those changes back into staging, so you don't lose any work:

      git reset --soft HEAD~1

       To completely delete the commit and throw away any changes:

      git reset --hard HEAD~1

      Squashing Commits

      Maybe you have 4 commits, but you haven't pushed anything yet and you want to put everything into one commit so the lead developer doesn't have to read a lot of unnecessary details during code review:

      git rebase -i HEAD~4

      Pushing

      Push a local branch for the first time:

      git push --set-upstream origin <branch>
      git push

      Configure Git to always push using the current branch name:

      git config --global push.default current

      Push a local branch to a different remote branch:

      git push origin <local_branch>:<remote_branch>

      Use the -f option flag to force it.

      Undo Last Push

      Some would say this is bad practice. Once you push something you shouldn't overwrite those changes. Instead, you're supposed to create a new commit that reverts the changes in the last one. So, technically, you shouldn't do this, but in rare cases you need to do it.

      git reset --hard HEAD~1 && git push -f origin master

      Fetching

      Note on Fetch vs Pull

      Fetch is the command that tells your local git to retrieve the latest meta-data info from the original. It doesn’t do any file transferring just checking to see if there are any changes available.

      Pull brings (copy) the changes from the remote repository to the local repository.

      Fetch changes from upstream:
      git fetch upstream

      Fetch changes from both origin and upstream in the same shot:

      git fetch --multiple origin upstream

      Clearing deleted remote branches (after pull request):

      git fetch --prune

      Merging

      Note on Merging and Rebasing

      Git rebase and merge both integrate changes from one branch into another. The difference is how it's done. Rebase moves a feature branch into a master, while merge adds a new commit, preserving the history.

      Therefore, rebase streamlines a potentially complex history (usually applied in huge projects where the commit noise is huge - e.g. in Linux kernel development). Merge is used to maintain complete history and chronological order (usually applied in smaller projects where the history is important).

      More information could be found in the Git Rebase vs. Merge: Which Is Better? article.

      Merge-in changes from origin's to feature_x branch:
      git fetch origin
      git merge origin/feature_x

      Pulling

      Pulling is a fetch followed by a merge. If you know that a branch is clean, go ahead and get the latest changes. There will be no merge conflicts as long as your branch is clean.

      git pull origin/feature_x

      Rebasing

      Rebasing is a way of rewriting history. In place of merge, what this does is stacks your commits on top of commits that are already pushed up. In this case, you want to stack your commits on top of origin/feature_x:

      git fetch origin
      git rebase origin/feature_x

      If you already have a local branch set to track feature_x then just do:

      git rebase feature_x

      Would you like to fetch, merge and then stack your changes on top, all in one shot? You can! If you have tracking setup on the current branch, just do:

      git pull --rebase

      Another great use of rebasing is just rewriting commit messages. To get an interactive text editor for the most recent commit, do:

      git rebase -i HEAD~1

      Now, you can replace "pick" with "r" and just change the commit message.

      Manually Set Tracking

      Perhaps you forgot to setup tracking when you pulled down a remote branch. No worries:

      git config branch.<local_branch>.remote origin
      git config branch.<local_branch>.merge refs/heads/<remote_branch>

      Deleting Branches

      Delete a local branch:

      git branch -d <local_branch>

      Use the -D option flag to force removal of an unmerged branch.

      Delete a remote branch on origin:

      git push origin :<remote_branch>

      Stashing

      Doing a stash with a message to identify changes:

      git stash -m "<message>"

      See which stashes are available, you can use:

      git stash list
      Note on Stashing and Cleaning

      Tags When you’ve been working on part of your project, things are in a messy state and you want to switch branches for a bit to work on something else - for example fixing a bug. The problem is, you don’t want to do a commit of half-done work just so you can get back to this point later. The answer to this issue is the git stash command. Stashing takes the dirty state of your working directory and saves it on a stack of unfinished changes that you can reapply at any time.

      More information could be found in Git Tools - Stashing and Cleaning article.

      If you want to unstash those changes and bring them back into your working directory:

      git stash pop

      Or maybe you want to unstash your changes without popping them off the stack. In other words, you might want to apply these stashed changes multiple times. To do this:

      git stash apply

      For a list of stashes:

      git stash list

      And to apply a specific stash from that list (e.g., stash@{3}):

      git stash apply stash@{3}

      Viewing the content of the stashed code (e.g., stash@{3}):

      git stash show -p stash@{3}

      Tagging

      Note on Tags

      Tags are ref's that point to specific points in Git history. Tags are generally used to capture a point in git history that is used for a marked version release (i.e. v1.0).

      List

      To list stored tags in a repo execute the following:

      git tag

      List stored tags with wildcard characters using the l option:

      git tag -l <regexp>

      Fetch all tags from the remote (i.e., fetch remote tags refs/tags/* into local tags with the same name), in addition to whatever else would otherwise be fetched.

      git fetch --tags

      Create

      Create a new annotated tag identified with v1.0.0 with a message.

      git tag -a v1.0.0 <commit-id> -m "Initial release"

      Push

      All refs under refs/tags are pushed, in addition to refspecs explicitly listed on the command line:

      git push --tags

      Delete

      Delete existing tags with the given names:

      git tag -d v1.0.0

      Grep

      Look for specified patterns in the tracked files in the work tree, blobs registered in the index file, or blobs in given tree objects:

      git grep <regexp> $(git rev-list --all)