In the previous tutorial, we learned to merge and rebase branches. But everything happened on your local computer. Real projects involve multiple people working together.

This is where GitHub comes in. GitHub is a platform that hosts Git repositories online. It lets you share code, review changes, and automate testing. In this tutorial, you will learn to push code to GitHub, create pull requests, and set up basic CI/CD with GitHub Actions.

Git vs GitHub

This confuses many beginners. They are not the same thing.

  • Git is the version control tool. It runs on your computer.
  • GitHub is a website that hosts Git repositories. It adds collaboration features like pull requests, issues, and CI/CD.

You can use Git without GitHub. But GitHub (or alternatives like GitLab and Bitbucket) makes team collaboration much easier.

Creating a Repository on GitHub

  1. Go to github.com and sign in
  2. Click the + icon in the top right and select New repository
  3. Name it my-project
  4. Choose Public or Private
  5. Do NOT initialize with a README (we already have a local repo)
  6. Click Create repository

GitHub shows you the commands to connect your local repository.

Connecting Your Local Repo to GitHub

After creating the repository on GitHub, connect it from your terminal:

git remote add origin https://github.com/alex/my-project.git

This tells Git where to send your code. The name origin is a convention — it means “the main remote repository.”

Check your remote:

git remote -v
origin  https://github.com/alex/my-project.git (fetch)
origin  https://github.com/alex/my-project.git (push)

SSH vs HTTPS

There are two ways to connect to GitHub:

HTTPS — uses username and token:

https://github.com/alex/my-project.git

SSH — uses SSH keys (no password needed after setup):

git@github.com:alex/my-project.git

Setting Up SSH Keys

SSH is more convenient because you do not have to enter credentials every time.

Generate a key:

ssh-keygen -t ed25519 -C "alex@example.com"

Press Enter to accept the default file location. Set a passphrase if you want extra security.

Start the SSH agent and add your key:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Copy the public key:

# macOS
cat ~/.ssh/id_ed25519.pub | pbcopy

# Linux
cat ~/.ssh/id_ed25519.pub

Go to GitHub: Settings > SSH and GPG keys > New SSH key. Paste the key and save.

Test the connection:

ssh -T git@github.com
Hi alex! You've successfully authenticated, but GitHub does not provide shell access.

Now use SSH URLs instead of HTTPS:

git remote set-url origin git@github.com:alex/my-project.git

Pushing Code to GitHub

The push command sends your local commits to the remote repository.

git push -u origin main
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 512 bytes | 512.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To github.com:alex/my-project.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

The -u flag sets up tracking. After this, you can just type git push without specifying the branch.

Push a feature branch:

git switch -c feature/search
# ... make commits ...
git push -u origin feature/search

Pulling Changes

When others push changes, you need to pull them to your local copy.

git pull

This does two things:

  1. Fetches the latest changes from the remote
  2. Merges them into your current branch

If you prefer rebasing instead of merging:

git pull --rebase

This keeps your history linear. Many teams use this as the default.

Fetching Without Merging

Sometimes you want to see what changed on the remote without merging anything.

git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
From github.com:alex/my-project
   a1b2c3d..b2c3d4e  main       -> origin/main

After fetching, you can compare:

git log HEAD..origin/main --oneline

This shows commits on origin/main that you do not have yet. When you are ready:

git merge origin/main

Pull Requests

A pull request (PR) is how you propose changes on GitHub. Instead of merging directly, you ask others to review your code first.

The Feature Branch Workflow

This is the most common way teams use Git and GitHub:

1. Create a branch      → git switch -c feature/login
2. Make changes          → edit files, commit
3. Push to GitHub        → git push -u origin feature/login
4. Create a pull request → on GitHub
5. Team reviews          → comments, suggestions
6. Merge the PR          → on GitHub
7. Delete the branch     → clean up
8. Pull main locally     → git switch main && git pull

Creating a Pull Request

After pushing your branch, go to GitHub. You will see a banner:

feature/login had recent pushes — Compare & pull request

Click it and fill in:

  • Title — short summary of the change
  • Description — what you changed and why
  • Reviewers — who should review

Click Create pull request.

Reviewing a Pull Request

Reviewers can:

  • Read the code changes
  • Leave comments on specific lines
  • Approve the PR
  • Request changes if something needs fixing

After approval, click Merge pull request on GitHub. Then delete the branch.

Pull Back to Local

After the PR is merged on GitHub:

git switch main
git pull
git branch -d feature/login

Forking

Forking creates a copy of someone else’s repository in your GitHub account. This is how you contribute to open-source projects.

