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
- Install
- Config
- Use
git config
.gitignore
git info exclude
git update-index
git init
git remote
git add
git stash
git commit
git revert
git reset
git restore
git clean
git fsck
git push
git pull
git branch
git rebase
git merge
git tag
git checkout
git switch
git cherry-pick
git diff
git difftool
git log
git show
git submodule
git filter-branch
andgit filter-repo
git blame
git sparse checkout
git hooks
git request-pull
- Pull/Merge request
- Server
- Misc
- How to fork a repository from one platform to another
- Code review with Git
- Troubleshooting
- Bonus
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¶
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 RSA key¶
Create an ssh
key pair if you don't have one:
When creating the key pair, if asked to enter a passphrase: no passphrase for git use is fine.
Copy the content of the public ssh
key to your clipboard
SSH in a GitLab project¶
Add the new RSA 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 RSA 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 RSA 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/user_name_git_pro_rsa
>
> Host github.com
> IdentityFile ~/.ssh/user_name_git_pro_rsa
>
> Host bitbucket.org
> IdentityFile ~/.ssh/user_name_git_pro_rsa
> ...
Or simpler:
$ vi ~/.ssh/config
> ...
> Host *
> IdentityFile ~/.ssh/user_name_git_pro_rsa
> IdentityFile ~/.ssh/user_name_git_perso_rsa
> ...
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.:
Or one might switch a pre-existing repository to use SSH instead of HTTPS:
Use¶
git config
¶
Get and set repository or global options.
Reference(s)
-
Print all of your settings and where they are coming from:
-
Set user name and email address globally:
-
Set user name and email address project wide:
-
Set default git editor:
-
Define a template message for git commits (project wide):
.gitignore
¶
Specifies intentionally untracked files to ignore.
-
Create a
.gitignore
file and edit it (don't forget to commit it and push it afterwards): -
List the files that are included in the "exclude lists" (based on
.gitignore
) -
Remove the files that are included in the "exclude lists" (based on
.gitignore
) from the repository (not from disk): -
Check if a file is ignored or not (based on
.gitignore
): -
If a previously tracked file (or folder) has been added to the
gitignore
file, then make sure to untrack it:
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).
But, if your local version differs from the remote one you will be notified with the following message: -
Cancel the previous command:
-
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.
- Cancel the previous command:
git init
¶
Create an empty Git repository or reinitialize an existing one.
Reference(s)
-
Init a git repository in an existing folder
my-project
: -
Init a git repository
my-project
from scratch:
git remote
¶
Manage set of tracked repositories.
Reference(s)
-
Show your remotes:
-
Change a remote URL:
-
Add a remote (e.g. named
test
): -
Remove a remote (e.g. named
bad-remote
): -
Pull all branches from all your remotes:
-
Pull all branches from all your remotes by default:
-
Pull a specific branch (e.g. "master") from a specific remote (e.g. "test"):
-
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
: -
Push all branches by default:
-
Push a specific branch (e.g. "master") to a specific remote (e.g. "test"):
-
Rename a remote:
git add
¶
Add file contents to the index.
Reference(s)
-
Stage all changes for commit:
-
Add a specific file to unstaged changes:
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)
-
Stash changes with a message:
-
List all the stashed changes:
-
Inspect the content of a stash:
-
Remove a stash:
-
Apply the most recent stash changes and remove it from the stash list:
-
Apply any stash from the list without removing the stash from the stash list:
git commit
¶
Record changes to the repository.
Reference(s)
-
Commit staged changes:
-
Edit previous commit message, if it hasn't been pushed already:
-
Git commit in the past:
-
Change the date of an existing commit:
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).
- Revert the project to a previous commit (e.g. with commit hash
0766c053
), with a single commit reverting all the changes:
git reset
¶
Reset current HEAD to the specified state.
Reference(s)
-
Remove/Undo "git add" before commit:
-
Removed staged and working directory changes:
-
Go 2 commits back:
-
Undo last commit (and never see it again):
-
Undo last commit (but it is preserved, one just go back of one commit):
git restore
¶
Restore working tree files.
Reference(s)
TODO
git clean
¶
Remove untracked files from the working tree.
Reference(s)
-
Remove untracked files:
-
Remove untracked and ignored files:
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:
-
Write dangling objects into
.git/lost-found/commit/
or.git/lost-found/other/
, depending on type:
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 push
¶
Update remote refs along with associated objects.
Reference(s)
-
Push to the tracked master branch:
-
Push to a specified repository:
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 branch
¶
List, create, or delete branches.
Reference(s)
- Create a new branch called
branch-name
, switch to it, add things and push it:
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: -
Completely delete the branch
branch-name
(locally and remotely, e.g. on remoteorigin
): -
Make an existing branch track a remote branch:
-
List all local and remote branches:
-
Print differences between the branch
master
and the branchbranch-name
: -
Print just which files differ, not how the content differ, between the branch
master
and the branchbranch-name
: -
Merge the branch
branch-name
intomaster
, and delete it afterwards (locally and remotely, e.g. from remoteorigin
):
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)
TODO
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.
-
List tags:
-
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
^{}
). -
Create tag (release point):
-
Show a tag (e.g.
v1.4
) in more details: -
Add a tag in the past (e.g. at commit
9fceb02
): -
By default,
$ git push
doesn't transfer tags to remote servers. Here is how to transfer them: -
If you don't want to transfer all tags to remote servers, but just a specific one (e.g.
v1.4
): -
Delete a tag (e.g.
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
): -
Move tag (e.g. move
v1.8
to current commit):
git checkout
¶
Switch branches or restore working tree files.
Reference(s)
-
Switch to tag/release point (e.g
v1.4
): -
Switch branch on a branch called "production":
-
Switch back to the master branch with the local changes made on "production": (this will switch branch and merge the local changes)
-
Restore the deleted file
hello.c
from the index: -
Reverts the
hello.c
file two revisions back on master branch: -
Create a bare new branch (one that has no commits on it):
-
Create a new branch from a different starting point, e.g. two commits behind:
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:
-
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:
-
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 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)
-
Diff staged files (after they have been "added"):
also--staged' is a synonym of
--cached`, so the following is valid: -
Diff one staged/cached files (after they have been "added"):
-
List files changed in
${commit_id}
: -
Check the changes between a local branch and it's remote branch:
-
difftastic
is a structural diff tool that compares files based on their syntax. It can be used withgit 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 replacediff
bydifftool
: -
difftastic
is a structural diff tool that compares files based on their syntax. It can be used withgit difftool
like explained here.
git log
¶
Show commit logs.
Reference(s)
-
Print a one liner of your current position:
-
Print commit history of a set of files:
-
View commits that will be pushed:
-
View changes that are new on a feature branch:
-
See everything you have done, across branches, in a glance:
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: -
List files changed in
${commit_id}
, pretty way, meant to be user facing: -
Show a tag (e.g.
v1.4
) in more details:
git submodule
¶
Initialize, update or inspect sub modules.
Reference(s)
-
Clone a repository including sub modules:
-
If you forgot the
--recurse-submodules
option when cloning a repository, you can fetch the missing sub modules with the following command: -
Update all your sub modules to latest tips of remote branches:
-
Add a sub module to your repository:
-
Add a sub module, in a specific branch, to your repository:
-
Print sub modules status:
-
Print remote/origin URLs (e.g. to ensure that a sub module is pointing to the right repository):
-
Remove a sub module:
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
$ 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
- Remove an unwanted file from the entire git repository:
- A fresh clone is needed:
- Remove the unwanted file:
- Add
origin
back (ifgit filter-repo
removed it): - ⚠️ Make sure to review carefully any modification before pushing:
- Push to
origin
(⚠️ double check previous step before pushing): - If previous step didn't worked (e.g. you failed to push because the
git
server rejected it), this might be because of a protected branch, see:- https://comp.umsl.edu/gitlab/help/user/project/protected_branches.md
- https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches
- https://confluence.atlassian.com/bitbucketserver/using-branch-permissions-776639807.html
TODO:
- https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html#DISCUSSION
- https://docs.gitlab.com/ee/user/project/repository/reducing_the_repo_size_using_git.html
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 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 hooks
¶
- Example of hooks:
.git/hooks
git request-pull
¶
Reference(s)
TODO
Pull/Merge request¶
Reference(s)
- https://docs.gitlab.com/ee/user/project/merge_requests/
- https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html
- https://docs.gitlab.com/ee/user/project/push_options.html
- https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests
- https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request
TODO
With 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):
A lot more usefull 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"
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:
-
Interactive rebase for the last 7 commits
-
Pull changes while overwriting any local commits:
-
Perform a shallow clone to only get latest commits (helps save data when cloning large repositories):
-
To unshallow a clone:
-
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:
-
The following can be used to prune all remotes at once:
-
Revert a commit and keep the history of the reverted change as a separate revert commit:
-
Move your most recent commit from one branch and stage it on TARGET branch:
How to fork a repository from one platform to another¶
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:
- One for our private repository on GitLab (will be the default one, called
origin
). - 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
): -
Rename the remote:
-
Create a new
blank
private project on GitLab (e.g.git@gitlab.com:whatever/private-repo.git
). -
Add the new origin to your repository:
-
Push the
master
branch to the private repository (you can push any other branch the same way): -
Push all tags to the private repository:
That's it!
-
To push to GitLab/master:
-
To retrieve updates from GitHub:
Code review with Git¶
Reference(s)
Navigate to the branch to review¶
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:
$ 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:
Visualize relationships between files¶
- E.g. in Python: https://medium.com/illumination/visualize-dependencies-between-python-modules-d6e8e9a92c50)
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:
Open diffs of the specified files:
Troubleshooting¶
- When cloning a repository, if the following error appears: Then do the following (https://stackoverflow.com/questions/21277806/fatal-early-eof-fatal-index-pack-failed):
Bonus¶
git lfs
¶
git annex
¶
Monitoring¶
- https://github.com/mvisonneau/gitlab-ci-pipelines-exporter
- https://blog.stephane-robert.info/post/gitlab-montoring-several-pipelines/
- https://about.gitlab.com/pricing/faq-consumption-cicd/
Games¶
- https://github.com/jsomers/git-game
- https://github.com/git-learning-game/oh-my-git
- https://ohmygit.org/
If this cheat sheet has been useful to you, then please consider leaving a star here.