Git & GitHub 101 · Hands-on workshop

$git init your dev journey

Git & GitHub 101, from first commit to a live portfolio.

Arlou Beloria

Current quests

  1. 01The story of Git and GitHub15 min
  2. 04Work with an existing repository40 min
  3. 02Connect your machine to GitHub15 min
  4. 05Ship your portfolio25 min
  5. 03Your first repository25 min
  6. 06The Git cheat sheet10 min + Q&A

Every command group ends with an activity, and every activity leaves you with something to keep.

Pre-flight check

  • [ ]Git installed (Windows: Git Bash comes with it)
  • [ ]GitHub account created and signed in
  • [ ]VS Code installed, and code . opens the current folder from the terminal
  • [ ]Terminal open: Git Bash on Windows, Terminal on Mac
$ git --version # anything 2.x is fine
$ code . # VS Code opens
# not found? Mac: Cmd+Shift+P → "Shell Command: Install 'code' command in PATH"
# Windows: reinstall VS Code with "Add to PATH" checked, then reopen Git Bash

Part 01 / 06

The story of Git and GitHub

What version control is, who made Git, and how GitHub fits in.

The folder-of-copies problem

Which one is the real one? What changed between v2 and v3? Who broke it?

Version control answers all three, with one folder.

project.zip
project_v2.zip
project_v2_FINAL.zip
project_v2_FINAL_fixed.zip
project_v3_USE_THIS_ONE.zip

The analogy

Git is a video game save state for your code.

Mess up the boss fight? Reload your previous save. Every commit is a save point you can return to.

Who made Git

Linus Torvalds, in 2005, after the Linux kernel team lost access to the tool they were using. He wrote a replacement in about two weeks.

~2 wks

to write the first working version

2005

released, still the standard 20+ years on

"git"

British slang; he named it after himself, as a joke

Git vs. GitHub

The tool

Git

Runs on your laptop. Tracks every change. Works offline.

The place

GitHub

A website that hosts Git repositories. Backup, sharing, collaboration.

You can use Git without GitHub. You cannot use GitHub without Git.

ACTIVITY

Introduce yourself to Git

Git signs every snapshot with a name and email. Set yours once and never again.

01Tell Git your name$ git config --global user.name "Your Name"
02Tell Git your email$ git config --global user.email "you@example.com"same email as your GitHub account
03Call the first branch main$ git config --global init.defaultBranch main
04When pulling, merge (we need this in Part 04)$ git config --global pull.rebase false
05Check your answers$ git config --list

Checkpoint: paste your git --version output in the chat.

Part 02 / 06

Connect your machine to GitHub

An SSH key is a password your computer types for you.

Two keys, one handshake

Private key

Stays on your laptop. Never share it, never paste it anywhere.

⇄

Public key

Goes to GitHub. When you push, the two keys shake hands.

No passwords to type, no tokens to expire mid-workshop.

Four ways to prove it is you

SSH key

Tonight's path. One key pair per machine, set up once, never typed again.

HTTPS + token

Clone over https:// and paste a Personal Access Token where it asks for a password. Works where the network blocks SSH.

gh auth login

The GitHub CLI walks you through it in the terminal and stores the credential for you.

Desktop or VS Code

Sign in inside the app once and it handles the credential silently. No terminal needed.

Your own laptop: SSH. A work or shared machine: HTTPS with a fine-grained token you can revoke.

ACTIVITY

Generate and register your key

01Create the key pair$ ssh-keygen -t ed25519 -C "you@example.com"press Enter three times
02Print the public key and copy the whole line$ cat ~/.ssh/id_ed25519.pubstarts with ssh-ed25519
03Give it to GitHubGitHub → Settings → SSH and GPG keys → New SSH key → paste
04Test the handshake$ ssh -T git@github.comtype yes when asked

Checkpoint: "Hi username! You've successfully authenticated." Windows: run everything in Git Bash, not PowerShell.

Part 03 / 06

Your first repository

Turn a folder into a repository, take your first snapshot, publish it as your profile.

The loop: edit, stage, commit, check

01

Edit

Change files like you always do.

02

Stage

Pick what goes in the snapshot.

03

Commit

Save it with a message.

04

Check

Read the history back.

add puts items in the shopping cart. commit checks out. Nothing is saved to history until you commit.

The four commands

$ git init # turn the folder into a repository
$ git status # what changed since the last snapshot
$ git add . # stage everything, or name one file
$ git commit -m "Describe the change" # take the snapshot
$ git log --oneline # history, one line per commit

