Toolmingo
Guides22 min read

Git Tutorial for Beginners (2025): Learn Version Control from Scratch

Complete Git tutorial for beginners — install Git, understand commits, branches, merging, rebasing, and real workflows. Includes cheat sheet and hands-on examples.

Git is the version control system used by virtually every software team on earth. It tracks every change you make to code, lets you experiment safely in branches, collaborate without overwriting each other's work, and roll back mistakes in seconds.

This tutorial takes you from zero — no Git installed — to confidently using Git in real projects.

What you'll learn

Topic What you'll be able to do
Setup Install Git, configure your identity
Core concepts Understand repos, commits, staging
Basic workflow init → add → commit → log
Branches Create, switch, merge, delete branches
Remote repos clone, push, pull, fetch
Undoing changes reset, revert, restore, stash
Real workflows Feature branch, GitFlow basics
Collaboration Merge conflicts, pull requests

Why Git?

Before Git, teams emailed zip files or used shared folders. One overwrite and a week of work was gone.

Without Git With Git
"final_final_v3.zip" Every version saved forever
One person edits at a time Teams work simultaneously
Mistakes are permanent Roll back to any point
No record of who changed what Full blame history
Scared to experiment Branches make experiments safe
Merging = manual copy-paste Git merges automatically

Git was created by Linus Torvalds in 2005 to manage the Linux kernel. Today it's the universal standard — used by Google, Microsoft, Netflix, NASA, and solo developers alike.


Part 1 — Install & Configure Git

Install Git

Windows: Download from git-scm.com and run the installer. Use all defaults.

macOS:

# Option 1: Xcode Command Line Tools (installs automatically when you run git)
git --version

# Option 2: Homebrew
brew install git

Linux (Debian/Ubuntu):

sudo apt update && sudo apt install git

Linux (Fedora/RHEL):

sudo dnf install git

Verify installation:

git --version
# git version 2.44.0

Configure your identity

Git stamps every commit with your name and email. Set them once:

git config --global user.name "Your Name"
git config --global user.email "you@example.com"

Other useful settings:

# Set VS Code as your default editor
git config --global core.editor "code --wait"

# Set default branch name to 'main' (modern standard)
git config --global init.defaultBranch main

# Better diff output on Windows
git config --global core.autocrlf true

# View your full config
git config --list

Config is stored in ~/.gitconfig:

[user]
    name = Your Name
    email = you@example.com
[core]
    editor = code --wait
    autocrlf = true
[init]
    defaultBranch = main

Part 2 — Core Concepts

Before typing commands, understand what Git actually tracks.

The three areas

Working Directory    Staging Area (Index)    Repository (.git/)
-----------------   --------------------    ------------------
Your files as        Snapshot of what        Permanent history
you edit them        will be committed        of all commits
        │                    │                      │
        └──── git add ───────┘                      │
                             └──── git commit ───────┘
Area What it is How to affect it
Working Directory Files on disk Edit files normally
Staging Area What's queued for the next commit git add
Repository Permanent commit history git commit

What is a commit?

A commit is a snapshot of your staged files at a point in time. It contains:

  • A unique SHA hash (e.g., a1b2c3d)
  • Author, date, and message
  • A pointer to the previous commit (parent)

Commits form a linked chain — the history of your project:

A ──── B ──── C ──── D  (main branch)
Initial  Add   Fix   New
commit   auth  bug   feature

What is a repository?

A repository (repo) is a folder that Git tracks. It contains:

  • Your project files
  • A hidden .git/ folder with all history, branches, and config
my-project/
├── .git/          ← Git's internal database (don't touch)
│   ├── HEAD
│   ├── config
│   ├── objects/   ← all commits, files, trees stored here
│   └── refs/      ← branch and tag pointers
├── index.html
├── style.css
└── app.js

Part 3 — Your First Repository

Create a new repo

mkdir my-project
cd my-project
git init

Output:

Initialized empty Git repository in /home/user/my-project/.git/

Check status

git status is your most-used command. Run it constantly:

git status
# On branch main
# No commits yet
# nothing to commit

Create a file and make your first commit

echo "# My Project" > README.md
git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        README.md

nothing added to commit but untracked files present

