Home DevOps Forking and Contributing — The PR That Was Open for 18 Months
Beginner 4 min · July 11, 2026
Forking and Contributing: The Open Source Workflow

Forking and Contributing — The PR That Was Open for 18 Months

A good first PR sat open for 18 months because the contributor forked from the wrong branch.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 11, 2026
last updated
2,406
articles · all by Naren
Before you start⏱ 15 min
  • Git 2.30+, GitHub account, basic command line familiarity, understanding of version control concepts (commit, branch, merge), Node.js 18+ (for running example CI scripts), a text editor or IDE
 ● Production Incident
Quick Answer

1. Fork the repo on GitHub. 2. Clone your fork: git clone . 3. Add upstream remote: git remote add upstream . 4. Create a branch: git switch -c feature. 5. Make changes and push. 6. Open a PR from your fork to the original repo.

✦ Definition~90s read
What is Forking and Contributing?

Forking creates a personal copy of someone else's repository on GitHub. You fork, clone your fork, make changes, and submit a Pull Request to the original repo. This is the standard open-source contribution model.

Imagine you want to suggest a change to a Wikipedia article.
Plain-English First

Imagine you want to suggest a change to a Wikipedia article. You can't edit it directly — you make a copy (fork), edit your copy, then submit a request to merge your changes back (Pull Request). The fork is your private sandbox where you can try anything without affecting the original. The maintainers review your changes and decide whether to accept them.

Forking is the foundation of open-source collaboration. You fork a repo, create changes in your personal copy, and submit a Pull Request. The maintainers review, discuss, and merge. The critical pattern that most beginners miss: keeping your fork in sync with the original (upstream) repository. A PR based on a stale fork will have merge conflicts that grow over time. This guide covers the full fork-and-PR workflow, the incident where a stale fork caused an 18-month-old PR, and how to keep your fork synchronized.

Why Fork? The Case for Controlled Collaboration

Forking a repository creates a personal copy under your GitHub account, giving you full control to experiment without affecting the original project. This is the foundation of the open source contribution model. Unlike direct branching on the upstream repo, forking decouples your work from the project's main development line. This is critical when you're not a core maintainer—you can't push branches to someone else's repo. Forking also protects the upstream from accidental force pushes or broken commits. In production, we've seen teams lose days of work because a contributor force-pushed to a shared branch. Forking eliminates that risk. Always fork before you start coding, even for small fixes. It's a habit that scales.

fork-setup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Fork the repo on GitHub UI, then clone your fork
git clone https://github.com/YOUR_USERNAME/upstream-repo.git
cd upstream-repo

# Add upstream remote to sync with original
git remote add upstream https://github.com/ORIGINAL_OWNER/upstream-repo.git

# Verify remotes
git remote -v
# Output:
# origin    https://github.com/YOUR_USERNAME/upstream-repo.git (fetch)
# origin    https://github.com/YOUR_USERNAME/upstream-repo.git (push)
# upstream  https://github.com/ORIGINAL_OWNER/upstream-repo.git (fetch)
# upstream  https://github.com/ORIGINAL_OWNER/upstream-repo.git (push)
Output
origin https://github.com/YOUR_USERNAME/upstream-repo.git (fetch)
origin https://github.com/YOUR_USERNAME/upstream-repo.git (push)
upstream https://github.com/ORIGINAL_OWNER/upstream-repo.git (fetch)
upstream https://github.com/ORIGINAL_OWNER/upstream-repo.git (push)
💡Name Your Remotes Clearly
Use 'upstream' for the original repo and 'origin' for your fork. This convention is universal and prevents confusion when syncing.
📊 Production Insight
In production, we once had a contributor accidentally push a branch with secrets to the upstream repo. Forking would have contained that leak to their own fork.
🎯 Key Takeaway
Forking gives you a safe sandbox to contribute without risking the main project's stability.
forking-contributing THECODEFORGE.IO Contribution Pipeline Layers Layered structure from fork to merge Source Control Upstream Repo | Forked Repo | Local Clone Branch Management Feature Branch | Main Branch | Release Tags Commit Quality Atomic Commits | Signed Commits | Descriptive Messages Review Process Pull Request | Code Review | CI Checks Integration Merge Commit | Squash Merge | Rebase THECODEFORGE.IO
thecodeforge.io
Forking Contributing

Syncing Your Fork: Stay Current or Get Left Behind