Create the repository on GitHub

  • 01The + button top right, then New repository. Or github.com/new.
  • 02Repository name: exactly your username, capitals do not matter. GitHub shows a "special repository" banner when it matches. No banner, typo.
  • 03Description: skip it tonight.
  • 04Visibility: Public. A private profile README shows to nobody.
  • 05Configuration: touch nothing. No template, README off, no .gitignore, no license. Each one writes a commit your laptop does not have, and your first push is rejected.
  • 06Create repository. The empty page that follows lists the remote-add and push lines you are about to type. Flip its toggle to SSH before copying.
GitHub's Create a new repository form, with Public visibility and every configuration option left off

Ticked something anyway? Delete the repository and create it again, or run git pull origin main --allow-unrelated-histories once before you push.

ACTIVITY

Your GitHub profile README

On GitHub: New repository named exactly your-username, Public, README off, no .gitignore, no license (previous slide). Then:

01Make a folder and go inside$ mkdir your-username && cd your-username
02Turn it into a repository$ git init
03Write your README$ code README.mdor nano README.md; sample on the next slide
04Stage and commit it$ git add README.md && git commit -m "Add profile README"
05Link it to GitHub$ git remote add origin git@github.com:your-username/your-username.git
06Push$ git push -u origin main-u once; plain git push after this

Checkpoint: refresh github.com/your-username. Your README is live at the top of your profile.

Markdown cheat sheet

# Heading 1Heading 1 ## Heading 2Heading 2 **bold** and *italic*bold and italic - item• item (bulleted list) 1. item1. item (numbered list) [text](https://url)text ![alt](image.png)an image `code`code ---

Full reference: markdownguide.org/cheat-sheet

Part 04 / 06

Work with an existing repository

Clone, break, undo, branch, and send a pull request.

Fork, clone, origin, upstream

Fork

A copy on GitHub, under your account. In the browser, once. You cannot push to a repo you do not own.

Clone

A copy on your laptop. In the terminal, as often as you like. Clone your fork, never the original.

origin

The remote your clone came from: your fork. Where git push goes when you do not say otherwise.

upstream

The name everyone gives the original. Not there by default; you add it, and you only pull from it.

Your fork does not update itself. Sync before you branch. Every time.

Fork it on GitHub

  • 01Open github.com/Arlovzki/git-github-101. The Fork button sits in the repo header, top right, between Watch and Star.
  • 02The dialog. Owner is you, the name stays git-github-101, and "Copy the main branch only" stays ticked. Create fork.
  • 03GitHub copies the repository into your account: github.com/your-username/git-github-101. That copy is origin from now on. The original is upstream, and you only ever pull from it.
  • 04Fork once, in the browser. Clone as often as you like, in the terminal, and always clone your fork: you cannot push to a repository you do not own.
The GitHub repository header for Arlovzki/git-github-101, with the Fork button between Watch and Star

Fork lives in the repo header, top right

The Create a new fork dialog: owner, repository name, description, and the checkbox for copying the main branch only

Leave "Copy the main branch only" ticked

ACTIVITY

Fork and clone the workshop repo

Open github.com/Arlovzki/git-github-101 and press Fork. Then clone your fork, not the original:

01Leave the profile repo$ cd ..
02Download your fork$ git clone git@github.com:your-username/git-github-101.git
03Go inside$ cd git-github-101
04The history came with it$ git log --oneline
05Where does origin point?$ git remote -vat your fork

Checkpoint: git log shows commits you did not write. That is the whole point of cloning.

Three ways to undo

restore

For files. Throw away edits you have not committed.

reset

For commits only you have. Rewinds history on your machine.

revert

For commits others may have pulled. Adds a new commit that reverses one.

Never reset --hard on a shared branch.

ACTIVITY

Break it, then undo it

01Undo an edit you have not staged

$ echo "oops" >> README.md
$ git diff # or click the file in VS Code's Source Control
$ git restore README.md

02Undo a commit, keep the work

$ echo "draft" >> notes.md
$ git add . && git commit -m "wip"
$ git reset --soft HEAD~1

03Undo a commit, discard the work

$ git add . && git commit -m "wip again"
$ git reset --hard HEAD~1
# careful: the work is gone

04Undo a commit that is already pushed

$ git revert HEAD --no-edit
# a new commit that reverses the last one
$ git reset --hard origin/main
# experiment over: back to your fork's main

