Git is a version control system first developed by Linus Torvalds. It facilitates collaboration on large projects, keeps track of changes, and allows mistakes to be rolled back into a previous state.
Configure Git
Git uses a hierarchy of configuration files:
- System: (
/etc/gitconfig
) Configuration for all users in a system. - Global: (
~/.gitconfig
) Configure Git for all project of the current user. - Local: (
.git/config
) Configure Git for the current project. - Worktree: (
.git/config.worktree
) Configure part of a project.
# Check if user name and email are set.
git config --get user.name
git config --get user.email
# If not, set those values.
git config --add --global user.name "username"
git config --add --global user.email "email@example.com"
git config --add --global init.defaultBranch main # GitHub's default
git config --unset example.key # Remove a configuration value
git config --unset-all example.key # Remove all instances of a configuration key
git config --remove-section section # Remove an entire section
# Rebase on pull by default to keep a linear history
git config --global pull.rebase true
Create a Repository
Git stores all project information in the .git
directory. This includes branches, commits, and metadata.
mkdir project && cd project
git init
Status
A file can be in one of several states in a Git repository.
untracked
: Not being tracked by Gitstaged
: Marked to be included in the next commitcommitted
: Saved to the repository’s history
git status # Shows he state of a repository.
Staging
Untracked files need to be indexed before being committed.
git add filename.ext # Add a file
git add . # Add every file in the current directory, recursively.
Commit
A commit is a snapshot of the entire repository at a given point in time. Each commit has a message that describes the changes made in that commit.
To commit all staged files:
git commit -m "message" # Commit all staged files
git commit --amend -m "message" # Replace the last commit's message
Git Log
The git log
command shows the history of commits in a repository. It provides information on who made a commit, when it was made, and what files were changed.
Each commit also has a unique identifier (commit hash).
For example, this is a valid commit hash: 46a4b5904d4ad737447052fed90c754ce8c616b6
.
Since these identifiers are very long, they are often shortened to their first 7
characters (46a4b59
in this example).
git log # Show the log in an interactive pager
git --no-pager log -n 10 # Show the last 10 lines from the log, without using the pager
git log -1 # Fetch the header of the first commit
git log --decorate=full # Shows the full pointer to a commit
git log --decorate=no # Don't show branch names
git log --oneline # Show each commit in a single line. ("compact" mode)
git reflog # History of actions. Commits, clone, pull, etc.
Git Diff
Show differences:
git diff # Differences between the working tree and the last commit
git diff HEAD~1 # Same, but includes the last commit and uncommitted changes
git diff COMMIT_HASH_1 COMMIT_HASH_2 # Differences between two commits
Git Tags
A tag is a name linked to a commit that doesn’t move between commits. Useful to mark versions.
git tag # List the current tag
git tag -a "tag name" -m "tag message" # Add a new tag
git tag -a v3.10.2 -m "Fixed a lil bug" # Example marking a release
Semantic Versioning (Semver)
Naming convention for versioning software.
v3.12.5
| | |
| | Patch (safe bug fixes)
| Minor (safe features)
Major (breaking changes)
Branch
A Git branch allows you to keep track of different changes separately.
D - E other_branch
/
A - B - C main
git branch # Check which branches are available, and which one is selected
git branch -m oldname newname # Rename a branch
git branch my_new_branch # Create a new branch
git switch -c my_new_branch # Create a new branch and switch to it immediately
git switch branch_name # Switch to an existing branch
git checkout branch_name # Deprecated alternative to git switch
git branch -d branch_name # Delete a branch
Merge
After modifying a new branch, all commits performed on it can be merged into the main branch.
A - B - C - F main
\ /
D - E other_branch
git log --oneline --graph --all # Show an ASCII representation of the commit history
git log --oneline --decorate --graph --parents # Also display any parent branches
git merge branch_name # Merge a branch into the current branch
Rebase
After a branch is created, it might fall behind its origin. A rebase includes the new changes from the origin into the current branch, without leaving a merge message behind. While merging can often accomplish the same task, a rebase keeps history linear and makes it easier to read. It’s recommended to use it on the main branch, for example, when updating smaller ones.
Before a rebase, the commit history might have the following structure:
A - B - C main
\
D - E feature_branch
After a rebase, the target branch is updated against its origin:
A - B - C main
\
D - E feature_branch
git rebase origin_name # Rebase against the origin
You should never rebase a public branch (like
main
) onto anything else, to not break commit history.
Conflicts
If a modified is pushed to another branch where that same file has been modified as well, a conflict arises. These can be fixed manually by editing text, or using Git commands.
git checkout --theirs path/to/file # Discard the current branch's changes and accept the target's changes
git checkout --ours path/to/file # Discard the target changes and replace with the ones in the current branch
git reset --soft HEAD~1 # Undo an accidental conflict resolution
Squash
Combine various commits into one:
- Start an interactive rebase with the command
git rebase -i HEAD~n
, wheren
is the number of commits you want to squash. - Git will open your default editor with a list of commits. Change the word
pick
tosquash
for all but the first commit. - Save and close the editor.
If the squashed commits already existed in the remote repository, it might be needed to push using:
git push origin main --force
.
Stash
git stash
saves the state of the current working directory and the index (staging area), then returns the repository to HEAD
. This allows you to work on a different issue, and then resume your previous task.
git stash
git stash -m "message" # It's also possible to stash with a message
git stash list # List all stashes
git stash pop # Apply the most recent stash to the working directory
git stash apply # Same as before, but doesn't delete the stash after applying
git stash drop # Discard a stash without applying changes
git stash apply stash@{2} # Apply the third most recent stash
Worktrees
The directory where the code tracked with Git lives. (And where the .git
directory is located).
Use instead of a stash
if the current worktree is too busy, or for long-lived changes. This acts like cloning the repo again and working there, except it doesn’t take up space on the host machine.
Any change done on a linked worktree is reflected on the main one instantly. Think of it as a different view of the main worktree.
git worktree list # Lists worktrees
git worktree add <path> [<branch>] # Create a worktree linked to the main one
git worktree remove WORKTREE_NAME # Remove a worktree
git worktree prune # Remove an empty worktree if its directories were removed
Bisect
Find a specific commit with binary tree search.
For example, if trying to find a bug in 100 commits, git bisect
allows it to be found with only 7
attempts.
git bisect start
- Use:
git bisect good <commitish>
to select a commit where the bug wasn’t introduced yet. - Select a commit where the bug exists using:
git bisect bad <commitish>
. - Git will checkout a commit between the good and bad commits for you to test to see if the bug is present.
- Execute
git bisect good
orgit bisect bad
. - Loop back to step 4 (until
git bisect
completes) - Exit the bisect mode with
git bisect reset
git bisect start
git bisect good <commitish> # Select a commit where the bug wasn't introduced yet.
git bisect bad <commitish> # Select a commit where the bug exists
Automated Bisect
If you have a script that can tell if the current source code is good or bad, you can bisect by issuing the command:
git bisect run <script> <arguments>
The script should exit with code 0
if the current source code is good/old, and exit with a code between 1
and 127
(inclusive), except 125
, if the current source code is bad/new.
Cherry-Pick
Apply only the selected commit to the working directory.
git cherry-pick <commit-hash>
Undo Changes
git reset --soft COMMITHASH # Undo the last commit, but keep its changes staged (does not delete files)
git reset --hard COMMITHASH # Undo the last commit and discard all changes (deletes files).
git reset --hard a1b2c3d # Rollback to an earlier commit, deleting all changes up to that point.
git revert COMMITHASH # Create a new commit that does the opposite of the one provided. (Reset, but keeps history)
Recover a Deleted Commit
HEAD
always keeps track of every change, including rollbacks, so it can be used to recover lost files.
git merge HEAD@{1}
Git Remote
‘Remotes’ are eternal repositories with a similar Git history to our local one. GitHub, for example, is a remote repository. It is not part of Git, but is often used as the “source of truth” for convenience.
If a repository is considered to be the project’s “true” source, it should be named origin
.
git remote add <name> <uri> # Add a remote repository (local folder or external url)
git fetch # Download a copy of the origin's metadata (.git/objects)
git log remote/branch # See the log of a remote branch after fetching data
git merge remote/branch # Merge between local and remote repos
git push origin main # Push (send) local changes to the selected remote
git push origin <localbranch>:<remotebranch> # Push a local branch to a remote, with a different name
git push origin :<remotebranch> # Push an empty branch to delete the remote branch
git pull [<remote>/<branch>] # Update local repo with remote changes (downloads files).
Pull Requests
Propose changes to a repository, before they are actually applies. A pull request is typically accepted by a maintainer or other team members.
GitHub
GitHub serves several purposes:
- As a backup of all your code on the cloud in case something happens to your computer
- As a central place to share your code and collaborate on it with others
- As a public portfolio for your coding projects
# Install GitHub CLI either through a package manager, or using the command:
curl -sS https://webi.sh/gh | sh
# Login through the browser
gh auth login
# Add a remote from GitHub
git remote add origin https://github.com/your-username/repo-name.git
# List remote repos
git ls-remote
Forks
On GitHub (and similar platforms), a repository can be forked, or, copied, to serve as the base for a future pull request.
The steps to submit a PR are usually as follows:
- Fork their repo into your account
- Clone your fork to your local machine
- Create a new branch (let’s call it
your_feature
) - Make changes
- Commit and push changes to your fork’s remote
your_feature
branch - Create a pull request to
original_owner/repo
main
fromyour_username/repo
your_feature
.gitignore
Prevents the specified files from being tracked by git.
folder_name # Ignores all directories with that name, even subdirectories of different directories
file.txt # Ignores a specific file in the current directory
folder/file.txt # Ignores a file inside a subdirectory
*.txt # Ignore all text files
/main.py # Ignore a file only in the current directory, not subdirectories
!important.txt # Track a file that would previously be ignored
If a file was already staged or committed, it won’t be ignored, however. Use the following command to remove it from cache and ignore it on the next commit:
git rm --cached file
It’s common to have
.gitignore
files in subdirectories. These only affect the directories they are inside of.
Which files should be ignored?
- Ignore things that can be generated (e.g. compiled code, minified files, etc.)
- Ignore dependencies (e.g.
node_modules
,venv
,packages
, etc.) - Ignore things that are personal or specific to how you like to work (e.g. editor settings)
- Ignore things that are sensitive or dangerous (e.g.
.env
files, passwords, API keys, etc.)