Git Introduction
Git stores data as snapshots of the project over time. Everything in Git is referred to by a SHA-1 hash checksum. Git has three main states of your files:
- Modified (file changed but not committed to the database yet).
- Staged (file marked as modified in its current version to go into your next commit snapshot).
- Committed (file is safely stored in your local database).
Git setup
When you customize git environment, git stores configuration variables in three places:
- System variables set with
--system
option are stored in/etc/gitconfig
file. - User variables set with
--global
option are stored in~/.gitconfig
file. - Repository variables set with
--local
option (default) are stored in.git/config
file (relative to your specific repository).
View Configuration
View all of your settings:
git config --list --show-origin
Check specific key's value in git config:
git config user.name
Configure User
Set up your username and email address:
git config --global user.name "John Doe"
git config --global user.email [email protected]
Configure Editor
Configure your default text editor:
git config --global core.editor nano
Configure Default Branch Name
Set main
as the default branch name:
git config --global init.defaultBranch main
Getting Help
Get help while using Git:
git help <verb>
git <verb> help
man git-<verb>
git <verb> -h
Git Basics
Initializing a Repository
Initialize Git in the current directory:
git init
Begin tracking files and do an initial commit:
git add *.c
git add LICENSE
git commit -m 'Initial commit'
Clone an existing repository:
git clone https://github.com/libgit2/libgit2
Clone a repository and specify the new directory name for it:
git clone https://github.com/libgit2/libgit2 mylibgit
Staging Modified Files
Check the status of your files:
git status
Add a file to the staging area:
git add README
Get a short output of the status command:
git status -s
git status --short
The first column indicates the status of the staging area, and the right column indicates the status of the working tree. Untracked files are marked with ??
, new files added to the staging area have an A
, and modified files have an M
next to them.
Ignoring Files
Ignore files that Git should not add or show as being untracked by creating a .gitignore
file in the repository directory:
# ignore all .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in any directory named build
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory and any of its subdirectories
doc/**/*.pdf
For more examples view a collection of .gitignore
templates.
The rules for the patterns in .gitignore
file:
- Blank lines of lines starting with
#
are ignored. - Standard glob patterns work and are applied recursively.
- Avoid recursivity by starting patterns with a forward slash
/
. - Specify a directory by ending patterns with a forward slash
/
. - Negate a pattern with an exclamation point
!
.
Viewing Staged and Unstaged Changes
Compare what is in your working directory with what is in your staging area:
git diff
Compare changes that you've staged with your last commit:
git diff --staged
git diff --cached
View options for external diff viewing program on your system:
git difftool --tool-help
Committing Changes
Commit the staged changes:
git commit
Commit and add the diff of your change in the commit message:
git commit -v
Type your commit message inline:
git commit -m "Story 123: fix user signup form validation"
Best Practice for Commit Messages
A good commit message consists of three parts:
- Commit Title (First Line)
- Keep it short (50-72 characters).
- Use imperative mood (as if giving a command).
- Start with a concise summary of the change.
- Commit Description (Optional, Multi-line)
- Explain why the change was made, not just what changed.
- Provide context if it's not obvious from the code.
- If fixing a bug, mention the issue number: Fixes #123
- Use bullet points for clarity in complex changes.
- Footer (Optional)
- Reference relevant tickets, issues, or breaking changes.
Example of a single-line commit (small changes):
fix: Correct typo in error message
Example of a multi-line commit (more details):
feat: Add user authentication using JWT
- Implement JWT-based authentication for API access
- Add login and logout routes
- Store tokens securely in HTTP-only cookies
- Fixes #45
Example of a commit with a breaking change:
refactor: Update user table schema
- Remove `username` field, use `email` as the unique identifier
- Migrate old data to reflect changes
- Update related authentication methods
BREAKING CHANGE: Existing usernames will no longer work as login credentials.
Using prefixes helps categorize commits systematically:
Prefix | Meaning |
---|---|
feat | Introduces a new feature |
fix | Fixes a bug |
docs | Updates documentation only |
style | Formatting, whitespace, linting (no logic changes) |
refactor | Code refactoring (no functional changes) |
perf | Performance improvements |
test | Adds or updates tests |
chore | Maintenance tasks (build scripts, dependencies) |
More information: Git Guidelines, A Note About Git Commit Messages, Git project.
Skipping the Staging Area
Skip the git add
part and automatically stage every file that is already tracked before doing the commit:
git commit -a -m 'Add new benchmarks'
Removing Files
Remove a file from your staging area and also from your working directory:
git rm README
Remove a file that you have already modified or added to staging area:
git rm -f README
Remove a file from your staging area but keep it in your working tree:
git rm --cached README
Remove files using glob patterns (note the use of backslash \
to prevent shell's filename expansion):
git rm log/\*.log
Moving Files
Rename a file in Git:
git mv file_from file_to
The above command is equivalent to:
mv file_from file_to
git rm file_from
git add file_to
Viewing the Commit History
List the commits in reverse chronological order:
git log
List the last two commits with patch output (differences) introduced in each commit:
git log -p -2
git log --patch -2
List commits with abbreviated stats:
git log --stat
List commits with one line output:
git log --pretty=oneline
Specify the format explicitly:
git log --pretty=format:"%h - %an, %ar : %s"
Useful specifiers for git log --pretty=format
:
Specifier | Description of Output |
---|---|
%H | Commit hash |
%h | Abbreviated commit hash |
%T | Tree hash |
%T | Tree hash |
%t | Abbreviated tree hash |
%P | Parent hashes |
%p | Abbreviated parent hashes |
%an | Author name |
%ae | Author email |
%ad | Author date (format respects the --date=option) |
%ar | Author date, relative |
%cn | Committer name |
%ce | Committer email |
%cd | Committer date |
%cr | Committer date, relative |
%s | Subject |
Add ASCII graph while showing branch and merge history:
git log --pretty=format:"%h %s" --graph
Common options to git log
:
Option | Description |
---|---|
-p |
Show the patch introduced with each commit |
--stat |
Show statistics for files modified in each commit |
--shortstat |
Display only the changed/insertions/deletions line from the --stat command |
--name-only |
Show the list of files modified after the commit information |
--name-status |
Show the list of files affected with added/modified/deleted information as well |
--abbrev-commit |
Show only the first few characters of the SHA-1 checksum instead of all 40 |
--relative-date |
Display the date in a relative format (for example, “2 weeks ago”) instead of using the full date format |
--graph |
Display an ASCII graph of the branch and merge history beside the log output |
--pretty |
Show commits in an alternate format. Option values include oneline , short , full , fuller , and format (where you specify your own format) |
--oneline |
Shorthand for --pretty=oneline --abbrev-commit used together |
View a specific commit:
git show commit_hash
Display only those commits that are on the latter branch and that are not on the first branch:
git log --no-merges issue54..origin/master
Limiting Log Output
List the last n
commits:
git log -10
List the commits made in the last two weeks:
git log --since=2.weeks
List the commits matching --author
and --grep
criteria:
git log --author="John Doe" --grep="fix"
List the commits that match the output of all --grep
patterns:
git log --grep="fix" --grep="issues" --all-match
List all commits that changed the number of occurrences of a given string:
git log -s function_name
List commits that introduced a change to files specified by a path:
git log -- path/to/file
Options to limit the output of git log
:
Option | Description |
---|---|
-<n> |
Show only the last n commits |
--since , --after |
Limit the commits to those made after the specified date |
--until , --before |
Limit the commits to those made before the specified date |
--author |
Only show commits in which the author entry matches the specified string |
--committer |
Only show commits in which the committer entry matches the specified string |
--grep |
Only show commits with a commit message containing the string |
-S |
Only show commits adding or removing code matching the string |
--no-merges |
Prevent the display of merge commits in log history |
Undoing Things
Modify the last commit with additional changes from your current staging area and a new commit message:
git commit --amend
Unstage a staged file (either of the following):
git reset HEAD file_name
git restore --staged file_name
Revert a file back to the last staged or committed version (either of the following):
git checkout -- file_name
git restore file_name
Working with Remotes
List remote servers you have configured:
git remote -v
Add a new remote to Git repository:
git remote add remote_name https://github.com/user_name/repository_name
Get data from a remote (but not merge it yet):
git fetch remote_name
Fetch data from the server and automatically merge:
git pull
Set default behavior of git pull
to fast-forward if possible, else create a merge commit:
git config --global pull.rebase "false"
Set default behavior of git pull
to fast-forward if possible, else rebase:
git config --global pull.rebase "true"
Push your work on the master
branch to your origin
server:
git push origin master
Inspect a remote:
git remote show origin
Rename a remote:
git remote rename old_name new_name
Remove a remote:
git remote remove remote_name
Tagging
List the existing tags:
git tag
Filter existing tags:
git tag -l "v1.8.2*"
Create an annotated tag:
git tag -a v0.1.0 -m "Release version 0.1.0"
Create a lightweight tag
git tag v0.1.1
Show the tag data:
git show v0.1.0
Create a tag for a specified commit hash:
git tag -a v0.0.1 ca82a6d
Share a tag with a remote:
git push origin v0.1.0
Push all the tags to a remote:
git push origin --tags
Push only annotated tags to a remote:
git push origin -- follow-tags
Delete a tag locally:
git tag -d v0.1.0
Delete tag on a remote:
git push origin --delete tag_name
Check out a tag to view the version of files a tag is pointing to:
git checkout v0.2.0
Check out a tag and create a new branch:
git checkout -b v0.2.0
Git Aliases
Set up Git aliases:
git config --global alias.unstage 'restore --staged'
git config --global alias.last 'log -1 -p HEAD'
git config --global alias.ls 'log --graph --decorate --oneline'
git config --global alias.la 'log --graph --pretty=format:"%h - %an, %ar : %s%d"'
git config --global alias.visual '!gitk'
Check alias definitions:
git config --global --list
Remove Git alias:
git config --global --unset alias.alias_name
Git Branching
A branch in Git is a lightweight movable pointer to one of the commits. The default branch name in Git is master
. As you start making commits, the pointer refers to the last commit that you've made while you are checked out in that branch.
Create a new branch:
git branch branch_name
Switch to an existing branch (either of the following commands):
git checkout branch_name
git switch branch_name
When you checkout
to another branch, Git moves the HEAD
pointer to refer to that branch_name
and replaces files in your working directory to the snapshot the branch points to.
Show commit history for the desired branch:
git log branch_name
Show commit history for all of the branches:
git log --all
Create a new branch and switch to it at the same time (either of the following):
git checkout -b branch_name
git switch -c branch_name
git switch --create branch_name
Return to previously checked out branch:
git switch -
Delete a branch:
git branch -d branch_name
Merging
Merge the finished work from iss23
branch into the master
branch:
git switch master
git merge iss23
When there are conflicts in the merge, first check which files are unmerged:
git status
Then open those files listed under unmerged paths section and resolve those conflicts manually:
<<<<<<< HEAD
<div>contact us: [email protected]</div>
=======
<footer>
Please contact us at [email protected]
</footer>
>>>>>>> iss23
Alternatively, use a graphical tool to resolve merge issues:
git mergetool
Set up Meld to be the default diff and merge tool:
git config --global diff.tool meld
git config --global merge.tool meld
git config --global difftool.prompt false
git config --global mergetool.prompt false
git config --global mergetool.keepBackup false
Then stage and commit the changes to finalize the merge:
git commit -a
After merge is done, close the issue in your issue-tracking system and delete the iss23
branch :
git branch -d iss23
Branch Management
List all local branches:
git branch
See the last commit on each branch:
git branch -v
List branches that are already merged into the current branch:
git branch --merged
Branches on this list without the *
in front of them are ready to be deleted because you have already merged them into the current branch.
List all the branches that contain work you have not yet merged into the current branch:
git branch --no-merged
Because such branches contain work that isn't merged in yet, trying to delete it will fail.
You can specify the branch name or commit as an argument to ask about the merge state of it:
git branch --merged branch_name
git branch --no-merged branch_name
Rename a branch locally:
git branch --move old_branch_name new_branch_name
Push the branch name change to a remote:
git push --set-upstream origin new_branch_name
List all local and remote branches:
git branch -all
Delete the old branch from a remote:
git push origin --delete old_branch_name
Show branch name in Ubuntu command prompt by modifying the file ~/.bashrc
with the following content:
# Add git branch if its present to PS1
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[01;31m\]$(parse_git_branch)\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w$(parse_git_branch)\$ '
fi
Push a local branch to the remote origin
with the same branch name:
git push origin local_branch_name
Push a local branch to the remote origin
that is named differently:
git push origin local_branch_name:remote_branch_name
Tracking Branches
When someone creates a new branch and pushes it to a remote, fetching data from the remote does not create a new local branch. Tracking branches are local branches that have a direct relationship to a remote branch, such that when you run git pull
, Git knows which server to fetch and which branch to merge in. You can create a tracking branch by running:
git fetch origin
git switch -c branch_name --track origin/branch_name
If the local branch name is the same as the remote one, you can use a shortcut:
git switch -t origin/branch_name
If the branch name you are trying to switch (a) doesn't exist and (b) exactly matches a name on only one remote, Git will create a tracking branch for you automatically:
git switch branch_name
Change the upstream branch for the local branch that you are tracking:
git branch -u origin/branch_name local_branch_name
List branches with information about tracking branches:
git branch -vv
Fetch from all remotes and list up-to-date information about branches:
git fetch --all; git branch -vv
Branch Naming Best Practice
- When working on a new feature, you can name the branch with a prefix like
feature/
followed by a short description of the feature or the user story ID:feature/user-authentication
feature/us1234-add-login-page
- For bug fixes, use a prefix like
bugfix/
orfix/
followed by a brief description or the bug ID:bugfix/fix-login-error
fix/bug5678-crash-on-startup
- For urgent fixes that need to be applied to the production code, use a prefix like
hotfix/
:hotfix/critical-security-patch
- When preparing for a release, you might use a prefix like
release/
followed by the version number:release/1.0.0
- Use hyphens to separate words in branch names for better readability.
- While being descriptive, try to keep branch names concise to avoid overly long names.
- Stick to lowercase letters to avoid case sensitivity issues.
Git On the Server
Multi-User Setup
Export an existing repository into a new bare repository:
git clone --bare my_project my_project.git
Put the bare repository on the server:
scp -r my_project.git [email protected]:/srv/git
Other users can clone the repository if they have SSH access:
git clone [email protected]:/srv/git/my_project.git
Let Git add group write permissions to the created bare repository:
ssh [email protected]
cd /srv/git/my_project.git
git init --bare --shared
Single User Setup
On a user's machine, generate a new SSH key if you don't have one:
ssh-keygen -t ed25519 -C "[email protected]"
Start ssh-agent in the background to prevent having to enter the password each time:
eval "$(ssh-agent -s)"
Add SSH private key to the ssh-agent:
ssh-add ~/.ssh/id_ed25519
On a remote machine, create a git
user account and a .ssh
directory for that user:
sudo adduser git
su git
cd
mkdir .ssh && chmod 700 .ssh
touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys
Append user's public key:
cat /tmp/id_ed25519.johndoe.pub >> ~/.ssh/authorized_keys
Set up an empty bare repository:
cd /srv/git
mkdir project.git
cd project.git
git init --bare
On a user's machine, initialize a new repository:
cd myproject
git init
git add .
git commit -m 'Initial commit'
git remote add origin [email protected]:/srv/git/project.git
git push origin master
Restrict the shell of the git
user account to only Git-related activities:
cat /etc/shells # see if git-shell is already in there. If not...
which git-shell # make sure git-shell is installed on your system.
sudo -e /etc/shells # and add the path to git-shell from last command
Change git
user's shell:
sudo chsh git -s $(which git-shell)
Prevent SSH port forwarding by prepending the authorized_keys
file with the following options:
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
The result should look like this:
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAABAQDEwENNMomTboYI+LJieaAY16qiXiH3wuvENhBG...
You can learn more how to further customize git-shell
:
git help shell
Distributed Git
Commit Guidelines
Confirm for whitespace errors before your commit:
git diff --check
Make each commit a logically separate unit. Split your work into at least one commit per issue, with a useful message per commit. If some of the changes modify the same file, use git add --patch
to partially stage files.
Workflow
Create a new branch and push it to a remote while tracking it:
git switch -c featureA
vim lib/simplegit.rb
git commit -am 'Add limit to log function'
git push -u origin featureA
Create a new branch based on the origin/master
branch:
git fetch origin
git checkout -b featureB origin/master
vim lib/simplegit.rb
git commit -am 'Add ls-files'
Push your work from local featureB
branch to the remote origin/featureBee
branch while tracking the local featureB
branch with the remote origin/featureBee
branch:
git fetch origin
git merge origin/featureBee
git push -u origin featureB:featureBee
Inspect the work from a collaborator and merge it into your local branch:
git fetch origin
git log featureA..origin/featureA
git checkout featureA
git merge origin/featureA
Make some changes in featureA
branch and push the work to the remote:
git commit -am 'Add small tweak to merged content'
git push
Git Tools
Revision Selection
Locate a specific commit you are interested in and refer to it by a partial hash that begins with at least four characters:
git log
git show 1c00
To examine the last commit object on a branch, simple refer to the branch name:
git log
git show topic1
Identify which specific SHA-1 a branch points to:
git rev-parse topic1
Every time your branch is updated, Git stores that information in a temporary history (for a few months), which you can view:
git reflog
View the third prior value of the HEAD
of your repository:
git show HEAD@{3}
Show where your master
branch was yesterday:
git show master@{yesterday}
View the reflog
information formatted like the git log
output:
git log -g master
Show a parent of a commit by using a ^
caret:
git show HEAD^
Show the first parent (from the branch you were on when you merged) and the second parent (from the branch that was merged) of a merge commit:
git show ac8b8d3^
git show ac8b8d3^2
Show the first parent of the first parent commit:
git show HEAD~2
Show the second parent of a third-degree ancestor:
git show 12f6403~3^2
Show all commits in experiment
branch that are not in master
branch:
git log master..experiment
Show all commits that you are about to push to a remote:
git log origin/master..HEAD
If you leave off HEAD
, Git will assume HEAD
:
git log origin/master..
Show all commits that are reachable from refA
or refB
, but not from refC
:
git log refA refB ^refC
All the following commands are equivalent:
git log refA..refB
git log ^refA refB
git log refB --not refA
Show all commits that are reachable by either of two references but not by both of them with side indication:
git log --left-right master...experiment
Interactive Staging
Enter an interactive shell mode:
git add -i
To start the interactive add mode to do the partial-file staging you can also use either:
git add -p
git add --patch
Start the interactive mode for partially resetting files:
git reset --patch
Partially checking out files:
git checkout --patch
Partially stashing parts of files:
git stash save --patch
Stashing and Cleaning
Push a new stash onto your stack:
git stash push
List the stored stashes:
git stash list
Apply previously pushed stash:
git stash apply
Apply a specific stash:
git stash apply stash@{2}
Apply the stash and stage the changes:
git stash apply --index
Remove a specific stash:
git stash drop stash@{0}
Apply the stash and remove it from your stack:
git stash pop
Save all your work in the stash (including staged), but also leave the staged work in the index:
git stash --keep-index
By default, git stash
will stash only modified and staged tracked files. If you specify --include-untracked
or -u
, Git will include untracked files in the stash:
git stash -u
Stash your work interactively:
git stash --patch
Create a branch from a stash:
git stash branch new_branch_name
Save all the files, including untracked and ignored files in a stash stack:
git stash --all
Remove all untracked files that are not ignored and subdirectories that become empty as a result:
git clean -f -d
Dry run of the previous command with either:
git clean -f -d --dry-run
git clean -f -d -n
Remove all ignored files too:
git clean -d -x
Remove files in an interactive mode:
git clean -x -i