Skip to content

This document is a WORK IN PROGRESS.
This is just a quick personal cheat sheet: treat its contents with caution!


Git

Git is distributed revision control and source code management software.

Reference(s)

Table of contents


Avoid dotfile madness

Prior to installation, make sure you stay in control of your home directory.

Prerequisite(s)

See how to handle Git related dotfiles.


Install

 # emerge --ask dev-vcs/git
# pacman -S git
# apt install git
# yum install git
# dnf install git

Config

Reference(s)

Configure git globally:

$ git config --global core.editor vi # set editor (e.g. `vi`)
$ git config --global color.ui true # enable color

$ git config --global init.defaultBranch main # set default name for the initial branch (e.g. `main`), the default one is `master`

SSH

Generate and copy a new SSH key

Create an ssh key pair if you don't have one:

$ ssh-keygen -o -t ed25519 -C "user.name@mail.com" -f "/home/user/.ssh/ssh_key_name"

Copy the content of the public ssh key to your clipboard

$ xclip -sel clip < ~/.ssh/ssh_key_name.pub

SSH in a GitLab project

Add the new SSH key to your GitLab account:

  • Login to your GitLab account: https://gitlab.com/users/sign_in
  • Click on your avatar in the upper right corner and select "Settings".
  • Select the "SSH Keys" tab.
  • Paste the content of your clipboard to the "Key" field (and optionally modify its title).
  • Click on "Add key".

Test SSH with GitLab:

$ ssh-add ~/.config/ssh/ssh_key_name
$ ssh -T git@gitlab.com # answer 'yes' when asking if you want to continue connecting

SSH in a GitHub project

Add the new SSH key to your GitHub account:

  • Login to your GitHub account: https://github.com/login
  • Click on your avatar in the upper right corner and select "Settings".
  • Select the "SSH and GPG Keys" tab.
  • Click on "New SSH keys"
  • Paste the content of your clipboard to the "Key" field (and optionally modify its title)
  • Click on "Add SSH key"

Test SSH with GitHub:

$ ssh-add ~/.config/ssh/ssh_key_name
$ ssh -T git@github.com # answer 'yes' when asking if you want to continue connecting

SSH in a Bitbucket project

Add the new SSH key to your Bitbucket account:

  • Login to your Bitbucket account: https://bitbucket.org/

  • Click on your avatar in the lower left corner (to access your profile and settings) and select "Bitbucket Settings"

  • Select the "SSH Keys" tab.

  • Paste the content of your clipboard to the "Key" field and modify its title

  • Click on "Add key"

Test SSH with Bitbucket:

$ ssh-add ~/.config/ssh/ssh_key_name
$ ssh -T git@bitbucket.org # answer 'yes' when asking if you want to continue connecting

SSH tip

You can avoid the $ ssh-add ... step by editing the ssh config file:

$ vi ~/.ssh/config
    > ...
    > Host gitlab.com
    >   IdentityFile ~/.ssh/ssh_key_name
    >
    > Host github.com
    >   IdentityFile ~/.ssh/ssh_key_name
    >
    > Host bitbucket.org
    >   IdentityFile ~/.ssh/ssh_key_name
    > ...

Or simpler:

$ vi ~/.ssh/config
    > ...
    > Host *
    >   IdentityFile ~/.ssh/ssh_key_name
    >   IdentityFile ~/.ssh/other_ssh_key_name
    > ...

Take advantage of SSH

If one wishes to take advantage of SSH with a Git project, one need to import this project the right way, e.g.:

$ git remote add origin git@gitlab.com:user.name/project.git

Or one might switch a pre-existing repository to use SSH instead of HTTPS:

$ git remote set-url origin git@gitlab.com:user.name/project.git


Use

git config

Get and set repository or global options.

Reference(s)
  • Print all of your settings and where they are coming from:

    $ git config --list --show-origin
    

  • Set user name and email address globally:

    $ git config --global user.name "John Doe"
    $ git config --global user.email johndoe@example.com
    

  • Set user name and email address project wide:

    $ cd /path/to/project
    $ git config user.name "John Doe"
    $ git config user.email johndoe@example.com
    

  • Set default git editor:

    $ git config --global core.editor vim
    

  • Define a template message for git commits (project wide):

    $ cd /path/to/project
    $ vi .gitmessage
        > Subject line (try to keep under 50 characters)
        >
        > Multi-line description of commit,
        > feel free to be detailed.
        >
        > [Ticket: X]
    
    $ git config commit.template .gitmessage.txt
    $ git commit
    

.gitignore

Specifies intentionally untracked files to ignore.