Stage the file:

git add README.md
git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   README.md

Commit it:

git commit -m "Initial commit: add README"
[main (root-commit) a1b2c3d] Initial commit: add README
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

View history

git log
commit a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9 (HEAD -> main)
Author: Your Name <you@example.com>
Date:   Wed Jul 16 10:00:00 2026 +0000

    Initial commit: add README

Shorter view:

git log --oneline
# a1b2c3d Initial commit: add README

Pretty graph:

git log --oneline --graph --all

Part 4 — The Daily Workflow

Staging commands

# Stage a single file
git add filename.txt

# Stage multiple files
git add file1.txt file2.txt

# Stage all changes in current directory
git add .

# Stage parts of a file interactively
git add -p filename.txt

# Unstage a file (keep changes in working directory)
git restore --staged filename.txt

Checking differences

# Changes not yet staged
git diff

# Changes staged (ready to commit)
git diff --staged

# Changes between two commits
git diff a1b2c3d e4f5g6h

# Changes in a single file
git diff HEAD filename.txt

Making good commits

A commit should represent one logical change. Bad vs good:

# BAD: everything in one commit
git add .
git commit -m "stuff"

# GOOD: separate logical commits
git add auth.js
git commit -m "feat: add JWT authentication middleware"

git add user.test.js
git commit -m "test: add unit tests for auth middleware"

Commit message format (Conventional Commits):

<type>: <short description>

[optional body]

[optional footer]
Type When to use
feat New feature
fix Bug fix
docs Documentation only
style Formatting, no logic change
refactor Code restructure, no feature/fix
test Adding or fixing tests
chore Build process, dependencies
perf Performance improvement

Examples:

feat: add user registration endpoint
fix: prevent crash when email is null
docs: update API authentication guide
chore: upgrade Jest to v29

Amending commits

Fix the last commit (before pushing):

# Add a forgotten file to the last commit
git add forgotten-file.txt
git commit --amend --no-edit

# Change the last commit message
git commit --amend -m "feat: better commit message"

Never amend commits you've already pushed — it rewrites history and breaks collaborators.


Part 5 — Branches

Branches let you work on features, fixes, or experiments without touching the main codebase.

How branches work

main:    A ──── B ──── C
                       │
feature:               └──── D ──── E

main stays stable. You work on feature. When done, merge feature back into main.

Branch commands

# List branches (* = current)
git branch
# * main
#   feature/login

# Create a new branch
git branch feature/user-auth

# Switch to a branch
git switch feature/user-auth
# (older syntax: git checkout feature/user-auth)

# Create and switch in one command
git switch -c feature/user-auth

# Delete a branch (after merging)
git branch -d feature/user-auth

# Force delete (even if not merged)
git branch -D feature/user-auth

# Rename current branch
git branch -m new-name

# List all branches including remote
git branch -a

Working on a branch

# 1. Start from main
git switch main
git pull  # get latest changes

# 2. Create feature branch
git switch -c feature/add-login

# 3. Work on the feature
echo "login code" > login.js
git add login.js
git commit -m "feat: add login page"

# 4. More commits
echo "tests" > login.test.js
git add login.test.js
git commit -m "test: add login tests"

# 5. Merge into main when done
git switch main
git merge feature/add-login

# 6. Delete the branch
git branch -d feature/add-login

Merge types