1. Fork the repo         → on GitHub, click "Fork"
2. Clone your fork       → git clone git@github.com:alex/their-project.git
3. Create a branch       → git switch -c fix/typo
4. Make changes + commit → edit, git add, git commit
5. Push to your fork     → git push -u origin fix/typo
6. Create a PR           → from your fork to the original repo

To keep your fork up to date with the original:

# Add the original repo as "upstream"
git remote add upstream https://github.com/original-author/their-project.git

# Fetch and merge their changes
git fetch upstream
git merge upstream/main

GitHub Actions — Basic CI/CD

CI/CD stands for Continuous Integration / Continuous Deployment. It means running tests and deployments automatically when you push code.

GitHub Actions lets you automate workflows. Workflows are defined in YAML files inside .github/workflows/.

Your First Workflow

Create the workflow file:

mkdir -p .github/workflows

Create .github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout 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

This workflow:

  • Runs on every push to main and every pull request
  • Checks out your code
  • Installs Node.js and dependencies
  • Runs your tests

Understanding the Workflow

on:                    # When to run
  push:
    branches: [main]   # On push to main
  pull_request:
    branches: [main]   # On PR targeting main
jobs:                  # What to do
  test:                # Job name
    runs-on: ubuntu-latest  # Use a Linux VM
    steps:             # Steps to execute
      - uses: actions/checkout@v4  # Pre-built action
      - run: npm test              # Shell command

A Python Example

name: Python CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run linter
        run: pip install flake8 && flake8 .

      - name: Run tests
        run: python -m pytest

This runs a linter (code style check) and tests on every push.

Checking Workflow Status

After pushing, go to the Actions tab on your GitHub repository. You will see your workflow running. A green check means it passed. A red X means something failed — click it to see the logs.

Pull requests also show the workflow status. Reviewers can see if the tests pass before approving.

Cloning a Repository

To get a copy of a repository on your computer:

git clone git@github.com:alex/my-project.git
Cloning into 'my-project'...
remote: Enumerating objects: 42, done.
remote: Counting objects: 100% (42/42), done.
Receiving objects: 100% (42/42), 8.12 KiB | 8.12 MiB/s, done.

Clone into a specific folder:

git clone git@github.com:alex/my-project.git my-folder

Cloning automatically sets up the origin remote. You can start working immediately.

Deleting Remote Branches

After merging a pull request, the branch still exists on GitHub unless you delete it. You can delete it on GitHub (there is a “Delete branch” button after merging a PR) or from the terminal:

git push origin --delete feature/user-profile
To github.com:alex/my-project.git
 - [deleted]         feature/user-profile

To clean up your local references to deleted remote branches:

git fetch --prune

This removes any local tracking branches that no longer exist on the remote. Run this periodically to keep things tidy.

Keeping Your Fork in Sync

If you forked a repository and the original keeps getting updates, your fork falls behind. Here is how to stay up to date:

# Make sure you have the upstream remote
git remote -v
# If upstream is not listed:
git remote add upstream https://github.com/original-author/their-project.git

# Fetch the latest from the original repo
git fetch upstream

# Merge into your main branch
git switch main
git merge upstream/main

# Push the updates to your fork
git push origin main

Do this regularly, especially before starting new work on your fork. It prevents conflicts when you create pull requests later.

The Complete Workflow

Here is the full workflow from start to finish:

# Clone the project (first time only)
git clone git@github.com:alex/my-project.git
cd my-project

# Get the latest changes
git switch main
git pull

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

# Make changes
echo "<div>User Profile</div>" > profile.html
git add profile.html
git commit -m "Add user profile page"

# Push to GitHub
git push -u origin feature/user-profile

# Create a pull request on GitHub
# Wait for review and CI to pass
# Merge the PR on GitHub

# Clean up locally
git switch main
git pull
git branch -d feature/user-profile

This workflow keeps main clean, ensures code is reviewed, and runs automated tests.

Common Mistakes

  1. Pushing to main directly — Most teams protect the main branch on GitHub (Settings > Branches > Branch protection rules). Always create a feature branch and use pull requests. This ensures code is reviewed and tests pass.

  2. Not pulling before starting new work — Always git pull on main before creating a new branch. If you branch from an old version of main, you will have more conflicts later.

  3. Storing secrets in the repository — Never commit API keys, passwords, or .env files. Use GitHub’s Secrets feature (Settings > Secrets) for CI/CD workflows. Add .env to your .gitignore file.

What’s Next?

You can now work with GitHub, create pull requests, and set up CI/CD. In the next tutorial, we will cover advanced Git — undoing mistakes, recovering lost commits, and power features that save you time.

Git Tutorial #5: Advanced Git — Undo Mistakes and Power Features

For a quick reference of all Git commands, check the Git Commands Cheat Sheet.


This is part 4 of the DevTools Tutorial series.