Skip to content

Git and GitHub

Git is a tool that keeps track of every change you make to your files over time. It is called a version control system. Think of it like a save button that remembers every single version of your project, so you can always go back to an older version if something breaks.

Git runs on your own computer. It does not need the internet to work, because it saves everything in a hidden folder called .git inside your project.

Keeps history

Every change you save is stored. You can look back at old versions or undo mistakes any time.

Safe experiments

You can try new ideas in a separate “branch” without touching your main, working code.

Works with a team

Many people can work on the same project at the same time without overwriting each other’s work.

Backup and sharing

When you push your code to GitHub, it is safely stored online and easy to share with others.

GitHub is a website where you can store your Git projects (called repositories, or “repos” for short) online. It adds extra features on top of Git, like:

  • A place to see and review other people’s code changes (pull requests)
  • A way to track bugs and to-do items (issues)
  • Tools to automate testing and deployment (GitHub Actions)
  • A way to work together with a whole team, safely

Git saves your project as a series of snapshots (called commits), not just a list of changes. Each commit points back to the commit before it, forming a chain of history.

graph LR A["Working Directory - your files"] -->|git add| B["Staging Area - ready to save"] B -->|git commit| C["Local Repository - saved history"] C -->|git push| D["Remote Repository - GitHub"] D -->|git pull / git fetch| C
  • Working Directory: The actual files you see and edit on your computer.
  • Staging Area: A waiting area where you choose exactly which changes you want to save next.
  • Local Repository: The saved history of commits, stored in the .git folder on your computer.
  • Remote Repository: A copy of your project stored somewhere else, like on GitHub.
  1. Install Git: Download it from the official Git website and follow the steps for your computer (Windows, macOS, or Linux).

  2. Check that it installed correctly:

    Terminal window
    git --version
  3. Set up your name and email: Git attaches your name and email to every commit you make, so people know who made each change.

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

    The --global flag means these settings apply to every project on your computer. If you want different settings for just one project, run the same commands without --global while inside that project’s folder.

  4. Check your settings any time:

    Terminal window
    git config --list
Terminal window
git init

This turns the current folder into a Git repository by creating a hidden .git folder. From now on, Git will start tracking changes in this folder.

If a project already exists on GitHub, you can download a full copy, including all its history, using git clone.

Terminal window
git clone https://github.com/username/repository.git

This creates a new folder on your computer with the same name as the repository, and downloads every file and every past commit.

This is the everyday flow you will use again and again: change a file, stage it, then commit it.

graph TD A["Edit files"] --> B["git status - see what changed"] B --> C["git add - stage the changes"] C --> D["git commit - save a snapshot"] D --> E["git push - upload to GitHub"]

1. git status - Shows you which files have changed, which are staged, and which are not tracked yet.

Terminal window
git status

2. git add <file> - Moves a file into the staging area, meaning “I want to save this change in my next commit.”

Terminal window
git add index.html
# stage just one file
git add .
# stage every changed file in the current folder

3. git commit -m "message" - Saves a snapshot of everything in the staging area, along with a short message describing what changed.

Terminal window
git commit -m "Add homepage layout"

4. git log - Shows the history of commits, from newest to oldest.

Terminal window
git log
# shows full details for every commit
git log --oneline
# shows a short, one-line summary for each commit

5. git diff - Shows the exact lines that changed, before you stage them.

Terminal window
git diff
# shows changes not yet staged
git diff --staged
# shows changes that are staged, ready to commit

Mistakes happen. Git gives you several ways to undo them, depending on how far you’ve already gone.

Terminal window
git checkout -- <file>
# or, in newer Git versions:
git restore <file>

Throws away your unsaved changes in that file and brings back the last committed version.

A branch is like a separate copy of your project where you can try new things without touching your main, working code. The default branch is usually called main (older projects sometimes call it master).

gitGraph commit id: "Initial commit" commit id: "Add homepage" branch feature-login checkout feature-login commit id: "Add login form" commit id: "Add validation" checkout main commit id: "Fix typo" merge feature-login commit id: "Release v1.0"

1. git branch - Lists every branch in your project, and shows which one you’re currently on.

Terminal window
git branch

2. git branch <name> - Creates a new branch, but does not switch to it yet.

Terminal window
git branch feature-login

3. git checkout <name> or git switch <name> - Moves you onto a different branch.

Terminal window
git checkout feature-login
# or, in newer Git versions:
git switch feature-login

4. Create and switch in one step:

Terminal window
git checkout -b feature-login
# or
git switch -c feature-login

5. git branch -d <name> - Deletes a branch that has already been merged.

Terminal window
git branch -d feature-login

Once your work on a branch is ready, you need to bring it back into main. There are two common ways to do this.