Your fork is a snapshot. While you work, the upstream repo evolves with new commits, bug fixes, and features. If you don't sync, your pull request will face merge conflicts or become outdated. The standard workflow is to fetch upstream changes and rebase your feature branch on top. Rebasing keeps a linear history, which maintainers prefer. Avoid merging upstream into your branch—it creates unnecessary merge commits. In production, we've seen PRs with 50+ merge commits that could have been a clean 3-commit series. Sync daily, or at least before you open a PR. Use git pull --rebase upstream main to keep your branch current.

sync-fork.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Ensure you're on your feature branch
git checkout my-feature

# Fetch upstream changes
git fetch upstream

# Rebase your feature branch onto upstream/main
git rebase upstream/main

# If conflicts occur, resolve them, then:
git add .
git rebase --continue

# Force push to your fork (since rebase rewrites history)
git push origin my-feature --force-with-lease
Output
Successfully rebased and updated refs/heads/my-feature.
⚠ Force Push with Care
Use --force-with-lease instead of --force. It prevents overwriting remote commits you haven't seen, avoiding accidental data loss.
📊 Production Insight
A junior dev once force-pushed without --force-with-lease and wiped out a colleague's commits on a shared branch. Always use the safer flag.
🎯 Key Takeaway
Regularly rebase your feature branch on upstream changes to avoid painful merge conflicts later.

Branching Strategy: One Change, One Branch

Each contribution should live on its own branch. This keeps your work isolated and makes it easy to manage multiple PRs simultaneously. Name your branches descriptively: fix/login-error, feat/user-avatar, docs/api-guide. Avoid generic names like patch-1 or my-changes. In production, we enforce a branch naming convention: <type>/<short-description>. This helps CI scripts and maintainers quickly understand the purpose. Never commit directly to main on your fork—that branch should mirror upstream exactly. If you need to update your fork's main, sync it with upstream, don't push to it.

branch-create.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Create a new branch from an up-to-date main
git checkout main
git pull upstream main
git push origin main  # sync your fork's main

# Create and switch to feature branch
git checkout -b fix/login-error

# Make changes, commit, and push
git add .
git commit -m "fix: handle empty email in login form"
git push origin fix/login-error
Output
Branch 'fix/login-error' set up to track remote branch 'fix/login-error' from 'origin'.
🔥Branch Naming Convention
Use prefixes like fix/, feat/, chore/, docs/. This is standard in many open source projects and helps with automated changelog generation.
📊 Production Insight
We once had a PR that touched 20 files because the contributor lumped three unrelated fixes into one branch. Review took 3 days. Separate branches would have taken 1 day total.
🎯 Key Takeaway
Isolate each logical change in its own branch with a descriptive name to simplify review and rollback.
Fork vs Branch Contribution Comparing fork-based and branch-based workflows Fork-Based Branch-Based Access Control No direct write needed Requires contributor access Isolation Complete repo separation Same repo, branch isolation Sync Overhead Manual upstream sync Automatic with repo PR Visibility Cross-repo PR Intra-repo PR Conflict Resolution More frequent conflicts Easier to rebase Best For External contributors Team members THECODEFORGE.IO
thecodeforge.io
Forking Contributing

Commit Hygiene: Crafting a Clear History

Your commits tell a story. Each commit should be a self-contained, logical unit with a clear message. Follow the Conventional Commits specification: type(scope): description. For example, fix(auth): prevent null pointer on logout. Keep the subject line under 50 characters, and use the body to explain why, not what. Squash fixup commits before opening a PR. In production, we require all PRs to have a clean commit history—no 'WIP', 'fix typo', or 'oops' commits. Use git rebase -i to squash and reorder. This makes bisecting bugs trivial.

squash-commits.shBASH
1
2
3
4
5
6
7
8
# Interactive rebase to squash last 3 commits
git rebase -i HEAD~3

# In the editor, change 'pick' to 'squash' for commits you want to combine
# Save and exit. Then write a new commit message.

# Force push the cleaned history
git push origin fix/login-error --force-with-lease
Output
Successfully rebased and updated refs/heads/fix/login-error.
💡Use `git commit --amend` for Small Fixes
If you forgot to include a file or need to tweak the message, amend the last commit instead of creating a new one.
📊 Production Insight
A messy commit history once delayed a hotfix rollback by 2 hours because we couldn't quickly identify which commit introduced the bug.
🎯 Key Takeaway
Write commits as if the next person to read them is a detective investigating a production outage.