Reference(s)
  • Create a .gitignore file and edit it (don't forget to commit it and push it afterwards):

    $ touch .gitignore
    $ vi .gitignore # use <https://www.gitignore.io/> to help create a good gitignore
    

  • List the files that are included in the "exclude lists" (based on .gitignore)

    $ git ls-files -ci --exclude-standard
    

  • Remove the files that are included in the "exclude lists" (based on .gitignore) from the repository (not from disk):

    $ git ls-files -ci --exclude-standard -z | xargs -0 git rm --cached
    

  • Check if a file is ignored or not (based on .gitignore):

    $ git check-ignore -v ./path/to/file
    

  • If a previously tracked file (or folder) has been added to the gitignore file, then make sure to untrack it:

    $ git rm --cached /path/to/file-or-folder
    

Tip

If you still see a file (or folder) with $ git status, after adding it to the .gitignore file (even after running $ git rm --cached /path/to/file-or-folder and after committing the removed file/folder), then the $ git check-ignore -v ./path/to/file-or-folder command should also return no output (meaning that the file/folder is not ignored). This is probably because the pattern of the .gitignore file is "wrong", e.g. you added a comment after a pattern (on the same line) instead of having a dedicated line for your comment.

git info exclude

Reference(s)

The purpose of .git/info/exclude is the same as .gitignore: excluding files and/or folders (and the syntax is the same).

But, as opposed to .gitignore, .git/info/exclude cannot be pushed/pulled, every developer manages it's own .git/info/exclude in it's local clone of the git repository. Hence what one person ignores in his clone is not available in some other person's clone.

In general, files/ignore rules that have to be universally ignored should go in .gitignore, and otherwise files that you want to ignore only on your local clone should go into .git/info/exclude.

git update-index

Register file contents in the working tree to the index

Reference(s)

$ git update-index will not propagate with git, each user will have to run it independently.

  • Stop updating a specific file or folder (new local modifications won't be tracked). It's like telling git you want your own independent version of the file or folder (see https://stackoverflow.com/a/40272289).

    $ git update-index --skip-worktree ./path/to/file-or-folder
    
    But, if your local version differs from the remote one you will be notified with the following message:
    error: Your local changes to the following files would be overwritten by merge:
      /path/to/file-or-folder
    Please commit your changes or stash them before you merge.
    Aborting
    

  • Cancel the previous command:

    $ git update-index --no-skip-worktree ./path/to/file-or-folder
    

  • Tell git to stop checking a specific file or folder for changes, locally, assuming there won't be any. The assume unchanged index will be reset and file(s) overwritten if there are upstream changes to the file/folder (when you pull). This really is for optimization purpose, in order to speed up git process, e.g. when tracking a folder with a large number of files on a slow file system.

$ git update-index --assume-unchanged ./path/to/file-or-folder
  • Cancel the previous command:
    $ git update-index --no-assume-unchanged ./path/to/file-or-folder
    

git init

Create an empty Git repository or reinitialize an existing one.

Reference(s)
  • Init a git repository in an existing folder my-project:

    $ cd /path/to/my-project
    $ git init
    

  • Init a git repository my-project from scratch:

    $ cd /path/to/
    $ git init my-project
    

git remote

Manage set of tracked repositories.

Reference(s)
  • Show your remotes:

    $ git remote -v
        > origin    git@origin-url.net/repoTest.git (fetch)
        > origin    git@origin-url.net/repoTest.git (push)
    

  • Change a remote URL:

    $ git remote set-url origin git@origin-url.net/repo.git
    $ git remote -v
        > origin    git@origin-url.net/repo.git (fetch)
        > origin    git@origin-url.net/repo.git (push)
    

  • Add a remote (e.g. named test):

    $ git remote add test git@url2.org/code.git
    $ git remote -v
        > test      git@test-url.net/repo.git (fetch)
        > test      git@test-url.net/repo.git (push)
    

  • Remove a remote (e.g. named bad-remote):

    $ git remote remove bad-remote
    

  • Pull all branches from all your remotes:

    $ git pull –all
    

  • Pull all branches from all your remotes by default:

    $ vi $HOME/.gitconfig # or $XDG_CONFIG_HOME/git/config or wherever
      + > pull = pull --all
      + >
        > ...
    
    $ git pull
    

  • Pull a specific branch (e.g. "master") from a specific remote (e.g. "test"):

    $ git pull test master
    

  • Push to all your remotes, by adding them to origin:

    $ git remote set-url --add origin git@test-url.org/code.git # add test remote url to origin push list
    $ git remote -v
        > origin    git@origin-url.org/repo.git (fetch)
        > origin    git@origin-url.org/repo.git (push)
        > origin    git@test-url.org/repo.git (push)
        > test      git@test-url.org/repo.git (fetch)
        > test      git@test-url.org/repo.git (push)
    
    $ git push              # (push default branch "master")
    $ git push branch-name  # (push specific branch "branch-name")
    $ git push -all         # (push all branches)
    

  • Remove a remote from origin:

    $ git remote set-url --delete origin git@test-url.org/code.git # remove test remote url from origin push list
    

  • Push all branches by default:

    $ vi .gitconfig
        > push = push --all
    
    $ git push
    

  • Push a specific branch (e.g. "master") to a specific remote (e.g. "test"):

    $ git push test master
    

  • Rename a remote:

    $ git remote rename old-name new-name
    

git add

Add file contents to the index.

Reference(s)
  • Stage all changes for commit:

    $ git add --all
    

  • Add a specific file to unstaged changes:

    $ git add -N /path/to/file
    

git stash

Stash the changes in a dirty working directory away.

Reference(s)
  • Stash changes locally: (this will keep the changes in a separate change list called stash and clean the working directory. You can apply changes from the stash anytime)

    $ git stash
    

  • Stash changes with a message:

    $ git stash save "message"
    

  • List all the stashed changes:

    $ git stash list
    

  • Inspect the content of a stash:

    $ git stash show stash@{42}
    

  • Remove a stash:

    $ git stash drop stash@{42}
    

  • Apply the most recent stash changes and remove it from the stash list:

    $ git stash pop
    

  • Apply any stash from the list without removing the stash from the stash list:

    $ git stash apply stash@{42}
    

git commit

Record changes to the repository.

Reference(s)
  • Commit staged changes:

    $ git commit -m "Your commit message"
    

  • Edit previous commit message, if it hasn't been pushed already:

    $ git commit --amend
    

  • Git commit in the past:

    $ git commit --date="`date --date='2 day ago'`"
    $ git commit --date="Jun 13 18:30:25 IST 2015"
    $ git commit --date="2 days ago" # for more recent git versions
    

  • Change the date of an existing commit:

    $ git filter-branch --env-filter \
        'if [ $GIT_COMMIT = 119f9ecf58069b265ab22f1f97d2b648faf932e0 ]
         then
             export GIT_AUTHOR_DATE="Fri Jan 2 21:38:53 2009 -0800"
             export GIT_COMMITTER_DATE="Sat May 19 01:01:01 2007 -0700"
         fi'
    

git revert

Given one or more existing commits, revert the changes introduced by theses commits. This requires a clean working tree (no modifications from the HEAD commit).

Reference(s)
  • Revert the project to a previous commit (e.g. with commit hash 0766c053), with a single commit reverting all the changes:
    $ git revert --no-commit 0766c053..HEAD
    $ git commit
    

git reset

Reset current HEAD to the specified state.

Reference(s)
  • Remove/Undo "git add" before commit:

    $ git reset filename
    

  • Removed staged and working directory changes:

    $ git reset --hard
    

  • Go 2 commits back:

    $ git reset HEAD~2
    

  • Undo last commit (and never see it again):

    $ git reset --hard HEAD~1
    

  • Undo last commit (but it is preserved, one just go back of one commit):

    $ git reset HEAD~1
    

git restore

Restore working tree files.

Reference(s)

TODO

git clean

Remove untracked files from the working tree.

Reference(s)
  • Remove untracked files:

    $ git clean -f -d
    

  • Remove untracked and ignored files:

    $ git clean -f -d -x
    

git fsck

Verifies the connectivity and validity of the objects in the database.

Reference(s)
  • Print objects that exist but that are never directly used:

    $ git fsck --dangling
    

  • Write dangling objects into .git/lost-found/commit/ or .git/lost-found/other/, depending on type:

    $ git fsck --lost-found
    

Tip

After a commit, you might end up pushing nothing, the commit seems to have "disappeared". This might be because your are not on the HEAD commit of your branch (maybe you previously checkout on a past commit, e.g. with a checkout on a tag), in this case you should not commit before doing a checkout at the HEAD of a branch. If you commit anyway, the commit will become "dangling", in this case you can find it and restore it like so:

$ git checkout master # checkout at the HEAD of master or any other branch you prefer
$ git fsck --dangling
    > ...
    > dangling commit 0d02483811d9032318bdb559a2accb6dfc033506
    > ...

$ git show  0d02483811d9032318bdb559a2accb6dfc033506
$ git merge 0d02483811d9032318bdb559a2accb6dfc033506

git push

Update remote refs along with associated objects.

Reference(s)
  • Push to the tracked master branch:

    $ git push origin master
    

  • Push to a specified repository:

    $ git push git@github.com:username/project.git
    

git pull

Fetch from and integrate with another repository or a local branch.

Reference(s)
  • Update the remote tracking branches for the repository you cloned from:
    $ git pull
    

git branch

List, create, or delete branches.

Reference(s)
  • Create a new branch called branch-name, switch to it, add things and push it:
    $ git branch branch-name
    $ git switch branch-name
    $ add foo
    $ git commit
    $ git push --set-upstream origin branch-name
    

Note that, in your new branch, if you add new files and/or folders and those are empty and/or ignored, then you might still see them after switching back to another branch. In this case, just run the following in the other branch : $ git clean -fd

  • Delete the branch branch-name locally:

    $ git branch -d branch-name
    

  • Completely delete the branch branch-name (locally and remotely, e.g. on remote origin):

    $ git push -d origin branch-name
    $ git branch -D branch-name
    
    $ git fetch --all --prune # run this on other machines to get rid of the deleted branch
    

  • Make an existing branch track a remote branch:

    $ git branch -u upstream/foo
    

  • List all local and remote branches:

    $ git branch -a
    

  • Print differences between the branch master and the branch branch-name:

    $ git diff master branch-name
    

  • Print just which files differ, not how the content differ, between the branch master and the branch branch-name:

    $ git diff --name-only master branch-name
    

  • Merge the branch branch-name into master, and delete it afterwards (locally and remotely, e.g. from remote origin):

    $ git switch master
    $ git merge branch-name
    $ git branch -d branch-name
    $ git push -d origin branch-name
    

git rebase

Reapply commits on top of another base tip. git rebase purpose is like to "cut" a branch and merge it to the tip of another one."

Reference(s)
  • What is the difference between merging and rebasing?

  • ⚠️ Only rebase on a "private" branch, i.e. a branch where you are the only one working on it! This is sometimes considered a golden rule.

  • An interactive rebase is very useful to "clean up" the commit history of a branch (or a fork), e.g. before proposing a pull request. For example:

    $ git rebase --interactive master
    
        pick 8714cb2 ...
        reword b749b62 ...
        edit aa25a46 ...
        pick fd62a63 ...
        exec make test1
        squash f7276ec ...
        pick 0c1f4fd ...
        exec make test2
        fixup 00b3c66 ...
        drop ded0ff0 ...
        break 516e3b6 ...
        pick bc778c0 ...
        pick e5c761a ...
        pick 9c9e3eb ...
        pick f739b57 ...
        pick bc6edb9 ...
        drop 0afd92a ...
    
        # Commands:
        # p, pick <commit> = use commit
        # r, reword <commit> = use commit, but edit the commit message
        # e, edit <commit> = use commit, but stop for amending
        # s, squash <commit> = use commit, but meld into previous commit
        # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
        #                    commit's log message, unless -C is used, in which case
        #                    keep only this commit's message; -c is same as -C but
        #                    opens the editor
        # x, exec <command> = run command (the rest of the line) using shell
        # b, break = stop here (continue rebase later with 'git rebase --continue')
        # d, drop <commit> = remove commit
        # l, label <label> = label current HEAD with a name
        # t, reset <label> = reset HEAD to a label
        # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
        #         create a merge commit using the original merge commit's
        #         message (or the oneline, if no original merge commit was
        #         specified); use -c <commit> to reword the commit message
        # u, update-ref <ref> = track a placeholder for the <ref> to be updated
        #                       to this position in the new commits. The <ref> is
        #                       updated at the end of the rebase
        #
        # These lines can be re-ordered; they are executed from top to bottom.
        #
        # If you remove a line here THAT COMMIT WILL BE LOST.
        #
        # However, if you remove everything, the rebase will be aborted.
    

    You can change any command in front of any commit hash, you can even re-order your commits.

  • Example of a rebase on a fork:

    $ cd /path/to/fork
    $ git remote add upstream git@git-server.com:group-name/upstream-project-name.git
    $ git fetch upstream
    $ git rebase -i upstream/master
        pick 8714cb2 ...
        fixup b749b62 ...
        fixup aa25a46 ...
    $ git push --force-with-lease
    
  • Example of a rebase including too much non-atomic commits (i.e. "bad" commits). In this case you might want to meld all commits together, then reset before that big commit in order to have all modifications unstaged. This way you can re-create all the commits you want in a more atomic way. For example :

    $ git rebase -i upstream/master
        pick 8714cb2 ...
        fixup b749b62 ...
        fixup aa25a46 ...
        fixup fd62a63 ...
        fixup f7276ec ...
        fixup 0c1f4fd ...
        fixup 00b3c66 ...
        fixup ded0ff0 ...
        fixup 516e3b6 ...
        fixup bc778c0 ...
        fixup e5c761a ...
        fixup 9c9e3eb ...
        fixup f739b57 ...
        fixup bc6edb9 ...
        ...
    
    $ git reset "HEAD~"
    
    $ git add --patch  # or `git add --interactive,`
    

    Note that git add --patch might not allow you to finely select which line(s) of which file(s) you want to a add in a commit. If you feel limited by this behavior, some external tools exists and allow a more precise selection, for example fugitive.

WIP/TODO

$ git pull --rebase

See https://www.atlassian.com/git/tutorials/advanced-overview See https://www.atlassian.com/git/tutorials/merging-vs-rebasing See https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing

git merge

Join two or more development histories together.

Reference(s)

TODO

git tag

Create, list, delete or verify a tag object signed with GPG.

Reference(s)
  • List tags:

    $ git tag -l
    

  • List tags associated to their own SHA-1 hash and the SHA-1 hash of the actual commit that the tag points to (lines ending with ^{}).

    $ git show-ref --tags -d
    

  • Create tag (release point):

    $ git tag v1.4 -m "my version message for 1.4"
    

  • Show a tag (e.g. v1.4) in more details:

    $ git show v1.4
    

  • Add a tag in the past (e.g. at commit 9fceb02):

    $ git tag -a v1.2 9fceb02
    

  • By default, $ git push doesn't transfer tags to remote servers. Here is how to transfer them:

    $ git push origin --tags
    

  • If you don't want to transfer all tags to remote servers, but just a specific one (e.g. v1.4):

    $ git push origin v1.4
    

  • Delete a tag (e.g. v1.4):

    $ git tag -d v1.4
    

  • By default, $ git push won't transfer a deleted tag to remote servers. Here is how to transfer one (e.g. v1.4):

    $ git push origin --delete v1.4
    

  • Move tag (e.g. move v1.8 to current commit):

    $ git tag -f v1.8
    $ git push -f origin v1.8
    

git checkout

Switch branches or restore working tree files.

Reference(s)
  • Switch to tag/release point (e.g v1.4):

    $ git checkout v1.4
    

  • Switch branch on a branch called "production":

    $ git checkout production
    

  • Switch back to the master branch with the local changes made on "production": (this will switch branch and merge the local changes)

    $ git checkout -m master
    

  • Restore the deleted file hello.c from the index:

    $ rm -f hello.c
    $ git checkout hello.c
    

  • Reverts the hello.c file two revisions back on master branch:

    $ git checkout master~2 hello.c
    

  • Create a bare new branch (one that has no commits on it):

    $ git checkout --orphan new_branch_name
    

  • Create a new branch from a different starting point, e.g. two commits behind:

    $ git checkout master^^ # equivalent to git checkout master~2
    $ git checkout -b new_branch_name
    

git switch

Switch branches.

Reference(s)

TODO

The git checkout command has a multitude of different jobs and meanings. That's why, pretty recently, the Git community decided to publish a new command: git switch. As the name implies, it was made specifically for the task of switching branches.

git cherry-pick

Apply the changes introduced by some existing commits.

Reference(s)
  • Pick a single commit (from the same branch or not) and apply it to local work:

    $ git cherry-pick <commit SHA1> # $ git cherry-pick ebe6942
    

  • Pick a range of commit (from the same branch or not), excluding the first one - including the last one, and apply it to local work:

    $ git cherry-pick <commit SHA1>..<commit SHA2> # e.g.: $ git cherry-pick ebe6942..905e279
    

  • Pick a range of commit (from the same branch or not), including the first one - including the last one, and apply it to local work:

    $ git cherry-pick <commit SHA1>^..<commit SHA2> # e.g.: $ git cherry-pick ebe6942^..905e279
    

git diff

Show changes between commits, commit and working tree, etc.

Reference(s)
  • Diff files WITHOUT considering them a part of git: (it can be used to diff files that are not in a git repository)

    $ git diff --no-index path/to/file/A path/to/file/B
    

  • Diff staged files (after they have been "added"):

    $ git diff --staged
    
    also --staged' is a synonym of--cached`, so the following is valid:
    $ git diff --cached
    

  • Diff one staged/cached files (after they have been "added"):

    $ git diff --staged -- yourFile
    

  • List files changed in ${commit_id}:

    $ git diff-tree --no-commit-id --name-only -r ${commit_id}
    

  • Check the changes between a local branch and it's remote branch:

    $ git fetch # update remote-tracking branches
    $ git diff master origin/master # make sure of the branches names with `$ git branch -a`
    

  • difftastic is a structural diff tool that compares files based on their syntax. It can be used with git diff (thanks to external diff support) like explained here.

git difftool

A Git command that allows you to compare and edit files between revisions using common diff tools. git difftool is a frontend to git diff and accepts the same options and arguments.

Reference(s)
  • The same commands used with git diff can be run, just replace diff by difftool:

    $ git difftool
    

  • difftastic is a structural diff tool that compares files based on their syntax. It can be used with git difftool like explained here.

git log

Show commit logs.

Reference(s)
  • Print a one liner of your current position:

    $ git log --pretty='format:%d  %h   %ci   %s' "$@" | head -1
    

  • Print commit history of a set of files:

    $ git log --pretty=email --patch-with-stat --reverse --full-index -- Admin\*.py > Sripts.patch
    

  • View commits that will be pushed:

    $ git log @{u}..
    

  • View changes that are new on a feature branch:

    $ git log -p feature --not master
    $ git diff master...feature
    

  • See everything you have done, across branches, in a glance:

    $ git reflog
    

git show

Show various types of objects.

Reference(s)
  • Show revisions can be identified with :/text. So, this will show the first commit that has "cool" in their message body:

    $ git show :/cool
    

  • List files changed in ${commit_id}, pretty way, meant to be user facing:

    $ git show --pretty="" --name-only bd61ad98
    

  • Show a tag (e.g. v1.4) in more details:

    $ git show v1.4
    

git submodule

Initialize, update or inspect sub modules.

Reference(s)
  • Clone a repository including sub modules:

    $ git clone --recurse-submodules git://github.com/foo/bar.git
    

  • If you forgot the --recurse-submodules option when cloning a repository, you can fetch the missing sub modules with the following command:

    $ git submodule update --init --recursive
    

  • Update all your sub modules to the latest tips of remote branches:

    $ git submodule update --recursive --remote
    

  • Update a specific sub module to the latest tip of remote branche:

    $ git submodule update ./path/to/submodule
    

  • Add a sub module to your repository:

    $ git submodule add git@git.server.name.io:path/to/repo.git submodule-folder-name
    

  • Add a sub module, in a specific branch, to your repository:

    $ git submodule add -b branch-name git@git.server.name.io:path/to/repo.git submodule-folder-name
    

  • Print sub modules status:

    $ git submodule status
    

  • Print remote/origin URLs (e.g. to ensure that a sub module is pointing to the right repository):

    $ git remote show origin
    

  • Remove a sub module:

    $ git submodule deinit -f -- submodule-name
    $ rm -rf .git/modules/submodule-name
    $ git rm -f submodule-name
    

git filter-branch and git filter-repo

⚠️ WIP ⚠️

Reference(s)

Use git filter-repo instead of git filter-branch

git filter-repo is now recommended by the git project instead of git filter-branch: https://git-scm.com/docs/git-filter-branch#_warning.

Install

# emerge -a dev-vcs/git-filter-repo
# pacman -S git-filter-repo
# nix-env -iA nixos.git-filter-repo
# nix-env -iA nixpkgs.git-filter-repo
# apt install git-filter-repo
# dnf install git-filter-repo
$ git clone https://github.com/newren/git-filter-repo.git
$ cd git-filter-repo
$ git checkout v2.34.0 # checkout to the latest release, e.g. v2.34.0 at the time of writing
$ make snag_docs
$ cp -a git-filter-repo $(git --exec-path)
$ cp -a git-filter-repo.1 $(git --man-path)/man1 && mandb
$ cp -a git-filter-repo.html $(git --html-path)
$ ln -s $(git --exec-path)/git-filter-repo \
    $(python -c "import site; print(site.getsitepackages()[-1])")/git_filter_repo.py

TODO:

git blame

Show what revision and author last modified each line of a file.

Reference(s)
  • See who committed which line in a file
    $ git blame filename
    

git sparse checkout

This command is used to create sparse checkouts, which means that it changes the working tree from having all tracked files present, to only have a subset of them. It can also switch which subset of files are present, or undo and go back to having all tracked files present in the working copy.

Reference(s)

Example:

$ git init
$ git remote add -f origin https://git.server.com/user/project.git
$ git config core.sparseCheckout true
$ git sparse-checkout init
$ git pull origin master
$ git switch branch-name # optionally switch to any branch
$ tree .
    .
$ git sparse-checkout set project/path/to/any/dir/or/file project/path/to/any/other/dir/or/file
$ git sparse-checkout list
    project/path/to/any/dir/or/file
    project/path/to/any/other/dir/or/file
$ tree .
.
└── project
    └── path
        └── to
            └── any
                ├── dir
                │   └── or
                │       └── file
                └── other
                    └── dir
                        └── or
                            └── file

Note that you can disable git sparse checkout like so:

$ git sparse-checkout disable
Once disabled, it will fetch all the folders/files from the repository.

git hooks

Reference(s)
  • Example of hooks: .git/hooks

git request-pull

Reference(s)

TODO

git-sizer

Reference(s)

git-sizer will compute various size metrics for a Git repository, it's very easy to use:

$ git-sizer --verbose

Pull/Merge request

Reference(s)

TODO

Creating a merge request for GitLab

As of GitLab 11.10, if you're using git 2.10 or newer, you can automatically create a merge request from the command line like this (see https://docs.gitlab.com/ee/user/project/push_options.html):

$ git push -o merge_request.create

A lot more useful options are available in order e.g. to add a title to the merge request, add a description, set the target of the merge request to a particular branch, mark it as a draft, add some labels, set a milestone, assign some users, etc:

$ git push -o merge_request.create \
           -o merge_request.title="The title I want" \
           -o merge_request.description="The description I want" \
           -o merge_request.target=project_path/branch-name \
           -o merge_request.draft \
           -o merge_request.label="label1" -o merge_request.label="label2" \
           -o merge_request.milestone="3.0" \
           -o merge_request.assign="user1" -o merge_request.assign="user2"
(see https://docs.gitlab.com/ee/user/project/push_options.html for more details)

Fetching a merge request from GitLab

WIP

$ git config remote.origin.fetch '+refs/merge-requests/*:refs/remotes/origin/merge-requests/*'
$ git fetch
$ git show-ref
6676fe2b810a3406747dfdaa5b4531db561b851e refs/heads/master
6676fe2b810a3406747dfdaa5b4531db561b851e refs/remotes/origin/HEAD
6676fe2b810a3406747dfdaa5b4531db561b851e refs/remotes/origin/master
6fd6fc72194c14c870fdb8e22089808f91b50bbc refs/remotes/origin/merge-requests/1/head

$ git switch ...

See https://www.jvt.me/posts/2019/01/19/git-ref-gitlab-merge-requests/.

Fetching a pull request from GitHub

$ git fetch origin pull/$PULL-REQUEST-ID/head:$YOUR-LOCAL-BRANCH-NAME
$ git switch $YOUR-LOCAL-BRANCH-NAME

Signing Your Work

Server

Misc

  • Reduce the size of a GitLab repository:

  • Sync a fork with the master repository:

    $ git remote add upstream git@github.com:name/repo.git # Set a new repository
    $ git remote -v                                        # Confirm new remote repository
    $ git fetch upstream                                   # Get branches
    $ git branch -va                                       # List local - remote branches
    $ git checkout master                                  # Checkout local master branch
    $ git checkout -b new_branch                           # Create and checkout a new branch
    $ git merge upstream/master                            # Merge remote into local repository
    $ git show 83fb499                                     # Show what a commit did.
    $ git show 83fb499:path/fo/file.ext                    # Shows the file as it appeared at 83fb499.
    $ git diff branch_1 branch_2                           # Check difference between branches
    $ git log                                              # Show all the commits
    $ git status                                           # Show the changes from last commit
    

  • Import commits from another repository:

    $ git --git-dir=../some_other_repo/.git format-patch -k -1 --stdout <commit SHA> | git am -3 -k
    

  • Interactive rebase for the last 7 commits

    $ git rebase -i @~7
    

  • Pull changes while overwriting any local commits:

    $ git fetch --all
    $ git reset --hard origin/master
    

  • Perform a shallow clone to only get latest commits (helps save data when cloning large repositories):

    $ git clone --depth 1 <remote-url>
    

  • To unshallow a clone:

    $ git pull --unshallow
    

  • Remove all stale branches (ones that have been deleted on remote), so if you have a lot of useless branches, delete them on GitHub and then run this:

    $ git remote prune origin
    

  • The following can be used to prune all remotes at once:

    $ git remote prune $(git remote | tr '\n' ' ')
    

  • Revert a commit and keep the history of the reverted change as a separate revert commit:

    $ git revert <commit SHA>
    

  • Move your most recent commit from one branch and stage it on TARGET branch:

    $ git reset HEAD~ --soft
    $ git stash
    $ git checkout TARGET
    $ git stash pop
    $ git add .
    

How to fork a repository

E.g. forking a public repository from GitHub to a private repository on GitLab.

A git repository can have more than one remote server, in this case we want to have two:

  1. One for our private repository on GitLab (will be the default one, called origin).
  2. One to be connected to the upstream repository on GitHub, to be able to pull new changes (will be called upstream).

Here is how to proceed:

  • Clone the GitHub repository you are interested in (e.g. git@github.com:whatever/repo.git):

    $ git clone git@github.com:whatever/repo.git
    

  • Rename the remote:

    $ git remote rename origin upstream
    

  • Create a new blank private project on GitLab (e.g. git@gitlab.com:whatever/private-repo.git).

  • Add the new origin to your repository:

    $ git remote add origin git@gitlab.com:whatever/private-repo.git
    

  • Push the master branch to the private repository (you can push any other branch the same way):

    $ git push -u origin master
    

  • Push all tags to the private repository:

    $ git push origin --tags
    

That's it!

  • To push to GitLab/master:

    $ git push
    

  • To retrieve updates from GitHub:

    $ git pull upstream master
    

How to mirror a repository

$ git clone --bare git@some-git-server.xyz:path/to/repository-name.git
$ cd repository-name
$ git remote add mirror-name git@maybe-another-git-server.xyz:maybe/another/path/to/repository-name.git
$ git push mirror-name --mirror

Code review with Git

Reference(s)
$ git pull
$ git checkout <branch>

Visualize file changes

Configure git files and git stat:

$ vi $HOME/.bashrc # or ${ZDOTDIR:-${HOME}}/.zshrc or wherever
    > ...
  + >
  + > # GIT
  + > export REVIEW_BASE="master"

$ vi ${XDG_CONFIG_HOME:-${HOME/.config}}/git/config
    > ...
  + > [alias]
  + >     # list files which have changed since REVIEW_BASE
  + >     # (REVIEW_BASE defaults to 'master' in my zshrc)
  + >     files = !git diff --name-only $(git merge-base HEAD \"$REVIEW_BASE\")
  + >
  + >     # Same as above, but with a diff stat instead of just names
  + >     # (better for interactive use)
  + >     stat = !git diff --stat $(git merge-base HEAD \"$REVIEW_BASE\")

See which files have changed:

$ git status --show-stash
$ git stat # list files that changed from master
$ REVIEW_BASE=HEAD^ git stat # list files that have changed only from the last commit

Visualize file change frequency

Install git heatmap (see https://github.com/jez/git-heatmap).

E.g. on Arch Linux (with AUR):

$ mkdir -p $HOME/apps/aur-apps
$ cd $HOME/apps/aur-apps/
$ git clone https://aur.archlinux.org/barchart.git
$ cd barchart
$ makepkg -si # install `barchart` dependency from AUR

$ mkdir -p $HOME/apps/src-apps
$ cd $HOME/apps/src-apps/
$ git clone https://github.com/jez/git-heatmap.git
$ cd git-heatmap
$ git checkout 0.10.3 # checkout to the latest tag/release (0.10.3 at the time of writing)

$ mkdir -p $HOME/bin
$ cd $HOME/bin
$ ln -s $HOME/apps/src-apps/git-heatmap/git-heatmap .

$ vi $HOME/.bashrc # or ${ZDOTDIR:-${HOME}}/.zshrc or wherever
    > ...
  + >
  + > # PATH
  + > export PATH="$HOME/bin:$PATH"

$ source $HOME/.bashrc # or ${ZDOTDIR:-${HOME}}/.zshrc or wherever

$ git heatmap -h

See which files are the most modified:

$ git heatmap

Visualize relationships between files

Review the diffs

Configure git review and git reviewone:

$ vi ${XDG_CONFIG_HOME:-${HOME/.config}}/git/config
    > ...
    > [alias]
    > ...
  + >   # Open all files changed since REVIEW_BASE in Vim tabs. Then, run fugitive's `:Gdiff` in each
  + >   # tab, and finally tell vim-gitgutter to show +/- for changes since REVIEW_BASE
  + >   review = !nvim -p $(git files) +\"tabdo Gvdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
  + >   vreview = !nvim -p $(git files) +\"tabdo Gvdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
  + >   hreview = !nvim -p $(git files) +\"tabdo Ghdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
  + >
  + >   # Same as the above, except specify names of files as arguments, instead of opening all files:
  + >   # (e.g. `$ git reviewone test1.rs test2.rs`)
  + >   reviewone = !nvim -p +\"tabdo Gdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"

Open diffs of all the changed files:

$ git review
    :tabnext
    :tabprev
    :tabclose
    :qa

Open diffs of the specified files:

$ git reviewone path/to/file.1 path/to/file.2
    :tabnext
    :tabprev
    :tabclose
    :qa

Troubleshooting

  • When cloning a repository, if the following error appears:
    fatal: the remote end hung up unexpectedly
    fatal: early EOF
    fatal: index-pack failed
    
    Then do the following (https://stackoverflow.com/questions/21277806/fatal-early-eof-fatal-index-pack-failed):
    $ git clone --depth 1 <repo_URI>
    $ cd <repo_name>
    $ git fetch --unshallow # or `$ git fetch --depth=2147483647`
    $ git pull --all
    $ vi .git/config
        > ...
      ~ > #fetch = +refs/heads/master:refs/remotes/origin/master
      + > fetch = +refs/heads/*:refs/remotes/origin/*
        > ...
    $ git fetch
    

Bonus

git lfs

install

# apk add git-lfs
# apt install git
# dnf install git
 # emerge --ask dev-vcs/git-lfs
# nix-env -iA nixos.git-lfs
# nix-env -iA nixpkgs.git-lfs
# pacman -S git-lfs
# xbps-install -S git-lfs

TODO

# zypper install git-lfs

config

$ git lfs install
    Git LFS initialized.

use

In each Git repository where you want to use Git LFS, select the file types you'd like Git LFS to manage (or directly edit your .gitattributes). You can configure additional file extensions at anytime. E.g. any *.psd file:

```console
$ git lfs track "*.psd"
```

Now make sure .gitattributes is tracked:

```console
$ git add .gitattributes
```

Note that defining the file types Git LFS should track will not, by itself, convert any pre-existing files to Git LFS, such as files on other branches or in your prior commit history. To do that, use the git lfs migrate command, which has a range of options designed to suit various potential use cases.

Finaly, just commit and push as you normally would; for instance, if your current branch is named main:

```console
$ git add file.psd
$ git commit -m "Add design file"
$ git push origin main
```

git annex

Monitoring

Games


If this cheat sheet has been useful to you, then please consider leaving a star here.