Watch the Source Control icon on VS Code's left sidebar: the badge counts changed files, and it drops to zero after each undo.

Branches: main always works

main feature git switch -c feature git merge feature commits on the branch

A branch is an alternate timeline. Experiment there, then merge it back or throw it away.

A pull request is how you ask for the merge, with review.

$ git switch -c feature # new branch
$ git switch main # go back
$ git merge feature # bring it in
$ git branch # where am I?

Naming branches

type/short-description

A type that says what kind of change it is, a slash, then a short description in lowercase words joined by hyphens. The name tells a stranger what is on the branch before they open it.

feature/

Something new. A page, a section, a file that did not exist before. Some teams write feat/; one repo picks one.

fix/

Something repaired. A bug, a dead link, a typo that changes meaning. hotfix/ is the urgent kind, straight to production.

docs/

Only the words changed. README, comments, a guide like the workshop page.

chore/

Housekeeping. Dependencies, config, renames. Nothing a user of the project would notice.

feature/add-alex-to-attendees
fix/pages-404
fix/42-pages-404 # with the issue number
docs/readme-setup-steps
chore/bump-dependencies
patch-1 # GitHub's default, says nothing
my-stuff # whose, and what stuff?
Alex_Branch_FINAL # caps, underscores, FINAL

Nobody rejects a pull request over a bad branch name. They do lose ten minutes working out what it was for, and one of those ten minutes is yours.