Opening a Pull Request: The Art of the First Impression

A good PR is more than code—it's communication. Write a descriptive title following the same convention as commits. In the description, explain what the change does, why it's needed, and how to test it. Reference related issues with Closes #123. Keep the scope narrow; a PR should do one thing. In production, we've rejected PRs that were too large to review effectively. Aim for under 400 lines changed. If your PR is larger, break it into smaller, dependent PRs. Use draft PRs for work in progress to signal that you're not ready for review.

pr-template.mdMARKDOWN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## Description

Fixes the login error when email field is empty.

## Changes

- Added null check in `login.js`
- Updated error message to be user-friendly

## Testing

1. Go to /login
2. Leave email empty and click submit
3. See error message instead of crash

Closes #42
🔥Use PR Templates
Many projects provide a .github/PULL_REQUEST_TEMPLATE.md. Fill it out completely—it guides you to include necessary information.
📊 Production Insight
We once merged a PR with a vague description that later caused a regression. The author had left, and no one understood the intent. Always document why.
🎯 Key Takeaway
A well-written PR reduces back-and-forth and gets your code merged faster.

Code Review: Giving and Receiving Feedback

Code review is a conversation, not a gate. When reviewing, focus on correctness, maintainability, and adherence to project standards. Be specific: 'This function could throw if data is null' is better than 'This looks wrong'. When receiving feedback, don't take it personally. Address each comment, even if it's just to explain why you disagree. In production, we use a 'resolve conversation' button only after the issue is addressed. If a reviewer requests changes, make them and re-request review. Never dismiss reviews without discussion.

review-comment.txtTEXT
1
2
3
4
5
Reviewer: Could we add a null check here? If `user` is undefined, line 42 will crash.

Author: Good catch. Added a guard clause. Please re-review.

Reviewer: Looks good now. Approved.
💡Review in Small Batches
Don't review more than 400 lines at a time. Your brain fatigues and misses issues. Ask the author to split large PRs.
📊 Production Insight
A missing null check in a reviewed PR caused a production outage affecting 10k users. The reviewer had approved after a quick glance. Slow down.
🎯 Key Takeaway
Code review is a shared responsibility to maintain quality, not a hurdle to overcome.

Handling Merge Conflicts: The Inevitable Reality

Merge conflicts happen when two branches modify the same part of a file. The fix is to resolve them manually. Use git mergetool or an IDE. Understand both sides of the conflict—don't just keep your version. In production, we've seen conflicts that were resolved incorrectly, introducing bugs. After resolving, test the code. If you're unsure, ask the other author. To minimize conflicts, sync your branch frequently and communicate with other contributors about shared files.

resolve-conflict.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Rebase onto upstream/main to catch conflicts early
git fetch upstream
git rebase upstream/main

# Git will pause and mark conflicted files
# Edit the files to resolve conflicts (look for <<<<<<<, =======, >>>>>>>)

# After resolving:
git add .
git rebase --continue

# Force push
git push origin my-feature --force-with-lease
Output
Auto-merging src/app.js
CONFLICT (content): Merge conflict in src/app.js
Resolved conflict in src/app.js.
⚠ Don't Resolve Conflicts Blindly
Always understand what both sides intended. A bad merge resolution can silently introduce bugs that pass tests.
📊 Production Insight
A developer once resolved a conflict by keeping their version, deleting a critical security check. The bug went unnoticed for two weeks until a security audit.
🎯 Key Takeaway
Merge conflicts are normal. Resolve them carefully, test, and communicate with the other contributor.

CI/CD and Testing: Let the Machines Check First

Before your PR is reviewed, automated checks should run: linting, unit tests, integration tests, and security scans. Most open source projects use GitHub Actions or similar. Run these tests locally before pushing to avoid wasting CI resources. In production, we've seen PRs that failed linting because the contributor didn't run npm run lint. Always run the project's test suite. If tests fail, fix them before requesting review. CI is your first line of defense—don't ignore it.