Fast-forward merge (main didn't change while you worked):

Before:
main:    A ──── B
                └──── C ──── D  (feature)

After git merge feature:
main:    A ──── B ──── C ──── D

Git just moves main forward. No merge commit.

Three-way merge (main changed while you worked):

Before:
main:    A ──── B ──── E
                └──── C ──── D  (feature)

After git merge feature:
main:    A ──── B ──── E ──── M  (merge commit)
                └──── C ──── D ─┘

Git creates a merge commit M combining both histories.

# Create a merge commit even when fast-forward is possible
git merge --no-ff feature/login

# Abort a merge in progress
git merge --abort

Part 6 — Merge Conflicts

A merge conflict happens when two branches changed the same lines differently. Git can't decide which version to keep — it asks you.

What a conflict looks like

<<<<<<< HEAD
This is the main branch version.
=======
This is the feature branch version.
>>>>>>> feature/login
  • <<<<<<< HEAD — your current branch's version
  • ======= — divider
  • >>>>>>> feature/branch — the incoming branch's version

Resolving conflicts

# 1. See which files conflict
git status
# both modified: login.js

# 2. Open the file and edit it to the correct version
# (remove the conflict markers, keep what you want)

# 3. After editing, mark as resolved
git add login.js

# 4. Complete the merge
git commit

Using a merge tool

# Open a visual merge tool
git mergetool

# Configure VS Code as merge tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

Conflict prevention tips

  • Pull from main frequently (merge conflicts grow with time)
  • Keep branches short-lived (days, not weeks)
  • Communicate with teammates which files you're editing
  • Break large features into smaller PRs

Part 7 — Remote Repositories

A remote is a version of your repo stored somewhere else — usually GitHub, GitLab, or Bitbucket.

Key remote commands

# View remotes
git remote -v
# origin  https://github.com/you/project.git (fetch)
# origin  https://github.com/you/project.git (push)

# Add a remote
git remote add origin https://github.com/you/project.git

# Remove a remote
git remote remove origin

# Rename a remote
git remote rename origin upstream

Clone an existing repo

git clone https://github.com/username/repo.git
# Creates a 'repo' folder with the project inside

# Clone into a specific folder name
git clone https://github.com/username/repo.git my-folder

# Clone a specific branch
git clone -b develop https://github.com/username/repo.git

Push and pull

# Push local commits to remote
git push origin main

# Push and set upstream (only needed once per branch)
git push -u origin feature/login
# After this, just: git push

# Pull latest changes (fetch + merge)
git pull origin main

# Fetch without merging
git fetch origin
git merge origin/main  # then merge manually

Fetch vs Pull:

Command What it does
git fetch Downloads remote changes, doesn't merge
git pull Downloads AND merges (fetch + merge)

Use git fetch when you want to review changes before merging. Use git pull when you trust the remote and want to sync immediately.

Push rejected?

If someone pushed to the remote after your last pull:

# This fails:
git push origin main
# ! [rejected] main -> main (non-fast-forward)

# Fix: pull first, then push
git pull origin main  # merges their changes
git push origin main  # now succeeds

Part 8 — Undoing Changes

Git's superpower: nothing is ever truly lost.

Discard working directory changes

# Discard changes to a single file (back to last commit)
git restore filename.txt

# Discard ALL unstaged changes (careful!)
git restore .

Unstage a file

# Remove from staging area, keep working directory changes
git restore --staged filename.txt

Undo commits

# Undo last commit, keep changes staged
git reset --soft HEAD~1

# Undo last commit, keep changes unstaged
git reset --mixed HEAD~1  # (default)

# Undo last commit AND discard all changes (DESTRUCTIVE)
git reset --hard HEAD~1

# Undo last 3 commits, keep changes
git reset --soft HEAD~3

reset vs revert:

Command Changes history? Safe to push?
git reset Yes — rewrites history Only if not pushed yet
git revert No — adds a new "undo" commit Always safe
# Create a new commit that undoes a previous commit
git revert a1b2c3d

# Revert last commit without opening an editor
git revert HEAD --no-edit

Use revert on shared branches. Use reset only on private branches.

Stash (save work without committing)

# Save current work to stash
git stash

# Stash with a description
git stash push -m "WIP: login form validation"

# List stashes
git stash list
# stash@{0}: WIP: login form validation
# stash@{1}: On main: WIP: dashboard

# Apply most recent stash (keep it in stash list)
git stash apply

# Apply and remove from stash list
git stash pop

# Apply specific stash
git stash apply stash@{1}

# Delete a stash
git stash drop stash@{0}

# Clear all stashes
git stash clear

When to stash: You're mid-feature when an urgent bug comes in. Stash your work, fix the bug, pop your stash and resume.

Find deleted content

# See all commits including "orphaned" ones
git reflog
# HEAD@{0}: commit: feat: add login
# HEAD@{1}: reset: moving to HEAD~1
# HEAD@{2}: commit: feat: add login  ← your "lost" commit

# Recover a lost commit
git checkout HEAD@{2}
# or
git cherry-pick HEAD@{2}

git reflog is your safety net. Git keeps everything for at least 30 days.


Part 9 — Rebase

Rebase is an alternative to merge. Instead of creating a merge commit, it replays your commits on top of another branch.

Before rebase:
main:    A ──── B ──── C
                └──── D ──── E  (feature)

After git rebase main:
main:    A ──── B ──── C
                       └──── D' ──── E'  (feature, replayed)

After rebase, feature looks like it was always based on the latest main.

# Rebase feature branch onto main
git switch feature/login
git rebase main

# Interactive rebase: clean up your last 3 commits before merging
git rebase -i HEAD~3

Interactive rebase lets you:

  • pick — keep the commit
  • squash / s — combine with previous commit
  • reword / r — change the commit message
  • drop / d — delete the commit
  • edit / e — pause and amend

Merge vs Rebase:

Merge Rebase
History Preserves full history Cleaner linear history
Merge commits Creates one None
Conflicts Resolved once Resolved per commit
Safe on shared branches Yes No — rewrites history
Best for Feature merges to main Cleaning up local history

Golden rule: Never rebase commits that are on a remote shared branch.


Part 10 — Tags

Tags mark specific commits as important — usually releases.

# List tags
git tag

# Create a lightweight tag
git tag v1.0.0

# Create an annotated tag (recommended — includes message)
git tag -a v1.0.0 -m "Release version 1.0.0"

# Tag a specific commit
git tag -a v0.9.0 a1b2c3d -m "Beta release"

# Push a tag to remote
git push origin v1.0.0

# Push all tags
git push origin --tags

# Delete a local tag
git tag -d v1.0.0

# Delete a remote tag
git push origin --delete v1.0.0

# Check out a specific tag
git checkout v1.0.0

Semantic versioning: v<MAJOR>.<MINOR>.<PATCH>

  • MAJOR — breaking changes
  • MINOR — new features, backwards compatible
  • PATCH — bug fixes

Part 11 — .gitignore

.gitignore tells Git which files to never track. Add it to every project.

# Dependencies
node_modules/
vendor/
__pycache__/
*.pyc

# Environment files (never commit secrets!)
.env
.env.local
.env.production
*.env

# Build output
dist/
build/
out/
.next/

# Logs
*.log
logs/

# OS files
.DS_Store
Thumbs.db
desktop.ini

# IDE files
.vscode/settings.json
.idea/
*.swp
*.swo

# Test coverage
coverage/
.nyc_output/

# Database files
*.sqlite
*.db
# If you already committed something that should be ignored:
git rm --cached filename.txt       # remove from tracking, keep on disk
git rm --cached -r directory/      # remove a directory
git commit -m "chore: untrack filename.txt"

Check what .gitignore is ignoring:

git status --ignored
git check-ignore -v filename.txt

Part 12 — Real-World Workflows

Feature Branch Workflow (most common)

# 1. Start from an updated main
git switch main
git pull origin main

# 2. Create a feature branch
git switch -c feature/user-profile

# 3. Work and commit often
git add -p          # stage selectively
git commit -m "feat: add avatar upload"
git commit -m "feat: add profile bio field"
git commit -m "test: add profile tests"

# 4. Keep up-to-date with main (to minimize conflicts)
git fetch origin
git rebase origin/main

# 5. Push for code review
git push -u origin feature/user-profile

# 6. Open a pull request on GitHub/GitLab
# After approval, merge via the web UI or:
git switch main
git merge --no-ff feature/user-profile
git push origin main

# 7. Clean up
git branch -d feature/user-profile
git push origin --delete feature/user-profile

GitFlow (for versioned releases)

main        ──────────────────────────── v1.0 ──── v1.1
develop     ──────── feature1 ── feature2 ─────────────────
feature/*          /           /
hotfix/*                                   ← patch on main
release/*                            ← prep before tagging
Branch Purpose
main Production-ready code only
develop Integration branch for features
feature/* New features from develop
release/* Release prep (version bump, final fixes)
hotfix/* Emergency fixes on main

Good for: libraries, mobile apps, software with versioned releases.

Trunk-Based Development (for fast teams)

Everyone commits to main (trunk) directly or via short-lived branches (< 1 day). Feature flags hide incomplete work.

# Short-lived branch
git switch -c feat/button-color
# ... one small commit ...
git push origin feat/button-color
# PR reviewed and merged same day

Good for: SaaS products, continuous deployment, experienced teams.

Commit often, push when ready

# Local commits — free, don't cost anything
git commit -m "WIP: starting login form"
git commit -m "WIP: add validation"
git commit -m "WIP: almost done"

# Before pushing, clean up with interactive rebase
git rebase -i origin/main
# squash all WIP commits into one clean commit
# "feat: add login form with validation"

git push origin feature/login

Part 13 — Essential Commands Reference

Navigation & inspection

git status                    # what's changed
git log --oneline             # compact history
git log --oneline --graph --all  # visual branch graph
git show a1b2c3d              # details of a commit
git diff                      # unstaged changes
git diff --staged             # staged changes
git blame filename.txt        # who wrote each line
git shortlog -sn              # commits by author

File operations

git add .                     # stage all
git add -p                    # stage interactively
git restore file.txt          # discard working changes
git restore --staged file.txt # unstage
git rm file.txt               # delete and stage removal
git mv old.txt new.txt        # rename and stage

Branches

git branch                    # list local branches
git branch -a                 # list all including remote
git switch -c feature/name    # create and switch
git switch main               # switch branch
git merge feature/name        # merge into current
git branch -d feature/name    # delete merged branch
git branch -D feature/name    # force delete

Remote

git clone <url>               # clone a repo
git remote -v                 # list remotes
git fetch origin              # download without merging
git pull origin main          # download and merge
git push origin main          # upload commits
git push -u origin feature    # push + set upstream

Undo

git restore file.txt          # discard unstaged changes
git restore --staged file.txt # unstage
git commit --amend            # fix last commit
git reset --soft HEAD~1       # undo commit, keep staged
git reset --mixed HEAD~1      # undo commit, keep unstaged
git reset --hard HEAD~1       # undo commit, discard changes
git revert HEAD               # create undo commit (safe)
git stash                     # save work temporarily
git stash pop                 # restore stashed work

Advanced

git rebase main               # replay commits on main
git rebase -i HEAD~3          # interactive rebase
git cherry-pick a1b2c3d       # copy a commit to current branch
git bisect start              # binary search for a bug commit
git tag -a v1.0.0 -m "..."    # create annotated tag
git reflog                    # view all HEAD movements

Part 14 — Three Hands-On Projects

Project 1 — Personal website with version history

mkdir my-website && cd my-website
git init

# Create initial files
cat > index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>My Website</title></head>
<body><h1>Hello, World!</h1></body>
</html>
EOF

git add .
git commit -m "feat: initial website"

# Add a style
cat > style.css << 'EOF'
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; }
EOF

# Link it in HTML
sed -i 's|</head>|<link rel="stylesheet" href="style.css"></head>|' index.html

git add .
git commit -m "feat: add CSS styling"

# Experiment in a branch
git switch -c experiment/dark-mode
echo "body { background: #1a1a1a; color: white; }" >> style.css
git add style.css
git commit -m "feat: dark mode experiment"

# View history
git log --oneline --graph --all
#   * a1b2c3d (experiment/dark-mode) feat: dark mode experiment
#   * b2c3d4e (main) feat: add CSS styling
#   * c3d4e5f feat: initial website

# Merge or discard
git switch main
git merge experiment/dark-mode   # keep it
# or
# git branch -D experiment/dark-mode  # discard it

Project 2 — Collaborate with yourself (two clones)

Simulate a team workflow using two copies:

# Create a "remote" (bare repo)
mkdir ~/repos
git init --bare ~/repos/project.git

# Clone as "Alice"
git clone ~/repos/project.git ~/alice-project
cd ~/alice-project
git config user.name "Alice"

echo "Hello from Alice" > alice.txt
git add alice.txt
git commit -m "feat: alice's file"
git push origin main

# Clone as "Bob"
git clone ~/repos/project.git ~/bob-project
cd ~/bob-project
git config user.name "Bob"

# Bob creates a feature branch
git switch -c feature/bob-work
echo "Hello from Bob" > bob.txt
git add bob.txt
git commit -m "feat: bob's file"
git push -u origin feature/bob-work

# Alice reviews and merges
cd ~/alice-project
git fetch origin
git merge origin/feature/bob-work
git push origin main

Project 3 — Fix a bug with bisect

git bisect uses binary search to find which commit introduced a bug:

# Create a history with a bug introduced midway
git init bug-project && cd bug-project

for i in 1 2 3 4 5; do
    echo "version $i" > version.txt
    git add .
    git commit -m "commit $i"
done

# Bug introduced here
echo "BUG" >> version.txt
git add . && git commit -m "commit 6 (has bug)"

for i in 7 8 9 10; do
    echo "version $i" >> version.txt
    git add .
    git commit -m "commit $i"
done

# Now find when the bug appeared
git bisect start
git bisect bad                    # current commit is bad
git bisect good HEAD~8            # commit 2 was good

# Git checks out the midpoint commit
# Test it, then tell Git if it's good or bad:
grep -q "BUG" version.txt && git bisect bad || git bisect good

# Repeat until Git finds the first bad commit
# Then reset:
git bisect reset

Common Mistakes

Mistake Problem Fix
Committing directly to main Risky in teams, no review Always work in feature branches
Committing .env files Exposes secrets to the world Add to .gitignore immediately
Vague commit messages "fix stuff" tells nothing Use type: description format
Force-pushing to shared branches Destroys teammates' history Only force-push to your own branches
Huge commits Hard to review, hard to revert Commit each logical change separately
Never running git fetch Out-of-date view of remote git fetch before branching
Using git reset --hard carelessly Data loss Always check git status first
Ignoring merge conflicts Code breaks silently Resolve and test immediately

Git vs Related Tools

Tool What it is
Git Version control system (local and remote)
GitHub Cloud hosting for Git repos + collaboration
GitLab Alternative to GitHub with built-in CI/CD
Bitbucket Atlassian's Git hosting (integrates with Jira)
Gitea Self-hosted open-source GitHub alternative
SVN Older centralized version control (not Git)
Mercurial Alternative to Git (less common)
Git LFS Git extension for large files (videos, models)
pre-commit Framework for Git pre-commit hooks
GitKraken/SourceTree Visual GUI clients for Git

Learning Path

Stage What to do Time
Beginner Install Git, init/add/commit/log Week 1
Basic workflow push/pull/clone, branches, merge Week 2-3
Collaboration conflicts, pull requests, fork Week 4-5
Intermediate rebase, stash, tags, .gitignore Month 2
Advanced cherry-pick, bisect, hooks, reflog Month 3+
Expert custom workflows, aliases, scripting Ongoing

Practice resources:

  • git help <command> — built-in docs
  • Learn Git Branching (interactive visualization)
  • Oh My Git! (card game for learning Git)
  • Pro Git book (free at git-scm.com/book)

FAQ

Do I need GitHub to use Git? No. Git is local software. GitHub is a hosting platform built on top of Git. You can use Git entirely on your own machine. GitHub becomes necessary when collaborating or backing up to the cloud.

What's the difference between git pull and git fetch? git fetch downloads changes from the remote but doesn't touch your local branches. git pull is git fetch followed by git merge. Use git fetch to see what's changed before applying it; use git pull when you want to sync immediately.

When should I use merge vs rebase? Use merge when combining completed feature branches into main — it preserves the full history. Use rebase to clean up your local commits before sharing them (interactive rebase to squash WIP commits). Never rebase branches other people are using.

How do I undo a git push? If you pushed by mistake and no one has pulled yet: git push --force-with-lease origin branch-name after reverting locally. If others have already pulled, use git revert to create an undo commit — never force-push to shared branches.

Should I commit package-lock.json / yarn.lock? Yes, always. Lock files ensure every developer installs the exact same dependency versions. Commit them, but add node_modules/ to .gitignore (don't commit the actual packages).

How large should commits be? Each commit should represent one logical, complete change that passes tests on its own. Not so small it's noise (fixing a typo in a comment = one commit, not three), not so large it's impossible to review (don't combine feature + refactor + bug fix). "If you can explain the commit in one sentence, it's the right size."

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools