More than forty short chapters. They teach you how to use Git and GitHub properly. Use them on your own, in a team, or with an AI helper that writes code for you.
Every chapter is short. Hard words are explained the first time they appear. When a hard word comes back later, a short reminder sits next to it in brackets. Most chapters have a small diagram. Most end with a sentence you can say out loud.
Start with the journey below. It is the loop almost every change goes through. It takes you from an idea on your laptop to code that is live.
1
Start with a repository
A project folder that remembers its whole history. A copy lives on GitHub.
Your progress is saved in this browser. Mark a chapter as read at the bottom of each page.
Git tracks your code's history. GitHub is a website that stores your Git projects.
Git is a free tool on your computer. It remembers every version of your files. You can see what changed and when. You can go back if something breaks. It works offline.
GitHub is a website. It keeps a copy of your Git project online. It also adds teamwork tools. You can track tasks, review each other's code and run automatic checks.
Other sites do the same job: GitLab, Bitbucket and Codeberg. They all use Git. Only the extra tools differ.
Why bother, even if you work alone? You get two things.
History. An undo button for your whole project, not just one file.
Backup. If your laptop dies, your code is still on GitHub.
In a team, you also get one shared place where everyone's work meets.
Git does the tracking on your computer. GitHub keeps a shared copy online.
Your computer and GitHub only talk when you ask. You send work with push (upload). You get work with pull (download). Both come back in later chapters.
Four words you will hear in almost every Git sentence.
A repository (or repo) is a project folder plus its full history. Git hides the history in a folder called .git. Never edit that folder by hand.
A commit is a saved snapshot of your project. It has a short message that says what changed. It also has an author and a time. Each commit gets an ID called a hash, like a1b2c3d. Commits link together in a chain. That chain is your history.
A branch is a separate line of work. Technically it is a movable label on a commit. The main line is usually called main. You make other branches for new features and fixes.
A remote is a copy of the repo somewhere else, usually on GitHub. The main remote is normally named origin. The copy on your computer is the local repo.
A repo is a chain of commits. A branch is a label on one of them. A remote is another copy.
Two more words. To clone is to download a full copy of a repo. HEAD means "the commit you are on right now".
Twenty minutes of setup now saves you from lockouts and awkward mistakes later.
Step 1: turn on 2FA. 2FA (two-factor authentication) means you need a password and a second proof to log in. Your account can publish code that others run, so protect it. Use a passkey (a login stored on your phone or laptop) or an authenticator app. Save the recovery codes in a password manager.
Step 2: install Git and tell it who you are. Every commit (saved snapshot) carries your name and email.
Do not want your real email in public? GitHub gives you a private one. Go to Settings, then Emails. Tick "Keep my email addresses private". Use the address it shows (it ends in @users.noreply.github.com).
Step 3: let your computer prove who it is. GitHub does not accept your password for Git commands. Pick one way:
GitHub CLI. Run gh auth login. It signs you in through the browser. This is the easiest.
HTTPS with a credential manager. Git for Windows and Mac includes one. It opens a browser sign-in once.
SSH key. You make a pair of files with ssh-keygen -t ed25519. One file is private and stays on your computer. One is public. You paste the public one into GitHub under Settings, then SSH and GPG keys. After that you never type a password.
A personal access token (PAT) is like a password with limited powers. Scripts use it. Never paste it into code, chats or screenshots.
Three ways your laptop can prove who it is. Pick one.
You can start two ways: make a new repo, or copy one that already exists.
Way 1: start from scratch. Go into your project folder. Turn it into a repo:
Now you have a repo with one commit (saved snapshot). To put it on GitHub, first create an empty repo on the website. Do not add a README there. Then connect and upload:
git remote add origin git@github.com:you/my-project.git
git push -u origin main
The -u tells Git "remember this link". Later, plain git push just works. The GitHub CLI can do all of this in one step: gh repo create.
Way 2: copy an existing repo. Use clone (download a full copy, history included). It also sets up origin for you:
On any repo page, the green Code button shows the address to copy.
Two starting points, one result: a local repo linked to GitHub.
Git has a waiting room between your edits and your history. Knowing it clears up most early confusion.
Your changes live in three places. They move through them in order.
The working tree is your actual files. Edits here are not saved anywhere yet.
The staging area (also called the index) is a list of changes you picked for the next commit. git add puts changes there.
The repository holds your saved snapshots. git commit turns the staged changes into a commit.
Why the middle step? So one commit can hold one idea. Say you fixed a bug and also tidied a heading. Stage and commit the bug fix first. Then do the tidy-up as a second commit.
A change travels left to right. Only the last arrow uses the internet.
Two handy extras. git add -p lets you pick single chunks inside a file. git commit -a skips staging for files Git already knows. It ignores brand-new files.
A good commit is small, does one thing, and has a clear message.
Commit often. A commit (saved snapshot) is a save point. It is cheap. Small commits are easy to read, easy to undo and easy to search when hunting a bug.
One idea per commit. People call this an atomic commit. If your description needs the word "and", it is probably two commits.
Write the message for a stranger. Use a short first line, about 50 characters. Write it as a command: "Fix login redirect", not "Fixed". If needed, add a blank line and a longer note about why. The diff (the list of changed lines) already shows what changed. The message should explain why.
git commit -m "Fix redirect loop after login" -m "The cookie was set after the redirect, so the next request looked logged out."
Many teams use Conventional Commits. That means each message starts with a type: feat: (new feature), fix: (bug fix), docs: (docs only) or chore: (housekeeping). It makes history easy to scan. Being consistent matters more than the exact style.
Small commits with clear names beat one giant commit.
Do not commit broken code to a shared branch. Test first.
Do not mix tidying with logic. Reformatting in the same commit as a fix hides the fix.
Fix the last commit with git commit --amend. Only do this before you push (upload).
Three commands let you look before you change anything. They never alter your files.
git status # what changed, what is staged, which branch am I on
git diff # exact line changes not yet staged
git diff --staged # exact line changes that are staged
git log --oneline --graph --decorate -15
git status is the one to run all the time. It also tells you what to do next. For example: "Your branch is ahead of origin/main by 2 commits".
A diff shows changes line by line. A line starting with - was removed. A line starting with + was added. Read your own diff before every commit. It catches leftovers like debug prints, stray files and secrets.
git log lists commits, newest first. The options above show one line each and draw the branch shape. Add a filename to see the history of one file.
Two detective tools. git blame file shows who last changed each line. git show a1b2c3d shows one commit in full. On GitHub, the History and Blame buttons on a file do the same.
- const title = "Welcome back";
+ const title = "Hello again";
That is how a diff looks. One line out, one line in.
Almost everything in Git can be undone. You just need the right undo for your situation.
Pick your undo by asking: is it saved in a commit, and is it uploaded yet?
Situation
Command
Throw away edits to a file (not staged)
git restore file
Unstage a file but keep the edits
git restore --staged file
Fix the last commit (not pushed yet)
git commit --amend
Undo a commit that is already shared
git revert a1b2c3d
Move a local branch back, keep the changes
git reset --soft HEAD~1
Move a local branch back, delete the changes
git reset --hard a1b2c3d
Find a commit you think you lost
git reflog
The big one is revert versus reset.
git revert adds a new commit that cancels an old one. History stays intact. It is safe on shared branches.
git resetmoves the branch back. With --hard it also deletes work. Use it only on commits nobody else has.
Your safety net is the reflog. It is a private diary of everywhere HEAD (the commit you are on) has been. If you reset too far, run git reflog. Find the commit from a minute ago. Then run git reset --hard HEAD@{1} to return.
Commits are very hard to lose. Edits you never committed are gone for good. That is one more reason to commit often.
Some files should never go in a repo: secrets, downloaded libraries, build output and editor clutter.
A file named .gitignore lists patterns. Git pretends those files do not exist. Here is a typical one:
# downloaded libraries and build output
node_modules/
dist/
.next/
# secrets and local settings
.env
.env.*
!.env.example
# computer and editor noise
.DS_Store
.vscode/
Why these?
Libraries (node_modules) are huge. You can download them again from package.json.
Build output can be regenerated.
Secrets (passwords and API keys) must never be in a repo. The next chapter explains why.
The line !.env.example is an exception. It keeps a template file with fake values. Teammates see which settings they need.
The .gitignore file is a filter. Ignored files stay on your computer only.
GitHub can create a .gitignore for your language when you make a repo. The github/gitignore project has templates for everything.
If a password or key lands in a commit, assume it is stolen. Replace it. Do not just delete it.
A secret is any private credential. Examples: API keys, database passwords, tokens and private keys. Bots scan public GitHub all day. They can find a leaked key in minutes.
Why is deleting the file not enough? Git remembers. The secret stays in older commits. It also stays in forks (copies on other accounts) and clones (downloaded copies).
Do these steps in order:
Step 1 is the only one that really fixes the problem.
Rotate the secret first. Make a new key in the provider's dashboard. Disable the old one.
Store the new one safely. Use an .env file that is gitignored, or your host's secret settings.
Optionally clean the history with a tool like git filter-repo, then force-push (overwrite the remote branch). This is tidying, not protection.
Prevention is better. GitHub has secret scanning (it spots known key formats). It also has push protection (it blocks a push that contains one). Both are on by default for public repos. You can switch them on for private ones.
A branch is a safe side-road. Work there cannot break main until you choose to merge it.
A branch is just a label on a commit (a saved snapshot). That is why making one is instant and free. Make one for every feature, fix or experiment.
git switch -c feature/login # make a new branch and move to it
git switch main # go back
git branch # list your branches
git branch -d feature/login # delete a branch you finished with
Older guides use git checkout. It does the same job but mixes in unrelated jobs. switch is clearer.
When you switch branches, Git changes the files in your folder to match. Save your work first, or stash it (shelve it, see the stash chapter). Otherwise Git may refuse.
The feature branch splits off at B. Main moves on (C). The merge commit M joins both lines of work.
Your computer and GitHub only talk when you tell them to. Three commands do almost everything.
push uploads your new commits to GitHub: git push.
fetch downloads new commits but does not touch your files: git fetch. It is always safe.
pull is fetch plus a merge (combine) into your current branch: git pull.
Push goes up. Fetch and pull come down.
The first push of a new branch must say where it goes: git push -u origin feature/login. After that, plain git push works.
Git keeps a local bookmark of each remote branch. They are named like origin/main. They show where GitHub was the last time you looked. git status uses them to say "ahead by 2" or "behind by 1".
Sometimes a push is rejected. You may see "non-fast-forward" or "fetch first". It means GitHub has commits you do not have. Pull first. Fix any conflicts (clashing edits). Push again. Do not reach for --force.
Build a habit: pull before you start. Push when you finish a chunk. Unpushed commits exist only on your laptop.
Both bring one branch's work into another. They leave different history behind.
Merge ties two branches together with a new merge commit. Nothing is rewritten. History shows exactly what happened, including that work ran in parallel.
git switch main
git merge feature/login
If main has not moved since you branched, Git just slides the label forward. That is a fast-forward. No merge commit is needed.
Rebase replays your commits on top of the latest main. It looks as if you started from there. You get a straight line. But your commits get new IDs, because they now sit on a different parent.
git switch feature/login
git rebase main
Merge keeps both lines. Rebase copies your commits (D', E') onto the end of main.
The golden rule: never rebase commits that other people already have. It rewrites shared history. It forces everyone else to untangle their copies. Rebase your own private branch freely.
Merge
Rebase
History
Kept, with merge commits
Straight and tidy
Rewrites commits?
No
Yes
Safe on shared branches?
Yes
No
Good for
Joining finished work
Updating your own branch with the latest main
A conflict means two people changed the same lines. Git needs a human to choose.
Git merges (combines) automatically when changes touch different places. When both sides edited the same lines, it stops. It marks the file like this:
<<<<<<< HEAD
const title = "Welcome back";
=======
const title = "Hello again";
>>>>>>> feature/greeting
The part under <<<<<<< HEAD is your side. The part above >>>>>>> is theirs. Your job is to pick one, or mix them.
Five small steps. The marker lines must all be deleted.
Stuck? Run git merge --abort (or git rebase --abort). Everything goes back to how it was.
VS Code and GitHub's web editor show conflicts with buttons like "Accept current" and "Accept incoming". They are friendlier than editing markers by hand.
You get fewer conflicts if branches are short, PRs (pull requests, proposals to merge) are small, and you bring in main often.
Three little tools that rescue you from awkward moments.
Stash puts your unsaved changes on a shelf. You use it when you are mid-edit and must switch branches.
git stash # shelve changes, working tree is clean
git switch other-branch
# ...do the other job, then come back...
git stash pop # take the changes back off the shelf
A stash is a shelf for half-finished work.
A tag is a permanent name on one commit. Teams use it for releases, like v1.2.0. Unlike a branch, a tag never moves.
git tag v1.0.0
git push origin v1.0.0
Cherry-pick copies one commit onto another branch: git cherry-pick a1b2c3d. Use it when a fix landed on the wrong branch.
A branch name should say what the work is. A branch should not live long.
The default branch is where finished work ends up. New GitHub repos call it main. Older ones may say master. Treat it as always working.
Feature branches are the short-lived ones. Common names:
feature/login-page for new things
fix/cart-total-rounding for bugs
chore/update-deps for housekeeping
Add an issue number if you have one: fix/123-cart-rounding
Use lowercase and hyphens. No spaces. Be consistent.
A healthy branch lives for hours or days, not months.
Keep branches short. The longer a branch lives, the more it drifts from main. Then conflicts (clashing edits) get worse. A day or two is great. A month is a warning sign. Split big work into several small branches.
Delete branches after merging. GitHub shows a Delete branch button once a PR merges. The commits are safe in main. You can restore a deleted branch from the PR page. On your laptop, git fetch --prune clears bookmarks for branches that no longer exist on GitHub.
Every repo page has the same tabs. Knowing them makes any project easy to explore.
The tabs along the top of every repo.
A repo is either public (anyone can see it) or private (only people you invite). Public does not mean anyone can change it. Only collaborators (people you gave access) can push. Everyone else must ask through a fork (their own copy) and a pull request (a proposal to merge).
On the right you will see About (description and links), Releases and Languages. A star is a bookmark and a thumbs-up. Watch means you get notifications.
Handy keys on a repo page. Press t to search for a file. Press . to open the repo in an editor in your browser. Press ? to see all shortcuts.
The README is your project's front door. The licence says what others may do with your code.
A file called README.md in the repo root shows on the repo page. It is written in Markdown. Markdown is plain text with simple marks for headings (#), lists (-) and links. A good README answers four questions:
What is this? One or two sentences. Add a screenshot if it is visual.
How do I run it? Give the exact commands from a fresh clone (a fresh download).
How do I set it up? List the settings it needs. Never show the real secret values.
How do I help? Link to a contributing guide, if there is one.
Test your README on a clean machine. "It works on my machine" is the most common README bug.
A licence is a file called LICENSE. It says how others may use your code. With no licence, nobody else may legally reuse it. Even if it is public. Common choices:
Licence
In one line
MIT
Do almost anything. Keep the notice.
Apache 2.0
Like MIT, plus a promise about patents.
GPL
Anything built on it must also be open source.
GitHub can add a licence when you create the repo. The site choosealicense.com helps you pick.
Other files GitHub knows: CONTRIBUTING.md (how to send changes), CODE_OF_CONDUCT.md (how to behave) and SECURITY.md (how to report a vulnerability in private).
An issue is a written note about something to do. It might be a bug, a feature or a question.
Issues keep work out of people's heads and chat threads. Each has a number (#42), a title, a description and a comment thread. Its status is open or closed. You can assign it to someone. You can add labels (coloured tags like bug). You can add it to a milestone (a goal like "v1.0").
A good bug report has three parts:
What you expected and what happened.
Steps to reproduce. Numbered, so anyone can see the bug themselves.
A good feature request explains the problem first. Then it suggests a fix. Owners can agree with a clear problem even if they choose a different fix.
Two tricks make issues powerful. A mention: typing #42 links to that issue, and @name notifies a person. A closing keyword: write Fixes #42 in a pull request (PR, a proposal to merge). When the PR is merged, the issue closes by itself.
The issue and the code change are linked from start to finish.
Repos can add issue templates. These give reporters a form, so you get the details you need.
A pull request (PR) is a conversation about a branch. It says: "here is my change, please look before it joins main".
The name is backwards. You are asking the owners to pull your changes in. A PR page shows the difference between your branch and the target branch (the base). It shows the commits (saved snapshots). It has a place to talk. It shows the results of automatic checks.
The PR is the meeting point for your code, the checks and the reviewers.
To open one, push your branch. Then click the yellow "Compare and pull request" banner. Or run gh pr create. Fill in two things:
A title that says what the change does.
A description. Say why it is needed and what changed. Say how you tested it. Add screenshots for visual work. Add Fixes #42 to link the issue.
You can open a draft PR early. It shows work in progress. It cannot be merged until you click Ready for review. New commits you push to the branch appear in the same PR.
Keep PRs small and focused. A 100-line PR gets a careful review. A 2,000-line one gets a nervous "looks fine".
A review is a second pair of eyes on a change. Good reviews are about the code, not the person.
Open the PR's Files changed tab. Click the + next to a line to comment. When you are done, send the review with one of three verdicts:
Comment. Feedback, no decision.
Approve. Good to merge.
Request changes. Fix something first.
Review is a loop. It ends when the reviewer approves.
Reviewers can also write suggestions. These are small edits the author accepts with one click. A conversation can be marked resolved when it is dealt with.
As a reviewer, ask: Does it do what the PR says? Is anything missing, like tests? Could it break something else? Could it leak a secret? Be specific and explain why. Mark small stuff as a nit ("nit: rename this if you like").
As the author, reply to every comment. Push fixes. Do not take it personally. Review is how a team keeps quality up and shares knowledge.
Review AI-written code as hard as anyone's. It can sound sure and be wrong.
GitHub's green button offers three ways to land a PR. Each leaves different history.
What each green-button option leaves on main.
Method
What lands on main
Use it when
Create a merge commit
All the branch's commits, plus a merge commit.
You want a full record of how the work happened.
Squash and merge
One single commit with the whole PR inside.
The branch has messy "wip" commits. Main then reads as one commit per change. A popular default.
Rebase and merge
Each commit copied onto main. No merge commit.
The commits are already clean and you want a straight line.
Repo admins can turn off methods they do not want. So you might see fewer options.
After a squash, your branch's original commits are not in main. So delete the branch. Start a fresh one from the updated main.
Before you merge, check three things. Checks are green (automatic tests passed). The review is approved. The PR title reads well. With squash, the PR title becomes the commit message.
A fork is your own copy of someone else's repo on GitHub. It lets you suggest changes without needing permission to push.
On a public project you cannot push. So you do this:
Fork it. Click the Fork button. You now have you/project on GitHub.
Clone (download) your fork to your computer.
Make a branch. Commit. Push to your fork.
Open a pull request (a proposal to merge) from your fork to the original.
You never push to the original. You push to your fork and ask the owners to pull from it.
Your fork does not update by itself. Click Sync fork on your fork's page. Or add the original as a second remote (a second linked copy), named upstream:
Open an issue before big work. Do not spend a weekend on something they will decline.
Keep PRs small. Be patient. Owners are often volunteers.
A workflow is an agreement on how changes get from someone's laptop into main.
GitHub flow is the simplest. It is also the most common. It is the one this guide has taught so far:
GitHub flow. Main is always working.
main is always ready to ship.
Every change gets its own branch.
Every change goes through a PR (pull request, a proposal to merge).
Merge only when checks are green and a person approved.
Trunk-based development goes further. Branches live for hours, not days. Unfinished features hide behind a feature flag. A feature flag is a switch in the code that turns a feature on or off. It needs no new deploy. This suits teams with strong automatic tests.
Gitflow is older and heavier. It has long-lived develop, release and hotfix branches. Some teams that ship numbered versions still use it. Most web teams have moved to something simpler.
Whichever you choose, the ideas stay the same. Protect main. Keep changes small. Review before merging. Let automation do the boring checking.
Rules that stop anyone, even you on a tired Friday, from pushing straight to main or merging broken code.
Branch protection rules and the newer rulesets live in Settings. They set conditions a change must meet before it can land. Think of them as gates.
Each rule is a gate. Nothing reaches main unless it passes them all.
A typical set of rules:
Require a pull request. Nobody pushes directly to main.
Require approvals. For example, one reviewer must say yes.
Require status checks. A status check is a pass or fail result from an automatic job, like tests. Required checks must pass.
Require conversations to be resolved.
Block force pushes and deletion. A force push overwrites the branch.
Rules apply to everyone. You can allow named people to bypass them. Keep that list tiny.
Some options depend on your plan. Rules work on public repos for free. Private repos may need a paid plan. If the options are missing in Settings, check your plan.
Small files in your repo that send reviews to the right people and make every PR and issue consistent.
CODEOWNERS is a file at .github/CODEOWNERS. It maps parts of the repo to people or teams. When a PR (pull request) changes those files, GitHub asks the owner for a review. Branch rules can also require their approval.
# everything defaults to the core team
* @acme/core-team
# payments code needs a payments reviewer
/payments/ @acme/payments
# docs can be reviewed by the docs team
*.md @acme/docs
CODEOWNERS answers "who should look at this?" automatically.
A pull request template pre-fills the description box. It lives at .github/pull_request_template.md. You might add headings like "What changed", "How to test" and "Screenshots". Issue templates do the same for bug reports.
Together they cut down on "who should look at this?" and "what does this even change?".
Three ways to organise work on GitHub, from light to full planning board.
Labels are coloured tags on issues and PRs. Examples: bug, docs, blocked. Keep the set small.
Milestones group issues and PRs towards a goal or date, like "v1.0 launch". They show a progress bar.
GitHub Projects are boards and tables. They can span many repos. Cards are issues and PRs. Columns are stages like Todo, In progress and Done.
A Project board. Cards are issues and PRs. They move right as work progresses.
The usual loop: an idea becomes an issue (a tracked task). It goes on the board. Someone opens a PR that says Fixes #n. When it merges, the card moves to Done by itself.
Do not over-engineer this. A small team can live on issues and labels. Add Projects when you need an overview.
An organisation is a shared account for a group. The repos belong to the team, not to one person.
A personal repo lives at github.com/you/project. An organisation's lives at github.com/acme/project. If a personal repo's owner leaves, the project leaves too. That is why teams use organisations.
An organisation owns the repos. Teams get access to them.
Inside an organisation you make teams, like @acme/backend. You give teams access to repos. Roles on a repo, from least to most power:
Role
Can do
Read
View and clone. Comment on issues and PRs.
Triage
Read, plus manage issues and PRs (labels, assignees). No code changes.
Write
Push branches. Review and merge PRs.
Maintain
Write, plus manage the repo, without the sensitive settings.
Admin
Everything. Settings, secrets, deleting the repo.
Follow the rule of least privilege. Give people the lowest role that lets them do the job. Most people need Write at most.
For outside helpers, add them as an outside collaborator on one repo. Do not add them to the whole organisation.
Most team friction comes from habits, not tools. A few habits fix most of it.
Small PRs. Aim for something a reviewer can read in 15 minutes. If it is bigger, split it.
Describe your change. Say what and why. Say how to test it. Link the issue.
Review your own diff first. Read it on the Files changed tab before asking anyone else. You will spot leftovers.
Review promptly. A PR left waiting for days blocks the author and invites conflicts (clashing edits).
Be kind and specific. Comment on the code, not the person. Ask questions: "What happens if this is empty?"
Warn before you force-push. Do not do it to a branch someone is reviewing without saying so. It breaks their view of what changed.
Pull often. Build on the latest main.
Write decisions down. If it was decided in chat, put the result on the PR or issue.
Tune notifications. In Settings, Notifications. "Participating and @mentions" is a good default.
Actions run a script on GitHub's computers whenever something happens in your repo. For example, a push or a new PR.
The script lives in a workflow. A workflow is a text file in .github/workflows/. It is written in YAML. YAML is a simple config format that uses indentation. A workflow has these parts:
An event starts the workflow. The result lands on your PR.
Event. What starts it. Set with on.
Job. A group of steps.
Runner. The temporary computer GitHub lends to run a job.
Step. One task. Either a shell command (run) or a ready-made helper (uses).
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test
Read it as: on every PR and every push to main, download the code, install Node, install the libraries and run the tests. Results show on the PR and in the Actions tab. Click a run to read its log.
This is CI (continuous integration). It means every change is built and tested automatically. Problems show up before the merge. Pair it with a required check (a gate in your branch rules). Then broken code cannot reach main.
Other triggers: schedule (a timer, for nightly jobs), workflow_dispatch (a manual Run button) and release. Public repos run Actions free. Private repos get a monthly free allowance. Check the pricing page before heavy use.
Two safety habits. Set permissions: to the least the job needs. And treat other people's actions as code you are running. Prefer well-known ones. Check each action's page for its latest version.
Workflows often need passwords and keys. GitHub stores them encrypted, so they never go in your code.
Go to Settings, then Secrets and variables. You can add two kinds:
Secrets. Encrypted values, like deploy keys and API tokens. Once saved, you cannot read them again. You can only replace them. Use one as ${{ secrets.DEPLOY_TOKEN }}. GitHub hides it in logs.
Variables. Plain settings that are not private, like a site URL. Use one as ${{ vars.SITE_URL }}.
The workflow uses the secret by name. It never sees the value in your code.
An environment is a named target, like staging or production. Each can have its own secrets. You can also require a person to click approve before a deploy to production.
Two limits. Workflows started by a PR from a fork (someone else's copy) do not get your secrets. Otherwise a stranger could write a PR that steals them. And never echo (print) a secret in a step.
Better still, avoid stored keys. For big clouds, Actions can use OIDC. OIDC lets a workflow ask for a short-lived temporary token. There is no long-lived secret to leak.
Pages turns a repo into a free website. The same push-to-deploy idea works on other hosts too.
GitHub Pages hosts static sites. Static means plain files: HTML, CSS and JavaScript. It serves them from your repo at you.github.io/project or on your own domain. Switch it on in Settings, then Pages. It is free for public repos. Private repos need a paid plan.
If your site needs a build step (for example Astro or Vite), choose "GitHub Actions" as the source. If you need a server or a database, use a host that runs code.
On any host the pattern is push to deploy. Connect a host to your repo. Examples: Cloudflare, Vercel, Netlify, Render. Every merge to main then builds and publishes your site.
Merging is the button that ships. Previews let you check before you merge.
Many hosts also make a preview deployment for each PR. That is a throwaway web address showing that exact branch. Share it for review before you merge.
Tie the live deploy to main only. Make sure checks pass first.
A release is a named, numbered snapshot of your project that people can download or depend on.
A tag is a name on one commit, like v1.4.2. A GitHub Release sits on top of a tag. It adds a title, notes about what changed and optional files to download. Make one from the Releases section of the repo page. Or run gh release create v1.4.2. GitHub can write the notes from your merged PRs.
Most projects use semantic versioning (semver). The number has three parts: MAJOR.MINOR.PATCH.
Version 2.5.1 read left to right: major, minor, patch.
Bump
When
Example
PATCH
A bug fix. Nothing else changes.
1.4.2 to 1.4.3
MINOR
A new feature. Old things still work.
1.4.3 to 1.5.0
MAJOR
A breaking change. Users must adapt.
1.5.0 to 2.0.0
A changelog tells users what is new and what to watch for. It can be a CHANGELOG.md file or the release notes. A pre-release tag like v2.0.0-beta.1 says "for testing, not final".
A release and a deploy are different things. A release gives a version a name. A deploy puts some version on a server.
Most security problems in small projects come from old libraries and leaked keys. GitHub watches for both.
A dependency is a library your project uses. Libraries get bug reports and security holes over time. Dependabot is GitHub's helper for this.
Dependabot does the boring part. You review and merge.
Open the Security tab. These tools are free for public repos:
Dependabot alerts warn you about vulnerable libraries.
Dependabot security updates open a PR that upgrades the library.
Dependabot version updates open regular PRs to keep libraries fresh. You set them in .github/dependabot.yml. Small steady upgrades beat one giant upgrade.
Secret scanning and push protection catch leaked keys.
Code scanning (CodeQL) reads your code for common security mistakes.
Private vulnerability reporting lets people tell you about a hole in private.
A lockfile (like package-lock.json) records the exact library versions you use. Commit it. Still test Dependabot PRs. Green checks make them safe to merge fast.
AI tools can change dozens of files in seconds. Git is how you stay in control.
The risk is not that AI is always wrong. The risk is that changes are big and fast. They are hard to follow. A few habits make this safe:
Save, branch, edit, check, then keep or throw away.
Start clean. Run git status. Commit or stash first. Then the AI's changes are the only ones in the diff.
Use a branch.git switch -c try-new-header. If it goes wrong, delete the branch.
Commit after each working step. Each success is a save point.
Read the diff before you commit. Look for deleted files, changed settings, new libraries and anything that looks like a key.
Run it. "It compiled" does not mean "it works".
If it spirals, stop and reset.git restore . throws away uncommitted edits. New untracked files stay, so check git status. git reset --hard goes back to the last commit. A fresh start with a clear prompt beats ten rounds of patching.
Be careful with commands you do not understand. Especially ones that delete files, rewrite history (--force, reset --hard) or install unknown packages. Never paste secrets into prompts.
Coding agents can now open pull requests themselves. That makes the PR and its checks your main control point.
An agent is an AI tool that does a whole task by itself. Some agents read an issue, write code on a branch and open a PR. Examples: GitHub's Copilot coding agent and Claude Code. Treat its PR like one from a new teammate. It needs a clear description, passing checks and a human reading the diff.
You stay the editor. The agent is the writer.
Write clear issues. Say what you want, why, and how you will know it is done.
Keep tasks small. One task, one small PR.
Let checks review first. Tests and linters (tools that spot style and simple errors) catch many AI mistakes. No tests? Ask for them in the same change.
Look for typical AI mistakes. Made-up functions or libraries. Deleted tests. Copy-pasted code. Hard-coded values. Missing error handling.
Protect main. Branch rules mean an agent cannot push there directly. A human must approve.
Limit its access. Give tokens the least power and shortest life that works. Keep production secrets out of reach.
AI can also be a reviewer. Copilot code review and similar tools comment on PRs. They are an extra pair of eyes. They do not replace yours.
The gh command lets you do GitHub things from your terminal. No browser needed.
CLI means command-line interface: a tool you type commands into. gh is separate from Git. Git handles commits and branches. gh handles GitHub's website features: PRs, issues and releases. Install it. Then run gh auth login once.
Two tools, two jobs.
gh repo create my-app --private --source=. --push
gh pr create --fill # open a PR from your commit messages
gh pr status # your PRs and their checks
gh pr checkout 123 # fetch someone's PR to try it
gh pr merge --squash --delete-branch
gh issue create --title "Fix login bug"
gh run list # recent Actions runs
gh run view --log-failed # why did CI fail?
It is handy for scripts and AI tools, because the commands are plain text. Many coding agents use gh behind the scenes to open PRs and read test results.
You do not always need to install anything. GitHub can give you a full editor in a browser tab.
github.dev. Press . on any repo page. You get a light version of VS Code in your browser. Good for quick edits. It cannot run your app.
Codespaces. A full computer in the cloud, set up for your repo. It has a terminal and your tools. Open one from the Code button. There is a monthly free allowance. It stops when idle. Delete the ones you no longer need.
Dev containers. A file at .devcontainer/devcontainer.json that describes the setup. Everyone gets the same environment. That ends "works on my machine".
Your browser is just the window. The computer is in the cloud.
For a tiny fix like a typo, use the pencil icon on a file. GitHub offers to save it on a new branch and open a PR in one go.
Nearly every Git panic is one of these. None of them is the end of the world.
What you see
What it means and what to do
rejected ... (fetch first) or non-fast-forward
GitHub has commits you do not have. Run git pull (or git pull --rebase). Fix any conflicts. Push again.
CONFLICT (content): Merge conflict in file
Two edits touched the same lines. Open the file. Fix the marker lines. Run git add, then commit. Or run git merge --abort to go back.
You are in 'detached HEAD' state
You are on a single commit, not on a branch. Commits made here are easy to lose. To keep them: git switch -c new-name. To leave: git switch main.
I committed on main by mistake
Before pushing, run git branch fix/my-change. That saves the commit on a new branch. Then run git reset --hard origin/main while on main. Then switch to the new branch.
My commit message is wrong
Not pushed yet? Run git commit --amend. Already pushed? Leave it and move on.
Permission denied (publickey)
GitHub does not know your SSH key. Add the public key in Settings, SSH and GPG keys. Or use HTTPS with gh auth login.
Authentication failed over HTTPS
Passwords no longer work. Sign in with gh auth login, a credential manager or a token.
file exceeds GitHub's file size limit of 100 MB
Remove the file from the commit. Add it to .gitignore. If it truly must be tracked, use Git LFS (a tool for big files).
My changes are missing from git status
The file may be gitignored. Or you are in the wrong folder or branch. Check git branch and pwd.
Wrong name or email on a commit
Fix your git config for next time. For the last commit (not pushed): git commit --amend --reset-author.
I deleted a branch or lost commits
Run git reflog. Find the commit. Run git switch -c rescued a1b2c3d.
A word on --force. git push --force overwrites the branch on GitHub with yours. It can destroy other people's commits. If you must (for example after rebasing your own branch), use git push --force-with-lease. It refuses if someone else pushed meanwhile. Never force-push to main.
Pairs that sound alike but mean different things. Getting these right is where credibility shows.
Often confused
The difference
Git vs GitHub
The tool on your computer versus a website that hosts Git repos and adds teamwork.
Fork vs clone
A fork is a copy on GitHub under your account. A clone is a copy on your computer. You often do both.
Fork vs branch
A branch is a line of work inside one repo. A fork is a whole separate repo.
Fetch vs pull
Fetch downloads without touching your files. Pull is fetch plus a merge.
Commit vs push
Commit saves a snapshot on your computer. Push uploads commits to GitHub.
Merge vs rebase
A merge joins histories with a merge commit. A rebase copies your commits onto the end, with new IDs.
Revert vs reset
Revert adds a new commit that undoes an old one. Reset moves the branch back.
Pull request vs merge request
Same thing. GitHub says pull request. GitLab says merge request.
Issue vs pull request
An issue describes a problem or idea. A PR holds code to deal with it.
Tag vs release
A tag names a commit. A release is a GitHub page built on a tag, with notes and files.
Release vs deploy
Naming a version versus putting a version on a server.
CI vs CD
CI tests every change automatically. CD ships it automatically.
Secret vs variable (in Actions)
Secrets are encrypted and hidden. Variables are plain settings that are not private.
Private vs public repo
Only people you invite can see it, versus everyone on the internet.
Star vs watch vs fork
Bookmark, get notifications, copy to your account.
origin vs upstream
Origin is where you cloned from (often your fork). Upstream is the original project you forked.
Putting it together: the same way of working, said three ways.
Engineers describe a workflow in order. They name each step and its safeguard. Here is a solo project at three levels of detail.
The one-liner:
The thirty-second version:
The team answer, if someone asks about safety:
Notice the pattern: where work happens, how it is checked, how it lands, how it ships, and what stops mistakes. That works for any team.
The commands you will use ninety percent of the time, in roughly the order you need them.
The daily loop in five commands.
Set up
Command
What it does
git clone <url>
Download a repo.
git init
Turn the current folder into a repo.
git remote -v
Show linked copies and their addresses.
git config --global user.name "Name"
Set who your commits are from.
Daily loop
Command
What it does
git status
What changed, and which branch you are on.
git pull
Get the latest from GitHub.
git switch -c name
Make a new branch and move to it.
git add file / git add -p
Stage a file / stage chosen chunks.
git commit -m "message"
Save the staged changes as a commit.
git push -u origin name
Upload a new branch (first time).
git push
Upload new commits.
Look around
Command
What it does
git diff / git diff --staged
See unstaged / staged changes.
git log --oneline --graph
Short history with the branch shape.
git show <hash>
One commit in full.
git blame file
Who last changed each line.
git branch
List branches.
Combine and undo
Command
What it does
git merge name
Merge a branch into the current one.
git rebase main
Copy your branch onto the end of main.
git stash / git stash pop
Shelve / bring back unsaved changes.
git restore file
Throw away unsaved edits to a file.
git restore --staged file
Unstage a file.
git commit --amend
Fix the last commit (before pushing).
git revert <hash>
Undo a commit with a new commit.
git reflog
Find commits you thought were lost.
git merge --abort / git rebase --abort
Back out of a merge or rebase.
GitHub CLI
Command
What it does
gh auth login
Sign in once.
gh pr create --fill
Open a PR from the current branch.
gh pr checkout 123
Try someone else's PR on your computer.
gh pr merge --squash
Merge a PR as one commit.
gh run view --log-failed
See why CI failed.
Quick definitions. Filter to jump to a term.
Actions (GitHub Actions)
GitHub's built-in automation that runs workflows when things happen in a repo.
Amend
Rewrite the most recent commit, for example to fix its message. Only before pushing.
Base branch
The branch a pull request will be merged into, usually main.
Blame
A view showing who last changed each line of a file, and in which commit.
Branch
A movable label pointing at a commit, used as a separate line of work.
Branch protection
Rules that must be met before changes can land on a branch, such as reviews and passing checks.
Check / status check
An automated pass or fail result shown on a pull request.
Cherry-pick
Copy a single commit from one branch onto another.
CI / CD
Continuous integration (test every change) and continuous delivery or deployment (ship it).
Clone
Download a full copy of a repo, history included.
CODEOWNERS
A file mapping parts of a repo to the people or teams who review changes there.
Collaborator
A person given access to a repo that isn't theirs.
Commit
A saved snapshot of the project with a message, author and unique hash.
Conflict
Two changes touching the same lines, which Git asks a human to resolve.
Conventional Commits
A naming style for commit messages such as feat: or fix:.
Dependabot
GitHub's tool for alerting you about vulnerable dependencies and opening upgrade PRs.
Dependency
A library or package your project relies on.
Detached HEAD
Being on a specific commit rather than on a branch.
Diff
A line-by-line view of what changed between two versions.
Draft PR
A pull request marked as work in progress that can't be merged yet.
Environment
A named deploy target, such as staging or production, with its own secrets and rules.
Fast-forward
A merge where Git just moves the branch pointer forward because nothing diverged.
Feature flag
A switch in code that turns a feature on or off without redeploying.
Fetch
Download new commits from a remote without changing your files.
Fork
Your own copy of someone else's repo on GitHub.
Force push
Overwriting a remote branch with your version. Use --force-with-lease if you must.
Git LFS
Git Large File Storage, for tracking big files without bloating the repo.
.gitignore
A file listing patterns Git should not track.
GitHub flow
Branch off main, open a PR, review, merge, deploy. The simplest team workflow.
Hash
The long ID of a commit, often shortened to seven characters.
HEAD
The commit you're currently on, normally the tip of your current branch.
Index
Another name for the staging area.
Issue
A tracked bug, task or idea with a number and discussion thread.
Label
A coloured tag on issues and PRs for sorting and filtering.
Licence
A file stating how others may use your code.
Local
The copy of a repo on your own computer.
Lockfile
A file recording the exact dependency versions installed, which should be committed.
main
The conventional name for a repo's primary branch.
Markdown
Simple text formatting used in READMEs, issues and PR descriptions.
Merge
Combine one branch's changes into another.
Merge commit
A commit that joins two branches together.
Milestone
A group of issues and PRs aimed at a goal or date.
Organisation
A shared GitHub account that owns repos on behalf of a group.
origin
The default name for the remote you cloned from.
OIDC
A way for workflows to get short-lived cloud credentials instead of storing keys.
Passkey
A phishing-resistant login stored on your device that replaces a password.
PAT
Personal access token, a password-like string with limited powers for tools and scripts.
Pull
Fetch from a remote and merge the new commits into your current branch.
Pull request (PR)
A proposal to merge one branch into another, with discussion, review and checks.
Push
Upload your local commits to a remote.
Push protection
A GitHub feature that blocks pushes containing detected secrets.
Rebase
Replay your commits on top of another branch, giving them new IDs.
Reflog
A local log of where HEAD has been, used to recover lost commits.
Release
A named, versioned snapshot on GitHub, with notes and optional files.
Remote
A copy of the repo hosted elsewhere, usually on GitHub.
Repository (repo)
A project folder together with its full history.
Reset
Move a branch pointer to another commit, possibly discarding changes.
Revert
Create a new commit that undoes an earlier one.
Review
Feedback on a pull request, ending in comment, approve or request changes.
Ruleset
GitHub's newer, more flexible alternative to branch protection rules.
Runner
The machine that executes a GitHub Actions job.
Secret
A credential such as an API key or password, which must never be committed.
Secret scanning
GitHub's detection of leaked credentials in a repo.
Semver
Semantic versioning, MAJOR.MINOR.PATCH, signalling how big a change is.
Squash
Combine many commits into one, often when merging a PR.
SSH key
A key pair used to prove who you are to GitHub without a password.
Stage
Choose changes to include in the next commit with git add.
Star
A bookmark and show of appreciation for a repo.
Stash
Shelve uncommitted changes temporarily.
Tag
A permanent label on a commit, often a version number.
Trunk-based development
A workflow with very short-lived branches and frequent merges to main.
Upstream
The original repo you forked, or the remote branch your local branch tracks.
Workflow
A YAML file in .github/workflows that defines automation.
Working tree
The actual files in your project folder that you edit.
YAML
Indentation-based config format used by Actions and many other tools.