Getting Started

Installation and initial setup

Hello, I am Gitou, and I will guide you through your discovery of Git, an exceptional project management tool for macOS and Windows.
The first thing to do after installing Git is to set your identity. This information will be attached to all your commits.
# Set name and email (once per machine)
$ git config --global user.name "John Doe"
$ git config --global user.email john@example.com

# Set the text editor (e.g. nano, vim, notepad++)
$ git config --global core.editor nano

# Set the default branch name (Git 2.28+)
$ git config --global init.defaultBranch main

# Check all settings and their sources
$ git config --list --show-origin

# Check a specific value
$ git config user.name
John Doe
The --global flag writes to ~/.gitconfig and applies to all your repositories. Without a level flag, Git writes to .git/config in the current repository. Note: the .git folder is and must remain hidden.

Create or clone a repository

Two entry points: initialise a repository in an existing directory, or clone a remote repository from the GitHub website (https://github.com/).
# Initialise a repository in the current directory
$ cd my_project
$ git init
Initialized empty Git repository in /home/user/my_project/.git/

# Add existing files and make the first commit
$ git add .
$ git commit -m "initial commit"

# Clone a remote repository (creates the libgit2/ directory)
$ git clone https://github.com/libgit2/libgit2

# Clone into a differently named directory
$ git clone https://github.com/libgit2/libgit2 mylibgit2

# Create a bare repository (for a server)
$ git clone --bare my_project my_project.git
Day-to-day work

Check the working tree status

# Full output (verbose)
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
  new file:   README
Changes not staged for commit:
  modified:   CONTRIBUTING.md
Untracked files:
  notes.txt

# Short format: two columns (index / working tree)
$ git status -s
A  README
 M CONTRIBUTING.md
?? notes.txt
Short format legend: A = new staged file, M = modified, ?? = untracked. Left column = index, right column = working tree.

Stage changes (git add)

# Stage a specific file
$ git add README

# Stage all modified and new files in the tree
$ git add .

# Stage by glob pattern
$ git add *.c

# Interactive hunk-by-hunk staging (partial selection)
$ git add -p file.py
@@ -10,6 +10,8 @@ def process():
...
Stage this hunk [y,n,q,a,d,/,e,?]?
If you modify a file after git add, you must run git add again so that the new changes are included in the next commit. Git stages the state of the file at the time of the call.

Inspect differences (git diff)

# Show UNSTAGED changes (working tree vs index)
$ git diff

# Show STAGED changes (index vs last commit)
$ git diff --staged

# Check for whitespace errors before committing
$ git diff --check

# Diff between two commits
$ git diff abc1234 def5678

# Statistical summary
$ git diff --stat HEAD~3

# Use an external graphical tool
$ git difftool

Commit changes (git commit)

# Commit by opening the editor for the message
$ git commit

# Commit with an inline message
$ git commit -m "Fix subnet calculation bug"
[master 463dc4f] Fix subnet calculation bug
 2 files changed, 4 insertions(+), 1 deletion(-)

# Auto-stage tracked files AND commit
$ git commit -a -m "Update network module"

# Include the diff in the editor for reference
$ git commit -v
Best practice: short first line (50 chars max), blank line, then detailed description. Use the imperative mood: "Fix…", "Add…", "Remove…".

Browse history (git log)

# Full history (reverse order)
$ git log

# One line per commit
$ git log --oneline

# Branch graph with decoration
$ git log --oneline --decorate --graph --all

# Show the last 2 commits with their diff
$ git log -p -2

# Statistics per commit
$ git log --stat

# Custom format: short SHA, author, relative date, title
$ git log --pretty=format:"%h - %an, %ar : %s"

# Filter by author, last 2 weeks, no merges
$ git log --author="John" --since="2.weeks" --no-merges

# Search commits that added or removed a string
$ git log -S "function_name"

# Limit to commits modifying a specific file
$ git log -- src/network.py

Remove and move files

# Remove a file and stage the removal
$ git rm PROJECTS.md

# Force removal of a modified or staged file
$ git rm -f modified_file.txt

# Remove from index only, keep in working tree
# (useful for a file accidentally omitted from .gitignore)
$ git rm --cached log/app.log

# Remove by pattern (escape * to prevent shell expansion)
$ git rm log/\*.log

# Rename / move a file
$ git mv old_name.py new_name.py
Undoing

Amend the last commit (--amend)

# Amend the last commit message
$ git commit --amend

# Add a forgotten file to the last commit
$ git add forgotten_file.py
$ git commit --amend --no-edit
# --no-edit keeps the existing message
Never use --amend on a commit already pushed to a shared repository. It rewrites the SHA-1 and forces collaborators to re-merge their work.

Unstage and reset files (git restore)

# Unstage a file (remove it from the staging area)
$ git restore --staged CONTRIBUTING.md

# Discard changes to a file in the working tree
# (resets to the last commit state)
$ git restore CONTRIBUTING.md
git restore <file> permanently discards uncommitted local changes. Only use this when you are absolutely sure.
On versions prior to Git 2.25, use: git reset HEAD <file> to unstage, and git checkout -- <file> to reset the working tree.

git reset — move HEAD and modify state

# --soft mode: undo commit but keep index and working tree
# (changes remain staged, ready to re-commit)
$ git reset --soft HEAD~1

# --mixed mode (default): undo commit and unstage
# (changes remain in working tree, unstaged)
$ git reset HEAD~1

# --hard mode: undo commit, unstage AND reset working tree
# (DESTROYS uncommitted changes)
$ git reset --hard HEAD~1

# Unstage a specific file (old syntax)
$ git reset HEAD CONTRIBUTING.md
Typical scenario You committed three debug commits that you want to squash into one clean commit before pushing:
git reset --soft HEAD~3 — the three commits vanish, but all changes remain staged. You can then run git commit -m "Full implementation of module X".
Branches

Create and switch between branches

# List local branches
$ git branch
  iss53
* master
  testing

# Create a branch without switching to it
$ git branch new-feature

# Create AND switch to the new branch (classic method)
$ git checkout -b new-feature

# Same with git switch (Git 2.23+)
$ git switch -c new-feature

# Switch to an existing branch
$ git checkout master
$ git switch master

# Return to the previous branch
$ git switch -

# Delete a merged branch
$ git branch -d hotfix

# Rename a local branch
$ git branch --move old-name new-name

# See all branches with last commit and upstream
$ git branch -vv

# See branches not yet merged
$ git branch --no-merged

Merge branches (git merge)

# Merge hotfix into master (fast-forward if possible)
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

# Merge iss53 into master (creates a 3-way merge commit)
$ git merge iss53
Merge made by the 'recursive' strategy.
 README | 1 +
 1 file changed, 1 insertion(+)

# Force a merge commit even when fast-forward would be possible
$ git merge --no-ff hotfix

# Abort an in-progress merge (when conflict is unmanageable)
$ git merge --abort

Resolve merge conflicts

When two branches have modified the same area of a file, Git cannot merge automatically. The conflicted file contains markers that you must resolve manually.
# See which files are in conflict
$ git status
Unmerged paths:
  both modified: index.html
The conflicted file contains the following markers:
<<<<<<< HEAD
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
  please contact us at support@github.com
</div>
>>>>>>> iss53
Edit the file, remove the markers and keep the desired version (or combine both). Then:
# Mark the conflict as resolved
$ git add index.html

# Finalise the merge commit
$ git commit

# Alternative: use a graphical merge tool
$ git mergetool

Rebase a branch (git rebase)

# Rebase the experiment branch onto master (linear history)
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

# Then fast-forward master
$ git checkout master
$ git merge experiment

# Rebase client onto master without touching server
# (transplant only the commits unique to client)
$ git rebase --onto master server client

# Interactive rebase: rewrite the last 3 commits
$ git rebase -i HEAD~3
# The editor opens with the commit list
# Actions: pick, reword, edit, squash, fixup, drop

# In case of conflict during a rebase
$ git add resolved_file.py
$ git rebase --continue
# Or abort completely
$ git rebase --abort
Only rebase local commits that have not yet been shared. Rebasing published commits forces your collaborators to re-merge their work.

Stash work in progress (git stash)

# Stash current changes (working tree + index)
$ git stash
Saved working directory and index state WIP on master: 049d078 added the index file

# List stashes
$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"

# Re-apply the most recent stash and remove it from the stack
$ git stash pop

# Re-apply a specific stash without removing it
$ git stash apply stash@{1}

# Create a branch from a stash
$ git stash branch testbranch

# Drop a stash
$ git stash drop stash@{0}
Remote repositories

Manage remote repositories (git remote)

# List remote repositories
$ git remote
origin

# List with URLs
$ git remote -v
origin  https://github.com/user/project (fetch)
origin  https://github.com/user/project (push)

# Add a remote repository
$ git remote add pb https://github.com/paulboone/ticgit

# Show details of a remote repository
$ git remote show origin

# Rename a remote repository
$ git remote rename pb paul

# Remove a remote repository
$ git remote remove paul

Fetch from a remote repository (fetch / pull)

# Fetch all new data from origin
# (updates origin/master etc. but does NOT merge)
$ git fetch origin

# Fetch from all remotes
$ git fetch --all

# Fetch from a specific non-origin remote
$ git fetch teamone

# Fetch AND merge into the current branch (fetch + merge)
$ git pull

# Fetch and rebase instead of merging
$ git pull --rebase

# Set pull.rebase as default
$ git config --global pull.rebase true

Push to a remote repository (git push)

# Push the master branch to origin
$ git push origin master

# Push AND set the remote branch as upstream
$ git push -u origin my-branch

# Push with a local:remote refspec
$ git push origin featureB:featureBee

# Push all annotated tags
$ git push origin --follow-tags

# Delete a remote branch
$ git push origin --delete stale-branch

# Push is rejected if the remote history has diverged
! [rejected] master -> master (non-fast forward)
# Solution: fetch and merge first
$ git pull
$ git push origin master

Remote tracking branches

# Create a local branch that tracks a remote branch
$ git checkout -b serverfix origin/serverfix
# Shorthand:
$ git checkout --track origin/serverfix
# Even shorter (if the name is unique):
$ git checkout serverfix

# Associate an existing local branch with a remote branch
$ git branch -u origin/serverfix

# See the status of all tracking branches
$ git branch -vv
  iss53       7e424c3 [origin/iss53: ahead 2] forgot the brackets
* master      1ae2a45 [origin/master] deploying index fix

# Delete a remote branch
$ git push origin --delete serverfix
Tags

Create, list and share tags

# List all tags
$ git tag
v0.1
v1.3
v1.4

# List with a filter
$ git tag -l 'v1.8.5*'

# Create an annotated tag (recommended for releases)
$ git tag -a v1.4 -m "version 1.4"

# Create a lightweight tag (simple pointer)
$ git tag v1.4-lw

# Tag a past commit
$ git tag -a v1.2 9fceb02

# Show an annotated tag's data
$ git show v1.4

# Push a specific tag to the remote
$ git push origin v1.5

# Push all annotated tags linked to pushed commits
$ git push origin --follow-tags

# Delete a local tag
$ git tag -d v1.4-lw

# Delete a tag on the remote
$ git push origin --delete v1.4-lw

# Check out a tag's content (creates a detached HEAD)
$ git checkout v2.29.2
# To work on it, create a branch
$ git checkout -b v2.29.X
Utilities

Locate a bug by binary search (git bisect)

# Start a bisect session
$ git bisect start

# Mark the current commit as bad
$ git bisect bad

# Mark a known good commit
$ git bisect good v2.1
Bisecting: 6 revisions left to test after this (roughly 3 steps)

# Git checks out a middle commit. You test, then:
$ git bisect bad   # or
$ git bisect good

# Automate with a test script (exit 0 = good, exit 1 = bad)
$ git bisect run python tests/test_feature.py

# End the session and return to the original branch
$ git bisect reset

Identify the author of a line (git blame)

# Annotate a file line by line
$ git blame network.py
^4832fe2 (John Doe   2025-01-15 10:23:11) def connect():
a8f4c13b (Jane Smith 2025-02-03 14:12:55)     host = resolve_dns(name)

# Limit to a line range
$ git blame -L 25,40 network.py

Create Git aliases

# Classic aliases
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status

# Alias to unstage a file
$ git config --global alias.unstage 'restore --staged'

# Alias to see the last commit
$ git config --global alias.last 'log -1 HEAD'

# Alias for a full graph log
$ git config --global alias.lg 'log --oneline --decorate --graph --all'

# Alias launching an external command (! prefix)
$ git config --global alias.visual '!gitk'

# Usage
$ git st
$ git last

Ignore files (.gitignore)

The .gitignore file at the root of the repository lists file patterns that Git should ignore. It applies recursively. Additional .gitignore files can exist in subdirectories.
# Example .gitignore
# Ignore all .a and .o files
*.a
*.o

# But track lib.a despite the previous rule
!lib.a

# Ignore TODO only at the root
/TODO

# Ignore the entire build/ directory
build/

# Ignore doc/notes.txt but not doc/server/arch.txt
doc/*.txt

# Ignore all .txt files under doc/ recursively
doc/**/*.txt
GitHub maintains .gitignore templates for many languages and frameworks at github.com/github/gitignore.
Advanced

Manage submodules (git submodule)

# Add a submodule to an existing repository
$ git submodule add https://github.com/user/lib.git libs/lib

# Clone a repository with its submodules
$ git clone --recurse-submodules https://github.com/user/project.git

# Initialise and update submodules after a plain clone
$ git submodule init
$ git submodule update

# Update submodules to the latest remote version
$ git submodule update --remote

# Execute a command in each submodule
$ git submodule foreach 'git fetch'

# Check the status of all submodules
$ git submodule status

Transfer data without a network (git bundle)

# Create a bundle of the entire master branch
$ git bundle create repo.bundle master

# Create a bundle of recent commits only
$ git bundle create recent.bundle HEAD~10..HEAD

# Verify a received bundle
$ git bundle verify repo.bundle

# Use a bundle as a remote repository
$ git clone repo.bundle target-repo
$ git fetch ../repo.bundle master

Recover lost work (reflog)

The reflog records locally all movements of HEAD, even after a git reset --hard. Any commit reachable via the reflog can be recovered.
# View the history of HEAD positions
$ git reflog
a41df0b HEAD@{0}: commit: Add network function
310154e HEAD@{1}: reset: moving to 310154e
a8f4c13 HEAD@{2}: commit: Update README

# Recover a commit removed by reset --hard
# (the commit still exists for 30 days by default)
$ git checkout a41df0b
# Or create a recovery branch
$ git branch recovery a41df0b

# Check for orphaned objects (dangling commits)
$ git fsck --lost-found

# Inspect a found dangling object
$ git show sha1_of_dangling_commit
Git only removes orphaned objects after 30 days by default (via git gc). Lost work is almost always recoverable as long as the repository has not undergone a gc --prune=now.