.github/workflows/ci.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
name: CI
on: [pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run lint
      - run: npm test
Output
All checks passed: lint, test.
🔥Run Tests Locally First
Don't rely on CI to catch basic errors. Run the test suite before pushing to save time and CI credits.
📊 Production Insight
A PR that skipped CI due to a misconfigured workflow introduced a breaking change that took down the staging environment for 3 hours.
🎯 Key Takeaway
Automated checks are your safety net. Always run them locally and ensure they pass before opening a PR.

After Merge: Clean Up and Celebrate

Once your PR is merged, delete your feature branch from your fork. This keeps your fork clean and prevents stale branches. Sync your fork's main branch with upstream. If your PR was part of a larger feature, update any related issues or documentation. In production, we automate branch deletion after merge using GitHub settings. Also, update your local repository: git checkout main && git pull upstream main && git push origin main. Then delete the local branch: git branch -d my-feature.

cleanup.shBASH
1
2
3
4
5
6
7
8
9
10
# Switch to main and sync
git checkout main
git pull upstream main
git push origin main

# Delete remote branch
git push origin --delete fix/login-error

# Delete local branch
git branch -d fix/login-error
Output
Deleted remote branch fix/login-error.
Deleted local branch fix/login-error.
💡Automate Cleanup
Enable 'Automatically delete head branches' in your GitHub repo settings to remove branches after PR merge.
📊 Production Insight
We once had over 200 stale branches in a repo, causing confusion and slowing down git branch operations. Regular cleanup is essential.
🎯 Key Takeaway
Clean up after merge to keep your fork tidy and avoid confusion with stale branches.

Etiquette and Community: Beyond the Code

Open source is people. Be respectful in discussions, even when disagreeing. Read the project's contributing guidelines and code of conduct. If you're stuck, ask questions in the designated channels (issues, discussions, Slack). Don't demand features—propose solutions. In production, we've seen contributors get frustrated when their PR isn't reviewed immediately. Maintainers are often volunteers. Be patient and follow up politely after a reasonable time (e.g., one week). If your PR is stale, consider pinging with a friendly comment.

follow-up-comment.txtTEXT
1
Hi maintainers, just checking in on this PR. I've addressed all the feedback. Let me know if there's anything else needed. Thanks!
🔥Read the Contributing Guide
Every project has its own workflow. Some require signed commits, others have specific PR templates. Ignoring these can get your PR closed.
📊 Production Insight
A contributor once spammed the issue tracker with 'when will this be merged?' comments, getting banned from the project. Respect maintainers' time.
🎯 Key Takeaway
Good communication and patience are as important as good code in open source.

Advanced: Managing Multiple Forks and Upstreams

Sometimes you need to contribute to multiple forks or track several upstreams. For example, you might fork a library, then fork another project that depends on it. Use multiple remotes to manage this. Name them descriptively: upstream-lib, upstream-app. You can also use git worktree to check out multiple branches simultaneously. In production, we use a script to automate syncing all forks. Be careful with force pushes when multiple remotes are involved—you might accidentally push to the wrong one.

multiple-remotes.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Add a second upstream
git remote add upstream-lib https://github.com/another-org/lib-repo.git

# Fetch from both
git fetch upstream
git fetch upstream-lib

# Create a branch tracking upstream-lib/main
git checkout -b lib-fix upstream-lib/main

# Make changes, then push to your fork
git push origin lib-fix
Output
Branch 'lib-fix' set up to track remote branch 'main' from 'upstream-lib'.
⚠ Double-Check Remote Names
When pushing, verify you're pushing to the correct remote. A wrong push can contaminate a fork or upstream.
📊 Production Insight
A developer once pushed a feature branch to the upstream repo instead of their fork, causing confusion and a temporary revert. Always verify remotes.
🎯 Key Takeaway
Multiple remotes give you flexibility but require careful naming and attention to avoid mistakes.

Common Pitfalls and How to Avoid Them

Even experienced contributors make mistakes. Common pitfalls include: forgetting to sync before creating a branch, committing directly to main, using git push --force instead of --force-with-lease, and not testing after resolving conflicts. Another is opening a PR from your fork's main branch—always use a feature branch. In production, we've seen all of these. The fix is discipline: follow the workflow every time, use scripts to automate repetitive steps, and double-check before pushing. If you mess up, don't panic—most mistakes are fixable with git reflog.

reflog-recovery.shBASH
1
2
3
4
5
6
7
8
# If you accidentally reset or lost commits, use reflog
git reflog

# Find the commit hash you need, then reset to it
git reset --hard <commit-hash>

# Or create a new branch from that commit
git checkout -b recovery-branch <commit-hash>
Output
abc1234 HEAD@{0}: reset: moving to HEAD~2
def5678 HEAD@{1}: commit: fix: handle edge case
⚠ Don't Panic, Use Reflog
Git keeps a history of your HEAD movements. If you lose commits, git reflog is your lifeline.
📊 Production Insight
A junior dev accidentally deleted their branch after a force push gone wrong. We recovered it from reflog in 2 minutes. Always keep a backup.
🎯 Key Takeaway
Mistakes happen. Know how to recover using git reflog and always follow the standard workflow.
● Production incidentPOST-MORTEMseverity: high

The 18-Month-Old PR That Could Never Be Merged

Symptom
A first-time contributor submitted a well-written PR fixing a documentation bug. They had forked the repo 18 months prior and never synced. The PR had 1,200 merge conflicts. Maintainers closed it as 'stale' after spending 2 hours trying to resolve conflicts.
Assumption
The contributor assumed forking was a one-time action. They didn't realize their fork would drift from the original repo over time, making their PR impossible to merge.
Root cause
1. Contributor forked the repo 18 months ago. 2. Made a small change on their fork's main branch. 3. Submitted a PR from fork/main to original/main. 4. Over 18 months, the original repo had 1,200+ commits. 5. The fork's main branch was 1,200 commits behind. 6. The diff between fork/main and original/main included thousands of unrelated changes. 7. Merge conflicts affected nearly every file. 8. Maintainers spent 2 hours manually resolving before giving up.
Fix
1. Closed the stale PR with a friendly message explaining why. 2. Contributor created a fresh fork and re-applied the change. 3. Submitted a new PR from a feature branch (not main) based on the latest upstream. 4. The new PR had zero merge conflicts and was merged within 24 hours. 5. Added a CONTRIBUTING.md guide explaining the fork-and-sync workflow.
Key lesson
  • Always keep your fork synchronized with upstream.
  • Never work on your fork's main branch.
  • Create a feature branch for each contribution.
  • Sync before creating a new PR: git fetch upstream && git rebase upstream/main.
Production debug guideUse this when production is on fire3 entries
Symptom · 01
I want to contribute to an open-source project
Fix
Fork the repo on GitHub, clone your fork, add upstream remote
Symptom · 02
My fork is behind upstream and I need to sync
Fix
Run git fetch upstream && git rebase upstream/main on your main branch
Symptom · 03
My PR has merge conflicts
Fix
Rebase your feature branch on the latest upstream: git rebase upstream/main
⚙ Quick Reference
12 commands from this guide
FileCommand / CodePurpose
fork-setup.shgit clone https://github.com/YOUR_USERNAME/upstream-repo.gitWhy Fork? The Case for Controlled Collaboration
sync-fork.shgit checkout my-featureSyncing Your Fork
branch-create.shgit checkout mainBranching Strategy
squash-commits.shgit rebase -i HEAD~3Commit Hygiene
pr-template.mdFixes the login error when email field is empty.Opening a Pull Request
review-comment.txtReviewer: Could we add a null check here? If `user` is undefined, line 42 will c...Code Review
resolve-conflict.shgit fetch upstreamHandling Merge Conflicts
.githubworkflowsci.ymlname: CICI/CD and Testing
cleanup.shgit checkout mainAfter Merge
follow-up-comment.txtHi maintainers, just checking in on this PR. I've addressed all the feedback. Le...Etiquette and Community
multiple-remotes.shgit remote add upstream-lib https://github.com/another-org/lib-repo.gitAdvanced
reflog-recovery.shgit reflogCommon Pitfalls and How to Avoid Them

Key takeaways

1
Fork creates a personal copy
clone your fork locally
2
Add upstream remote pointing to the original repo
3
Never work on your fork's main branch
use feature branches
4
Sync with upstream before each contribution to avoid merge conflicts
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the difference between forking and branching?
02
How often should I sync my fork with the upstream?
03
What should I do if my pull request has merge conflicts?
04
How do I handle a situation where a maintainer is not responding to my PR?
05
Can I contribute to open source without knowing how to code?
06
What is the best way to learn a project's codebase before contributing?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 11, 2026
last updated
2,406
articles · all by Naren
🔥

That's Git. Mark it forged?

4 min read · try the examples if you haven't

Previous
GitHub CLI: Manage Repositories from the Terminal
37 / 47 · Git
Next
Trunk-Based Development: Merge to Main Multiple Times Daily