Terminal window
git checkout main
git merge feature-login

Combines the two branches together and creates a new “merge commit” that joins their histories. This keeps the full history of both branches, exactly as it happened.

Sometimes Git cannot automatically combine two changes, because they both edited the same lines. This is called a merge conflict. Git will pause and mark the conflicting area in the file, like this:

<<<<<<< HEAD
This is the version from your current branch.
=======
This is the version from the branch you're merging in.
>>>>>>> feature-login
  1. Open the file and look for the <<<<<<<, =======, and >>>>>>> markers.

  2. Edit the file so it has the final, correct content, and remove the marker lines.

  3. Stage the fixed file:

    Terminal window
    git add <file>
  4. Finish the merge with a commit:

    Terminal window
    git commit

A remote is simply a saved link to an online copy of your repository, like the one on GitHub.

1. git remote add <name> <url> - Connects your local project to an online repository.

Terminal window
git remote add origin https://github.com/username/repository.git

origin is just a nickname - almost everyone uses origin as the standard name for the main remote.

2. git remote -v - Shows every remote connected to your project, and their URLs.

Terminal window
git remote -v

3. git push - Uploads your commits to the remote repository.

Terminal window
git push origin main
# uploads the "main" branch to the "origin" remote
git push -u origin main
# same as above, but also remembers this pairing,
# so next time you can just type "git push"

4. git pull - Downloads new commits from the remote and combines them into your current branch.

Terminal window
git pull origin main

git pull is really just git fetch followed by git merge, done in one step.

5. git fetch - Downloads new commits from the remote, but does not merge them into your files yet.

Terminal window
git fetch origin

Using SSH keys lets you connect to GitHub without typing your username and password every time.

  1. Create an SSH key (skip this if you already have one):

    Terminal window
    ssh-keygen -t ed25519 -C "you@example.com"
  2. Copy your public key:

    Terminal window
    cat ~/.ssh/id_ed25519.pub
  3. Add it to GitHub: Go to GitHub -> Settings -> SSH and GPG keys -> New SSH key, and paste the key you copied.

  4. Test the connection:

    Terminal window
    ssh -T git@github.com
  5. Clone repositories using the SSH link instead of the HTTPS one:

    Terminal window
    git clone git@github.com:username/repository.git

A .gitignore file tells Git which files and folders to never track or save. This is useful for things like passwords, build files, or huge folders like node_modules that don’t need to be saved in your project’s history.

# Ignore dependency folders
node_modules/
venv/
# Ignore environment and secret files
.env
.env.local
# Ignore build output
dist/
build/
# Ignore log files
*.log
# Ignore OS and editor files
.DS_Store
.vscode/

Sometimes you need to quickly switch to a different branch, but you’re not ready to commit what you’re working on yet. git stash lets you save your unfinished changes for later, without committing them.

Terminal window
git stash
# saves your uncommitted changes and cleans your working directory
git stash list
# shows every stash you've saved
git stash pop
# brings back the most recent stash, and removes it from the list
git stash apply
# brings back the most recent stash, but keeps it in the list too

A tag marks a specific commit as important, usually to label a release version, like v1.0.0.

Terminal window
git tag v1.0.0
# creates a tag on the current commit
git tag
# lists every tag in the project
git push origin v1.0.0
# uploads a single tag to GitHub
git push origin --tags
# uploads every tag to GitHub

GitHub Repositories, Forks, and Pull Requests

Section titled “GitHub Repositories, Forks, and Pull Requests”

A repository (or “repo”) is a project’s home on GitHub. It holds your code, its full history, and extra tools like issues and pull requests.

A fork is your own personal copy of someone else’s repository, made under your own GitHub account. You can freely make changes in your fork without affecting the original project.

graph TD A["Fork the repository"] --> B["Clone your fork to your computer"] B --> C["Create a new branch"] C --> D["Make changes and commit"] D --> E["Push your branch to your fork"] E --> F["Open a Pull Request to the original repo"] F --> G["Owner reviews and merges it"]

A pull request is a request asking the project owner to pull your changes into their repository. It shows exactly what you changed, and lets other people leave comments, ask questions, or request changes before it gets merged.

  1. Push your branch to GitHub:

    Terminal window
    git push origin feature-login
  2. Go to the repository on GitHub. You’ll usually see a button to “Compare & pull request”.

  3. Write a clear title and description explaining what you changed and why.

  4. Submit the pull request, and wait for a review.

  5. If changes are requested, make them, commit, and push again - the pull request updates automatically.

  6. Once approved, the pull request gets merged into the main branch.

Issues are GitHub’s way of tracking bugs, feature requests, and to-do items for a project. Anyone with access can open an issue, discuss it in the comments, and link it to the pull request that fixes it.

