Git and GitHub: The Complete Workflow Every Developer Uses
Learn the real GitHub workflow: pull requests, code reviews, Issues, forking, branch protection, and the collaboration patterns used at actual companies.
Knowing Git commands is one thing. Using Git and GitHub together the way professional teams actually do -- pull requests, code reviews, branch protection, Issues -- is something else entirely. This guide bridges that gap.
If you're comfortable with git add, git commit, and git push but have never opened a pull request or reviewed someone else's code, this is where you level up from "I use Git" to "I collaborate with Git."
GitHub Is Not Git
This confuses a lot of people, so let's clear it up.
Git is the version control tool that runs on your machine. It tracks changes, manages branches, and stores history locally. GitHub is a website that hosts Git repositories and adds collaboration features on top: pull requests, Issues, Actions, Pages, and a web interface for browsing code.You can use Git without GitHub. You can't use GitHub without Git. They're complementary, but they're different things.
Other platforms like GitLab and Bitbucket offer similar features. The concepts transfer. But GitHub is where most open-source projects live and where most teams collaborate, so that's what we'll focus on.
Setting Up: Connecting Git to GitHub
Before anything else, you need Git on your machine talking to your GitHub account.
SSH keys (the right way)
Password authentication for Git operations was deprecated on GitHub. SSH keys are the standard.
# Generate an SSH key pair
ssh-keygen -t ed25519 -C "your_email@example.com"
# Start the SSH agent
eval "$(ssh-agent -s)"
# Add your private key
ssh-add ~/.ssh/id_ed25519
# Copy your public key to clipboard (macOS)
pbcopy < ~/.ssh/id_ed25519.pub
# On Linux, just cat it and copy manually
cat ~/.ssh/id_ed25519.pub
Go to GitHub > Settings > SSH and GPG keys > New SSH key. Paste the public key. Done.
Test it:
ssh -T git@github.com
# Should say: Hi username! You've successfully authenticated...
Creating a repo on GitHub
Two common paths:
Starting fresh on GitHub:- Click "New repository" on GitHub
- Give it a name, optionally add a README
- Clone it to your machine:
git clone git@github.com:yourusername/your-repo.git
cd your-repo
Pushing an existing local project:
# In your project directory
git init
git add .
git commit -m "Initial commit"
# Create the repo on GitHub (empty, no README)
# Then connect and push:
git remote add origin git@github.com:yourusername/your-repo.git
git branch -M main
git push -u origin main
The -u flag sets the upstream, so future git push commands know where to go without you specifying it.
The Branch Workflow
Here's the most important concept in professional Git usage: you almost never commit directly to main.
Instead, the workflow looks like this:
- Create a branch for your feature or fix
- Do your work on that branch
- Push the branch to GitHub
- Open a pull request
- Get it reviewed
- Merge it into main
Step 1: Create a branch
# Make sure you're on main and up to date
git checkout main
git pull origin main
# Create and switch to a new branch
git checkout -b feature/add-user-search
Branch naming conventions vary by team, but common patterns include:
feature/description-- for new featuresfix/description-- for bug fixeschore/description-- for maintenance tasksdocs/description-- for documentation updates
feature/PROJ-123-add-user-search. Whatever your team uses, be consistent.
Step 2: Do your work
Write code, make commits. Keep commits focused and descriptive.
# Work, work, work...
git add src/components/SearchBar.jsx
git commit -m "Add SearchBar component with debounced input"
git add src/api/users.js
git commit -m "Add user search API endpoint"
git add src/pages/Users.jsx
git commit -m "Integrate search into Users page"
Notice how each commit does one thing and the message says what it does. Not "WIP" or "stuff" or "asdfasdf." Your future self and your teammates will thank you.
Step 3: Push the branch
git push -u origin feature/add-user-search
This creates the branch on GitHub and pushes your commits to it. The -u flag sets up tracking so you can just git push next time.
Step 4: Open a pull request
Go to your repo on GitHub. You'll usually see a banner saying "feature/add-user-search had recent pushes" with a "Compare & pull request" button. Click it.
Or go to the "Pull requests" tab and click "New pull request."
A good pull request includes:
- A clear title -- "Add user search functionality" not "PR for thing"
- A description -- What does this change? Why? Any context a reviewer needs?
- Screenshots if it's a UI change
- Testing notes -- How can a reviewer verify this works?
## What
Added search functionality to the Users page.
Why
Users currently have to scroll through the entire list to find someone.
This was reported in #42.
How
- Added a SearchBar component with debounced input (300ms)
- Created a new API endpoint that filters users by name
- Integrated the search into the existing Users page
Testing
- Go to /users
- Type a name in the search bar
- Results should filter after 300ms of no typing
Step 5: Code review
This is where collaboration actually happens. Reviewers will:
- Read through your code changes
- Leave comments on specific lines
- Ask questions or suggest improvements
- Approve the PR or request changes
- Respond to every comment (even if it's just "good point, fixed")
- Push new commits to address feedback
- Don't force-push during review -- it makes it hard for reviewers to see what changed
# After making changes based on review feedback
git add .
git commit -m "Address review: extract search logic to custom hook"
git push
The PR updates automatically with your new commits.
Step 6: Merge
Once approved, you merge the PR. GitHub offers three merge strategies:
Create a merge commit -- preserves all individual commits plus adds a merge commit. Full history, but can be noisy. Squash and merge -- combines all your commits into one. Clean history. This is what most teams prefer for feature branches. Rebase and merge -- replays your commits on top of main. Linear history, no merge commit. Purists love this one.Most teams pick one strategy and stick with it. Squash-and-merge is the most common default.
After merging, delete the branch. GitHub has a button for this, and most teams enable auto-deletion of merged branches in repo settings.
Keeping Your Branch Up to Date
While you're working on your feature, other people are merging their PRs into main. Your branch can fall behind.
# Option 1: Merge main into your branch
git checkout feature/add-user-search
git fetch origin
git merge origin/main
# Option 2: Rebase your branch on top of main
git checkout feature/add-user-search
git fetch origin
git rebase origin/main
Merge is safer and creates a merge commit. Rebase is cleaner but rewrites history -- never rebase commits that other people are working on.
If you get merge conflicts:
# Git will tell you which files have conflicts
# Open each file, look for the conflict markers:
<<<<<<< HEAD
your code
=======
their code
>>>>>>> origin/main
# Edit the file to resolve the conflict
# Then:
git add resolved-file.js
git commit -m "Resolve merge conflict in resolved-file.js"
GitHub Issues
Issues are GitHub's built-in task tracker. They're more useful than most people realize.
Creating useful Issues
A good Issue includes:
- Clear title -- "Search doesn't work on mobile" not "Bug"
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Labels -- bug, feature, documentation, good-first-issue, etc.
Linking Issues to PRs
When your PR fixes an Issue, include a keyword in the PR description:
Fixes #42
Closes #42
Resolves #42
When the PR merges, the Issue automatically closes. This creates a clear paper trail: Issue describes the problem, PR describes the solution, they're linked together.
Issue templates
For team projects, set up Issue templates in .github/ISSUE_TEMPLATE/. This ensures bug reports always include steps to reproduce and feature requests always include a use case. GitHub walks you through creating these in the repo settings.
Forking and Contributing to Open Source
When you want to contribute to a project you don't own, you fork it.
A fork is your personal copy of someone else's repo on your GitHub account. You can do anything you want with it without affecting the original.
# 1. Fork the repo on GitHub (click the Fork button)
# 2. Clone YOUR fork
git clone git@github.com:yourusername/their-repo.git
cd their-repo
# 3. Add the original repo as "upstream"
git remote add upstream git@github.com:original-owner/their-repo.git
# 4. Create a branch for your contribution
git checkout -b fix/typo-in-readme
# 5. Make your changes, commit, push to YOUR fork
git add .
git commit -m "Fix typo in installation instructions"
git push origin fix/typo-in-readme
# 6. Open a PR from your fork to the original repo
When you open the PR on GitHub, it automatically detects that you're proposing changes from your fork to the original repo.
Keeping your fork in sync
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
Do this regularly so your fork doesn't drift too far from the original.
Branch Protection Rules
For team projects, you want to prevent people (including yourself) from pushing directly to main. Branch protection rules enforce this.
Go to Settings > Branches > Add branch protection rule for main:
- Require a pull request before merging -- no direct pushes to main
- Require approvals -- at least 1 (or 2) people must approve the PR
- Require status checks to pass -- CI/CD must be green before merging
- Require branches to be up to date -- your branch must include the latest main
- Include administrators -- even admins follow the rules
GitHub Actions (CI/CD Basics)
GitHub Actions lets you automate tasks when certain events happen. The most common: run tests automatically when a PR is opened.
Actions are defined in .github/workflows/ as YAML files:
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- run: npm run lint
Now every PR automatically runs tests and linting. Combined with branch protection requiring status checks to pass, you can't merge broken code.
Common things teams automate with Actions:
- Running tests and linters
- Building and deploying on merge to main
- Checking for security vulnerabilities
- Generating code coverage reports
- Auto-labeling PRs based on files changed
GitHub Pages
GitHub Pages lets you host a static website directly from a repo. It's free and perfect for documentation, portfolios, or project landing pages.
To enable it:
- Go to Settings > Pages
- Choose a source branch (usually
mainorgh-pages) - Choose a folder (
/root or/docs) - Your site is live at
yourusername.github.io/repo-name
yourusername.github.io and it'll be available at that URL directly.
You can use plain HTML, Jekyll (GitHub's default static site generator), or any framework that produces static files. Just push the output to the configured branch and folder.
The .gitignore File
Every repo needs one. It tells Git which files to ignore -- things that shouldn't be in version control.
# Dependencies
node_modules/
venv/
__pycache__/
# Build output
dist/
build/
.next/
out/
# Environment variables (NEVER commit these)
.env
.env.local
.env.production
# OS files
.DS_Store
Thumbs.db
# IDE files
.vscode/settings.json
.idea/
GitHub maintains a collection of .gitignore templates at github.com/github/gitignore for virtually every language and framework. Use them as a starting point.
The most critical entry: .env. Accidentally committing API keys or database passwords to a public repo is a common and serious mistake. Even if you delete the file later, it's still in the Git history. If this happens, consider those credentials compromised and rotate them immediately.
The README
Your README is the front page of your project. At minimum it should include:
- What the project does (one paragraph)
- How to install and run it
- How to contribute (for open-source projects)
Real-World Team Workflows
Here's what a typical day looks like using this workflow:
Morning:git checkout main
git pull origin main
# Check assigned Issues and pick one to work on
git checkout -b fix/PROJ-89-date-picker-timezone
During the day:
# Write code, make focused commits
git add src/components/DatePicker.jsx
git commit -m "Fix timezone offset in DatePicker component"
git add src/__tests__/DatePicker.test.jsx
git commit -m "Add tests for timezone edge cases"
# Push and open PR
git push -u origin fix/PROJ-89-date-picker-timezone
PR description:
Fixes #89
The DatePicker was displaying dates in UTC instead of the user's local
timezone. This caused dates to appear one day off for users west of GMT.
- Fixed timezone conversion in
formatDate utility
- Added test cases for GMT-12 through GMT+14
Review cycle:
- Teammate leaves a comment: "Can we also handle the case where the timezone is undefined?"
- You add a commit addressing it
- Teammate approves
- You squash and merge
- Branch auto-deletes
Common Mistakes and How to Avoid Them
Pushing directly to main. Set up branch protection. Even on solo projects, using PRs creates a record of why changes were made. Giant PRs. A PR with 2,000 lines changed is hard to review and likely to have bugs slip through. Aim for PRs under 400 lines. If a feature is big, break it into smaller PRs that build on each other. Vague commit messages. "Fix bug" tells you nothing six months later. "Fix timezone offset causing DatePicker to show wrong date for GMT- users" tells you everything. Not pulling before branching. Alwaysgit pull origin main before creating a new branch. Otherwise you're branching from an outdated version of main.
Committing .env files. Add .env to .gitignore before your first commit. If you've already committed it, remove it from tracking with git rm --cached .env and rotate all the credentials in it.
Force-pushing to shared branches. git push --force rewrites history. On your own feature branch before anyone's looked at it? Fine. On a branch others are working on? You'll destroy their work.
What to Learn Next
This covers the GitHub workflow that 90% of development teams use daily. Once you're comfortable with this, explore:
- Git hooks -- scripts that run automatically on commit, push, etc.
- GitHub CLI (
gh) -- create PRs, review code, and manage Issues from the terminal - Conventional Commits -- a standardized commit message format
- Trunk-based development -- an alternative to feature branches used by some large teams
- Git bisect -- finding which commit introduced a bug using binary search
Practice these workflows with real projects on CodeUp -- the collaboration patterns you build now are the same ones you'll use every day as a professional developer.