Fork a Project and Submit a PR
Learn to fork a project, clone, branch, commit, push, and submit a pull request on GitHub, with tips for upstream sync and common pitfalls.
Focus: fork a project and submit a pull request
You’ve been working on your own branches and merging them locally — but now you want to contribute to a project you don’t own. Maybe you’ve found a bug in an open-source library, or you want to add a feature to a teammate’s repo. Without the right workflow, you’ll either get locked out of the repo or create a messy, unmergeable pull request. This lesson teaches you the fork and pull request workflow — the standard way to contribute to any project on GitHub, GitLab, or Bitbucket — so you can submit clean, reviewable changes that maintainers actually want to merge.
The problem this lesson solves
Direct push access is a privilege, not a right. For most projects — especially open-source — you can’t just git push to the main repository. You need a way to propose changes without stepping on the maintainers’ toes. The fork-and-PR workflow solves exactly that: you create your own copy of the repo (a fork), make your changes there, and then ask the original project to pull your changes in via a pull request. This keeps the main repo stable, gives maintainers full control over what gets merged, and creates a clear audit trail of who changed what and why.
Without this workflow, you’d be stuck waiting for someone to add you as a collaborator, or worse — you’d end up emailing patch files around like it’s 1995. The fork-and-PR model is the backbone of modern collaborative development, and mastering it unblocks you from contributing to thousands of projects.
Core concept / mental model
Think of a fork as a personal copy of a repository that lives under your own GitHub account. It’s like photocopying a document so you can mark it up with a red pen without ruining the original. The pull request is your polite way of saying, “Hey, I made these changes in my copy — would you like to pull them into your original?”
Here’s the mental model in three layers:
- Upstream — the original repository you want to contribute to (e.g.,
python/cpython). - Origin — your fork on GitHub, a full copy of upstream that you control.
- Local — your development machine, where you clone your fork and do the actual work.
The flow is a loop: you pull from upstream to stay current, push to origin to share your changes, and open a PR from your fork’s branch to the upstream repo. The term pull request is literal: you’re requesting that the upstream project pulls your changes into their codebase.
Pro tip: Always keep your fork’s
mainbranch in sync with upstream. A fork that’s months behind will produce merge conflicts that make maintainers cringe.
How it works step by step
Here’s the complete workflow, broken into seven logical steps:
- Fork the repository — click the “Fork” button on GitHub (or equivalent) to create a copy under your account.
- Clone your fork locally —
git clone https://github.com/your-username/project.git— this downloads your fork to your machine. - Add the upstream remote —
git remote add upstream https://github.com/original-owner/project.git— this lets you fetch the latest changes from the original repo. - Create a feature branch — always work on a new branch (e.g.,
fix-typo-in-readme), never directly onmain. This keeps yourmainclean and makes it easy to open multiple PRs. - Make and commit your changes — edit files,
git add, andgit commitwith a clear message. Commit early and often. - Push your branch to your fork —
git push origin your-branch— this uploads your branch to GitHub. - Open a pull request — go to your fork on GitHub, click “Compare & pull request,” fill in the title and description, and submit. Maintainers review, discuss, and merge.
After your PR is merged (or rejected), you can update your fork’s main by pulling from upstream, and then delete the feature branch to keep things tidy.
Hands-on walkthrough
Let’s walk through a real example. We’ll use a fictional repo, cool-project owned by python-community. Your GitHub username is dev-user.
Step 1: Fork and clone
Navigate to the repo’s page on GitHub and click the Fork button. Once it’s created, clone your fork and set up the upstream remote:
# Clone your fork
git clone https://github.com/dev-user/cool-project.git
cd cool-project
# Add the original repo as 'upstream'
git remote add upstream https://github.com/python-community/cool-project.git
# Verify remotes
git remote -v
Expected output:
origin https://github.com/dev-user/cool-project.git (fetch)
origin https://github.com/dev-user/cool-project.git (push)
upstream https://github.com/python-community/cool-project.git (fetch)
upstream https://github.com/python-community/cool-project.git (push)
Step 2: Create a branch and make changes
Always use a descriptive branch name. Use the imperative mood for commit messages (e.g., "Fix typo", "Add feature").
# Create and switch to a new branch
git checkout -b fix-readme-typo
# Make your edits (e.g., fix a typo in README.md)
# Then stage and commit
git add README.md
git commit -m "Fix typo in installation instructions"
Step 3: Push and open a PR
Push your branch to your fork, then open a pull request via GitHub’s web UI.
git push origin fix-readme-typo
Expected output:
remote: Create a pull request for 'fix-readme-typo' on GitHub by visiting:
remote: https://github.com/dev-user/cool-project/pull/new/fix-readme-typo
Visit that URL, fill in the PR title and description, and click Create pull request. You should see a green “This branch has no conflicts with the base branch” message if all is well.
Comparing with direct push
If you had direct push access to the repo, you could just push your branch directly. But for a fork, you need the PR to merge. Let’s compare the two approaches:
| Aspect | Direct push | Fork + PR |
|---|---|---|
| Access required | Collaborator or owner | None (anyone with GitHub account) |
| Review process | Optional (but recommended) | Required by workflow |
| Risk to main branch | High if you push to main | Low — changes are isolated |
| Use case | Small team, full trust | Open source, external contributors |
Troubleshooting & edge cases
I’m getting merge conflicts in my PR
If your PR shows conflicts, it means upstream changed the same lines you edited. Fix by syncing your fork with upstream, then rebasing your branch:
# Fetch upstream changes
git fetch upstream
# Rebase your branch onto the latest upstream main
git checkout fix-readme-typo
git rebase upstream/main
# Resolve conflicts, then continue
git add .
git rebase --continue
# Force-push to update your PR (use with care)
git push --force-with-lease origin fix-readme-typo
I accidentally pushed to main on my fork
That’s fine! Your fork is your playground. You can open a PR from main to upstream, but it’s cleaner to move your changes to a branch:
git checkout -b my-feature
# Now your changes are on the branch; reset main
git checkout main
git reset --hard upstream/main
git push --force origin main
Pro tip: Use
--force-with-leaseinstead of--force. It refuses to overwrite if someone else pushed to your fork, preventing accidental data loss.
My fork is outdated — how do I sync?
Before starting any new work, always sync your main:
git checkout main
git fetch upstream
git merge upstream/main
git push origin main
This ensures your new branch starts from the latest code.
What you learned & what's next
You now understand the fork and pull request workflow — the foundation of contributing to any Git-based project. You can fork a repo, clone it, create a feature branch, push it to your fork, and open a clean PR. You also know how to handle merge conflicts and keep your fork synced with upstream.
You’ve met the learning objectives: you can explain why forking matters and you’ve completed a hands-on exercise that takes you from fork to PR.
What’s next? In the next lesson, you’ll learn how to handle code review feedback — how to update your PR based on comments, request re-review, and follow up with maintainers. This is the final communication layer that turns a good PR into a merged one.
Practice recap
For your next step, fork a real open-source project (like a popular Python package), make a small documentation fix, and submit a PR. Try syncing your fork with upstream after a few days to stay current, and practice resolving a merge conflict by editing the same file as a teammate.
Common mistakes
- Forgetting to add the upstream remote and then wondering why your fork is out of date — always
git remote add upstreamright after cloning. - Committing directly to your fork’s
mainbranch — this makes it hard to open multiple PRs and easily gets out of sync; always create a feature branch. - Pushing with
--forceafter a rebase, which can overwrite history on your fork and confuse others — use--force-with-leaseinstead. - Skipping the
git fetch upstreamstep before creating a new branch, leading to unnecessary merge conflicts later.
Variations
- Some platforms (like GitLab) call it a merge request instead of a pull request — the core workflow is identical.
- Instead of using GitHub’s web UI to open a PR, you can use the
ghCLI:gh pr createfor a fully command-line experience. - For smaller projects, you might use a shared branch model where you push directly to a feature branch — but fork + PR is safer for external contributions.
Real-world use cases
- Contributing a bug fix to an open-source Python library like Django or requests — you fork the repo, fix the issue, and submit a PR with tests.
- Collaborating on a team project where you don’t have write access — you fork, implement a feature, and the maintainer reviews your PR before merging.
- Participating in hackathons or code sprints where your changes must be isolated and reviewed — forking keeps your work separate until it’s accepted.
Key takeaways
- A fork is a personal copy of a repo that you fully control — it’s the gateway to contributing without direct push access.
- Always create a separate branch for each logical change to keep your PRs focused and reviewable.
- The three key remotes are: upstream, origin (your fork), and local — keep your local
mainsynced with upstream. - Pull requests are not just code pushes — they’re a conversation that includes review, discussion, and changes before merge.
- Merge conflicts are normal; mastering
git rebaseand--force-with-leasewill save you time and protect your work. - Using the
ghCLI or GitHub’s web UI both work — choose what fits your workflow; the underlying Git commands remain the same.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.