Have you ever been told to download something from GitHub and been greeted with a long list of folders and no clear DOWNLOAD button? This is a jarring yet completely normal experience — git and GitHub are targeted towards software developers. But fear not, you will become a competent user of both tools at the end of this tutorial.
Git vs GitHub: the key distinction
Git and GitHub are two separate things. Git is a program installed on your computer that tracks changes to files in a project folder. That folder, once tracked by git, is called a local repository (or local repo) — it lives entirely on your machine and works with no internet connection required. GitHub is a separate website that hosts git repositories in the cloud. That cloud-hosted copy is called the remote repository (or remote repo). Think of your local repo as your personal working copy, and the remote repo as the shared, backed-up version that your whole team can access.
You turn any ordinary folder into a local git repo by running
git init inside it. Once your local repo is linked to a
GitHub repo, git push uploads your committed changes
from your machine up to GitHub, and git pull downloads
the latest changes from GitHub and applies them to your local repo.
Staging and commit
git add -A saves all of the changes you've made in your
local repo to a temporary location known as the
staging area. It allows for more granular version control
than git commit — it's useful for grouping related
changes before locking them in with a commit (pun intended).
git commit logs all of the staged changes you've made
in your local repo up to this point, permanently. This is especially
useful for when something goes wrong:
git revert <commit-hash> creates a new commit
that undoes the changes from a specific earlier commit, without
erasing history. You may think of git commit as
creating a newer version of the project.
To keep things simple you can just run
git add -A && git commit -m "<commit
message>"
to create a newer version of your project, or append
&& git push to also upload the latest version
to GitHub.
git add, locked in
with git commit, and uploaded to GitHub with
git push. git pull brings changes back
down.
Downloading: clone, fetch, and pull
You might have also heard of git fetch and
git clone — together with git pull they
all function as the DOWNLOAD button, but they have important
differences:
-
git clone <url>downloads the entire project from GitHub to your current working directory. Use this command if you are downloading a project for the very first time. -
git fetchdownloads the latest version of a project from GitHub to your already-synced local repo, but leaves your current local version unchanged. -
Once you're ready, you can update your local version to match via
git merge. -
git pullis justgit fetch && git mergein a single step.
git clone is for
first-time setup; git pull is the everyday shorthand;
git fetch followed by git merge gives
you the same result in two explicit steps — useful when you want
to inspect changes before applying them.
What about curl?
curl is a command-line tool for transferring data from
any URL — it speaks HTTP, HTTPS, FTP, and a dozen other protocols.
Where git clone and git fetch understand repositories, commits, and
history, curl simply fetches whatever the server
returns at a given address and either prints it or saves it to a
file. It has no concept of version control.
The most common flags:
-
-O— save the file using its original remote filename (e.g.curl -O https://example.com/file.zip) -
-o <filename>— save to a specific local filename instead -
-L— follow redirects. Many download URLs redirect once or twice before reaching the actual file; without-Lyou get the redirect response, not the file. Almost always needed. -
-s— silent: suppress the progress meter and error messages. Useful in scripts. -
-v— verbose: print full request and response headers. Useful for debugging. -
-X <METHOD>— set the HTTP method:-X POST,-X DELETE, etc. Defaults to GET. -
-H "<Header: value>"— add a request header, e.g.-H "Authorization: Bearer <token>"for authenticated API calls. -
-d "<data>"— send a request body (implies POST). Commonly used with-H "Content-Type: application/json".
A typical one-liner to download a single raw file from GitHub:
curl -L https://raw.githubusercontent.com/user/repo/main/script.sh -o script.sh
curl vs git clone / git fetch at a glance:
- curl downloads the raw bytes at a URL — a file, an API response, an HTML page. It knows nothing about commits, branches, or history.
- git clone / git fetch download a git repository — the full history, all branches, and all metadata. You can revert to any past commit, create branches, and push changes back.
-
Use
curlwhen you want a single file or need to talk to an API. Usegit clonewhen you want to work with the project itself — edit, commit, and push.
Starting a new project from scratch (2026 workflow)
If you're starting a fresh project rather than cloning an existing one, the typical setup looks like this:
- Create a new repository on github.com (click the "+" icon → "New repository"). Give it a name and leave it empty — no README, no .gitignore — so it doesn't conflict with your local files.
-
In your local project folder, run
git initto make it a git repository. -
Create a
.gitignorefile in the root of your project folder. This is a plain text file that tells git which files and folders to never track — things likenode_modules/(JavaScript dependencies),.env(secrets and API keys), or__pycache__/(Python cache). Without it, you risk accidentally committing files that are too large, auto-generated, or sensitive. GitHub maintains ready-made templates for most languages at github.com/github/gitignore. -
Link your local repo to the GitHub repo:
git remote add origin <url>
where<url>is the HTTPS URL shown on your new GitHub repo page. This tells git wheregit pushandgit pullshould point. -
Stage, commit, and push your initial code:
git add -A && git commit -m "initial commit" && git push -u origin main
The-uflag sets the upstream so futuregit pushandgit pullcommands don't need the branch name.
Authentication (easiest method in 2026): use the
GitHub CLI. Install it from
cli.github.com, then run
gh auth login and follow the browser prompt. This
handles OAuth automatically — no personal access tokens to create or
manage.
Optional — GitHub CLI for one-step setup: if you
have the GitHub CLI installed, you can skip steps 1–3 entirely. From
inside your local folder, run:
gh repo create <repo-name> --public --source=.
--remote=origin --push
This creates the GitHub repo, links it as origin, and pushes your
initial commit all in one command.
Branches and pull requests
git checkout -b <branch-name> creates a new and
identical copy of your project in a separate branch. This allows you
to keep working without touching the main branch. For a solo project
this is optional, but for a team project it's important to keep
track of who made what changes. (The modern equivalent is
git switch -c <branch-name>.)
Once you've made commits on your branch and pushed it to GitHub, you open a pull request (PR) on GitHub. A PR is a formal proposal to merge your branch into main — GitHub displays exactly what changed, and your teammates can leave comments, request changes, or approve it. Once approved, someone clicks "Merge pull request" and your branch is merged. This is the standard team collaboration workflow on GitHub: each person works on their own branch, then proposes changes via a PR rather than pushing directly to main.
main. A feature branch is created at C and receives
commits D and E. A pull request on GitHub proposes merging the
feature branch back into main, producing merge commit
F.
Quick-reference: useful commands
git diff— lists all unstaged changes-
git diff --staged— lists all staged but uncommitted changes -
git restore --staged <file>— unstages a file without losing your changes; moves it back from the staging area to your working directory -
git restore <file>— discards all unstaged changes to a file, reverting it to its last committed state. Warning: this cannot be undone. -
git status— gives a summary of which files have been changed and their tracking status (unstaged/staged) git log— lists commit history-
git branch -a— displays all git branches; the currently active branch is marked with an asterisk (*) -
git switch <branch-name>— switches to an existing branch -
git switch -c <new-branch>— creates a new branch and switches to it -
git switch --detach <commit-hash>— moves you to a specific past commit. This puts you in a "detached HEAD" state, where HEAD points directly to that commit instead of a branch. Warning: any new commits made in this state are not part of any branch and may be lost if you switch away without first runninggit switch -c <new-branch-name>. -
git merge <branch-name>— integrates the changes from the specified branch into your current branch. It creates a merge commit that ties together the histories of both branches, preserving the full record of where each change came from. -
git rebase <branch-name>— replays your current branch's commits on top of the specified branch, resulting in a cleaner, linear commit history. Unlike merge, it rewrites your commits rather than adding a merge commit. Because it rewrites history, avoid rebasing commits that have already been pushed and shared with others.
git merge vs git rebase on the same
starting point (main has A–B–C, feature has D–E). Merge adds a
merge commit F and preserves the full branching history. Rebase
replays D and E on top of C, producing rewritten commits D′
and E′ with a clean linear history — but no record of the
branch.
Your daily workflow
This is a lot of information, but as a solo developer your daily workflow can be very simple. As an example:
-
Navigate to your project folder and pull the latest changes at the
start of each session:
cd ~/path/to/local-repo && git pull -
Work on your project. At each logical stopping point, log your
progress:
git add -A && git commit -m "<what you changed>"
Repeat this throughout your session — small, frequent commits are better than one large one at the end. -
Upload your progress to GitHub when you're done for the day:
git push
Merging multiple branches into main
A common pattern: you have a main branch and several
child branches, each representing one unit of work — a new article,
a bug fix, a feature. The safest approach is to merge them into
main one at a time, pushing to the remote after each
merge so the remote always reflects your latest stable state.
main. Each is merged back one at a time, producing a
merge commit (M1–M4). Push to the remote after each merge.
The workflow, step by step:
-
Make sure
mainis current before you start:
git checkout main && git pull -
Merge the first branch and push:
git merge feature/feature-1 && git push origin main
If conflicts arise, resolve them first (see below), then push. - Repeat for each remaining branch in order.
-
Delete the merged branches when done:
git branch -d feature/feature-1 feature/feature-2 ...
How does git merge work under the hood
When you run git merge B while on branch A, git first
locates the merge base — the most recent commit the two
branches share in their history. It then computes two diffs: what
changed from the merge base to A's latest commit, and what changed
from the merge base to B's latest commit. Finally, it combines both
sets of changes into a new merge commit that records both
A and B as its parents. Because three commits are involved — the
merge base plus the two branch tips — this is called a
three-way merge.
Fast-forward. If branch B is a direct ancestor of
A — meaning A has no new commits since B was created — there is
nothing to combine. Git simply moves A's branch pointer forward to
B's tip without creating a merge commit. You will notice this in
git log: no merge commit appears, just a straight line
of history. Pass --no-ff to force a merge commit
regardless, which keeps a visible record that a branch existed.
Resolving merge conflicts
A conflict happens when two branches have each modified the
same lines in the same file. Git cannot decide which
version to keep, so it halts the merge and marks the affected files
for you to resolve manually. Running git status will
list every file flagged both modified.
Open a conflicted file and you will find conflict markers inserted by git:
<<<<<<< HEAD
the version from your current branch (main)
=======
the version being merged in (feature/article-2)
>>>>>>> feature/article-2
Everything between
<<<<<<< HEAD and
======= is what your current branch has. Everything
between ======= and
>>>>>>> is what the incoming
branch has.
Your job: edit the file to its correct final state, then delete all three marker lines. Three strategies:
- Keep one side — delete the other block and all three markers. Use this when one version is simply right and the other is outdated.
- Keep both — delete the three marker lines but keep both blocks of content. Use this when adding entries to a shared list — both additions are valid and should coexist.
- Rewrite — delete everything between the markers and write the correct content from scratch. Use this when neither version is quite right on its own.
Once every conflicted file is saved with no markers remaining, complete the merge:
git add <conflicted-file> # mark as resolved
git merge --continue # opens editor for the merge commit message
git push origin main
To abandon the merge entirely and return to the pre-merge state:
git merge --abort.
The most common conflict in web projects is a
shared data file — a central articles.json, a
navigation list, a config — where multiple branches each appended a
new entry. The resolution is always the same: keep all entries from
both sides, remove the markers, save, and continue.
Comments