Naming branches: the rules

  • 01One topic per branch. If you cannot name it in a few words, it is two branches. A branch that fixes the 404 and restyles the header is merged as one thing, and the reviewer who wanted only the fix cannot take it.
  • 02What Git itself refuses: spaces, .., any of ~ ^ : ? * [ \, a leading dot, a name ending in / or .lock. Everything else is convention.
  • 03The trap. A branch called feature blocks every feature/... branch: Git stores branches as files inside folders, and a file cannot also be a folder.
  • 04Local only, like my-notes in the next activity? The name barely matters. Pushed? It is documentation: the pull request, git branch -r on everyone's laptop, and the history of who did what.
  • 05Match the repo. git branch -r shows how they name theirs. If they use feat/, you use feat/. If they put the ticket number first, so do you. A consistent log beats a correct one.
  • 06Branches are temporary. main is the only one that lives forever; develop or release/1.2 are the exceptions, not the pattern. Everything else is deleted the moment it merges. Forty stale branches, and nobody can tell what is still in progress.

Lowercase, hyphens, no spaces. The rest is whatever the team you join already does.

Commit messages: the rule every repo shares

if applied, this commit will Add Alex to attendees

  • 01Imperative mood. Finish the sentence above. Not "added", not "adding": Add.
  • 02Under 50 characters for the bare sentence, and no full stop at the end.
  • 03Why imperative? Git writes its own messages that way: Merge branch 'main', Revert "Add banner". Match it and the log reads in one voice instead of two.
  • 04Need to explain why? Blank line after the subject, then a body wrapped around 72 characters. Most commits do not need one. The ones that do are the ones you will thank yourself for.
Add Alex to attendees # says what changed
Fix the 404 on the Pages deploy
Remove the debug logging from index.html
update # update what?
asdf # bad
final FINAL fix pls workand you will read it again in a year

A commit subject is a sentence about the change, not a diary entry about your afternoon.

Conventional commits

type(scope): short description

type: one word from the list. (scope): the part you touched, optional. description: the same imperative sentence, now lowercase, still no full stop. Keep the whole first line under 72 characters; GitHub cuts it off there.

feata new feature minor 1.1.0 fixa bug fix patch 1.0.1 feat!a breaking change, any type major 2.0.0 docsdocumentation only styleformatting, whitespace; same behaviour refactorneither a fix nor a feature perfa change that makes it faster testadding or correcting tests buildthe build system or dependencies cithe pipeline configuration choremaintenance nothing else covers revertundoes a commit; name it in the body
type(scope): short description
 
optional body: what and why, wrapped at 72
Closes #12
BREAKING CHANGE: what the reader must change
feat: add Alex to attendees
fix(pages): serve index.html from the root
docs: add the setup steps to the README
feat!: drop support for Node 18

Conventional Commits keeps the imperative sentence and puts one lowercase word in front of it, so that a machine can read the log as well as a person.

Conventional commits: good, bad, and why

feat: add Alex to attendees
fix(pages): serve index.html from the rootthe scope says where
docs: add the setup steps to the README
chore: bump dependencies
feat!: drop support for Node 18the ! warns everyone up front
Fixed bug # no type, past tense, which bug?
feat: stuff # right type, empty sentence
FEAT: Add Login Page. # caps and a full stop
  • 01git log --oneline reads like a changelog instead of a diary.
  • 02commitlint rejects a message that breaks the shape before it lands.
  • 03release-please and semantic-release pick the next version number from the types and write the release notes for you: fix = patch, feat = minor, ! or BREAKING CHANGE = major.
  • 04This site's own history is written that way: feat(codex): host the workshop page, fix(sprite): laptop faces the engineer, build(deck): make the .pptx reproducible. Open any popular repository and you will find the same shape.

Nobody sits down to write the changelog. The log already is one.

Conventional commits: match the repo

  • 01Read the log first. git log --oneline -20 before your first commit. Types there? Use them. None? Plain subjects like "Add Alex to attendees" are that repo's convention, and a lone feat: in the middle of them looks wrong. Never mix the two in one repo.
  • 02Tonight. The workshop repo uses plain subjects, so tonight's activities do too.
  • 03Squash merges read the title. GitHub uses the pull request title as the commit message on main by default. On a Conventional Commits repo the title needs the type too: feat: add Alex to attendees, not the branch name GitHub suggests.
  • 04Footers close the message. Closes #12 links the issue and closes it on merge. BREAKING CHANGE: says what the reader must now change, the same signal as the ! after the type.
  • 05The specification is one page: conventionalcommits.org. The types beyond feat and fix come from the Angular convention, which most teams and @commitlint/config-conventional use.

One repo, one convention. A consistent log beats a correct one.

ACTIVITY

Branch, then merge it yourself

Still inside git-github-101. Write notes on a branch, then merge them into main on your own machine.

01Create a branch and move onto it$ git switch -c my-notes
02Write a note$ echo "Today I learned: git restore undoes edits" >> notes.md
03Commit it on the branch$ git add . && git commit -m "Add my notes"
04Go back to main$ git switch mainnotes.md is gone; it lives on the branch
05Bring the branch into main$ git merge my-notes
06Delete the branch$ git branch -d my-notesthe work is already in main

Checkpoint: git log --oneline shows "Add my notes" on main. Nothing pushed yet; that comes next.

Anatomy of a pull request

  • 01Title. The commit subject for the whole branch. Imperative and specific.
  • 02Description. What changed and why. Do not make them read the diff to find the point.
  • 03Closes #12. Links the issue, and closes it the moment this merges.
  • 04Draft. Open it early to get eyes on the direction before you have finished.
  • 05Small. A diff a reviewer can hold in their head gets reviewed today.

Read your own Files changed tab first. GitLab calls the same thing a merge request.

ACTIVITY

Branch, then open a pull request

Same idea, on a repo you do not own: the merge happens on GitHub through a pull request. Add yourself to the attendance folder.

01Create a branch$ git switch -c feature/add-your-name
02Create your own file: name and a link$ code attendees/your-username.mdone file each, so nobody's pull request collides
03Commit it$ git add attendees && git commit -m "Add Alex to attendees"
04Push the branch to your fork$ git push -u origin feature/add-your-name
05Open the pull requestGitHub banner: Compare & pull request → Create pull request

Checkpoint: your pull request appears on Arlovzki/git-github-101. I merge them live, one by one.

Open the pull request

  • 01Push the branch, then open your fork on GitHub. For a few minutes it shows a banner: Compare & pull request. Press it. Missed it? Pull requests tab, then New pull request.
  • 02Check the header first. Base is Arlovzki/git-github-101 on main; compare is your fork on your branch.
  • 03Title: the commit subject for the whole branch, imperative and specific. Description: what and why, and Closes #12 when there is an issue.
  • 04Create pull request. The arrow beside it offers a draft, for when you want eyes on the direction before you have finished.
  • 05Then read your own Files changed tab before anyone else does.
The Compare and pull request banner GitHub shows on a fork in the minutes after a branch is pushed The Open a pull request form: base and head branch selectors, the title field, the description box, and the Create pull request button with its draft option

Three ways to merge

The green button has a dropdown. The three options do different things to history, and the repo lives with whichever you pick.

  • Create a merge commit. Keeps everything: every commit on the branch lands on main, plus one merge commit tying them together. Your six wip commits included. GitHub's default.
  • Squash and merge. One branch, one commit. The whole branch collapses into a single commit on main. Clean, linear history; the messy branch history goes with it.
  • Rebase and merge. Linear, every commit kept. Each commit is replayed onto main with no merge commit. Reads beautifully, but only if every commit was worth keeping.
merge commitmain M ── ── ── ── mergeevery commit, plus one squashmain M ── ABCone pull request, one commit rebasemain M ── A' ── B' ── C'linear, kept, new IDs
The merge box at the foot of an open pull request: the green Merge pull request button and the dropdown offering squash and rebase

The dropdown beside the green button is where the choice lives

Unsure? Squash. One pull request becomes one commit on main. Never rebase a branch other people have already pulled: use git revert there instead.

ACTIVITY

Pull everyone's names

Your fork only knows your name. The original repo now has the whole room's. Pull the update from there.

01Add the original repo as a second remote$ git remote add upstream git@github.com:Arlovzki/git-github-101.git
02Check: origin is your fork, upstream is mine$ git remote -v
03Make sure you are on main$ git switch main
04Pull everyone's commits$ git pull upstream mainthen ls attendees
05Bring your fork up to date too$ git push
UPSTREAM · GITHUBArlovzki/git-github-101 ORIGIN · GITHUByou/git-github-101 LOCAL · LAPTOP~/git-github-101 git pull upstream main git push pull request

Checkpoint: the attendees folder has one file per person in the room. This is how teams work every day: branch, pull request, merge, pull.

Part 05 / 06

Ship your portfolio

Clone a starter site, make it yours, put it on the internet for free.

ACTIVITY

Make the starter yours

Fork github.com/Arlovzki/portfolio-starter, then on your fork: Settings → Rename to portfolio. Then:

01Leave the workshop repo$ cd ..
02Download your portfolio$ git clone git@github.com:your-username/portfolio.git
03Go inside and open it in VS Code$ cd portfolio && code .
04Edit index.htmlchange the name, the one-line bio, and the GitHub link
05Commit$ git add . && git commit -m "Make the portfolio mine"
06Push$ git push

Checkpoint: your changes show on github.com/your-username/portfolio.

ACTIVITY

Deploy to GitHub Pages

  1. 01Repo Settings → Pages → Source: Deploy from a branch → main / (root) → Save
  2. 02Wait about a minute, then refresh the same page: the live URL appears at the top with a Visit site button
  3. 03Open your sitehttps://your-username.github.io/portfolio
The GitHub Pages settings screen with Source set to Deploy from a branch, the main branch selected, and the root folder selected

Settings → Pages → Deploy from a branch → main / (root)

The GitHub Pages settings screen after deployment, showing the live site URL with a Visit site button

A minute later the live URL appears at the top of the same screen

Checkpoint: drop your live link in the chat. Free hosting, forever.

Every future update

$ git add .
$ git commit -m "..."
$ git push

Pages rebuilds on its own. That loop is the whole job.

Part 06 / 06

The Git cheat sheet

Every command from tonight, in the order you will reach for it.

Bookmark it: www.arloubeloria.com/workshop/git-github-101

Once

git config --global user.name "..."
git config --global user.email "..."
git config --global init.defaultBranch main
git config --global pull.rebase false
ssh-keygen -t ed25519 -C "..."
ssh -T git@github.com

Every day

git status # what changed
git diff # see the changes
git add . # stage
git commit -m "message" # snapshot
git push # send
git pull # receive
git log --oneline # history

Per project

git init # new repo here
git remote add origin git@github.com:user/repo.git
git push -u origin main # first push
git clone git@github.com:user/repo.git # copy an existing repo
git remote -v # where do I push?
git remote add upstream git@github.com:owner/repo.git
git pull upstream main # get the original's updates

Branches and undo

git switch -c name # new branch
git switch main # go back
git merge name # bring it in
git branch # list · -d name deletes
git restore file # undo unstaged edits
git reset --soft HEAD~1 # uncommit, keep work
git reset --hard HEAD~1 # uncommit, discard work
git revert HEAD # undo a pushed commit
git reflog # everything you ever did
git blame file # who last touched each line

Keep going

Slides, cheat sheet, and every resource, in one place:

www.arloubeloria.com/workshop/git-github-101

Running your own workshop someday? Fork all of it and use it as your starting point.

Questions?

← workshop page · ARROWS MOVE · CLICK A COMMAND TO COPY · C COPIES THE SLIDE · F FULLSCREEN
01 / 45