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
- Go to github.com and sign in
- Click the + icon in the top right and select New repository
- Name it
my-project - Choose Public or Private
- Do NOT initialize with a README (we already have a local repo)
- 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:
- Fetches the latest changes from the remote
- 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
mainand 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
Pushing to main directly — Most teams protect the
mainbranch on GitHub (Settings > Branches > Branch protection rules). Always create a feature branch and use pull requests. This ensures code is reviewed and tests pass.Not pulling before starting new work — Always
git pullonmainbefore creating a new branch. If you branch from an old version ofmain, you will have more conflicts later.Storing secrets in the repository — Never commit API keys, passwords, or
.envfiles. Use GitHub’s Secrets feature (Settings > Secrets) for CI/CD workflows. Add.envto your.gitignorefile.
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.