Project owners can set up branch protection rules on important branches like main, so that:

  • Changes can only come in through a reviewed pull request, not a direct push.
  • At least one (or more) other people must approve the pull request first.
  • Automated checks (like tests) must pass before merging is even allowed.

GitHub Actions is GitHub’s built-in tool for automating tasks, like running tests, checking code style, or deploying your app, every time something happens in your repository (like a push or a pull request). This is often called CI/CD (Continuous Integration / Continuous Deployment).

Continuous Integration (CI)

Automatically test and check your code every time someone pushes changes, so bugs get caught early.

Continuous Deployment (CD)

Automatically deploy your app to a server or a website once your code passes all its checks.

Automation

Automate almost anything - labeling issues, sending notifications, running scheduled jobs, and more.

graph TD A["Event happens - e.g. push, pull request"] --> B["Workflow file is triggered - .github/workflows/*.yml"] B --> C["Job 1 runs on a runner - a fresh virtual machine"] B --> D["Job 2 runs on a runner"] C --> E["Steps run in order inside the job"] D --> F["Steps run in order inside the job"]
  • Workflow: A full automation process, written as a YAML file, stored in the .github/workflows/ folder of your repository.
  • Event: Something that starts (triggers) a workflow, like a push, a pull request, or a schedule.
  • Job: A set of steps that run together on the same runner (virtual machine). A workflow can have more than one job, and by default they run at the same time (in parallel).
  • Step: A single task inside a job, like running a command or using someone else’s pre-built action.
  • Action: A reusable piece of automation that someone else already built, which you can just plug into your workflow (for example, an action that sets up Node.js for you).
  • Runner: The actual computer (virtual machine) that runs your jobs. GitHub provides free runners for Linux, Windows, and macOS.

Workflow files must be placed in this exact folder in your repository:

.github/workflows/

Each .yml file inside this folder is its own separate workflow.

This example runs tests automatically every time someone pushes code, or opens a pull request, to the main branch.

# .github/workflows/ci.yml
name: Run Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
  • name: Run Tests - A friendly name for the workflow, shown on GitHub’s “Actions” tab.
  • on: - Lists the events that trigger this workflow. Here, it runs on a push or a pull request to main.
  • jobs: - Lists the jobs in this workflow. Here there is one job, named test.
  • runs-on: ubuntu-latest - Says which type of virtual machine (runner) this job should use.
  • steps: - The list of tasks this job runs, in order, from top to bottom.
  • uses: - Runs a pre-built action made by GitHub or the community (here, actions/checkout downloads your repository’s code onto the runner, and actions/setup-node installs Node.js).
  • run: - Runs a plain command line instruction, just like you would type in a terminal.
on:
push:
branches: [main]

Runs the workflow every time someone pushes commits to the listed branch.

Never write passwords, API keys, or tokens directly inside a workflow file, since anyone who can see your repository could read them. Instead, store them as secrets.

  1. Go to your repository on GitHub.

  2. Open Settings -> Secrets and variables -> Actions.

  3. Click New repository secret, give it a name (like API_KEY), and paste in the value.

  4. Use it in your workflow like this:

    steps:
    - name: Deploy app
    run: ./deploy.sh
    env:
    API_KEY: ${{ secrets.API_KEY }}

A matrix lets you run the same job multiple times, automatically, with different settings each time - for example, testing your app on several versions of Node.js at once.

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test

This example runs the whole job three separate times, once each for Node.js 18, 20, and 22, all at the same time.

By default, jobs run at the same time (in parallel). Use needs to make one job wait for another to finish first.

jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building the app..."
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploying the app..."

Here, the deploy job will always wait for build to finish successfully before it starts.

Artifacts let you save files created during a job (like a build output) and share them with other jobs, or download them later from GitHub.

steps:
- name: Build the app
run: npm run build
- name: Upload build output
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/

A Simple Example: Deploying on Every Merge to Main

Section titled “A Simple Example: Deploying on Every Merge to Main”
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install
- name: Build the app
run: npm run build
- name: Deploy
run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
TaskCommand
Start tracking a new projectgit init
Copy an existing projectgit clone <url>
Check what changedgit status
Stage changesgit add <file> or git add .
Save a snapshotgit commit -m "message"
See historygit log --oneline
Create a branchgit branch <name>
Switch branchesgit switch <name>
Create and switch at oncegit switch -c <name>
Combine branchesgit merge <branch>
Upload to GitHubgit push origin <branch>
Download from GitHubgit pull origin <branch>
Save unfinished work for latergit stash
Undo an unstaged changegit restore <file>
Undo a committed change (safely)git revert <commit id>