In April 2005, Linus Torvalds lost the right to use BitKeeper, the proprietary version-control tool the Linux kernel had depended on for three years. He did not go looking for a replacement. He wrote one. Within roughly two weeks he had a tool self-hosting its own source code — Git. Two decades later, every kernel release carries patches from well over a thousand contributors, none of whom asked a company's permission to send one. Every May since, tens of thousands of students worldwide apply to Google Summer of Code, and for over a decade India has sent one of the largest contingents of accepted students of any country — a large share of their first accepted patches look exactly like the one-line fix you are about to trace by hand in this chapter.
This chapter will not tell you that open source is a community of kind strangers waiting to mentor you, although it often is. It will show you the actual data structure your contribution becomes the moment a maintainer clicks "merge," the algorithm that decided what "changed" in your patch, and the one belief about that process most students get wrong on their first pull request.
The pipeline: what actually happens when you contribute
"Contributing to open source" is a specific, mechanical sequence, not a vague act of goodwill. For a project you don't have write access to — which is almost every project the first time you touch it — the sequence is:
- Fork: GitHub creates a server-side copy of the repository under your account, sharing history with the original ("upstream") repo at the moment of forking.
- Clone:
git clonepulls your fork onto your machine, giving you a full local copy of the object database — every commit, every file version, ever. - Branch:
git checkout -b fix-circumference-docscreates a new pointer so your work is isolated from the fork'smain. - Commit: each
git commitsnapshots the project state and chains it to its parent. - Push: your branch travels back to your fork on GitHub.
- Pull request (PR): you ask the upstream maintainers to pull your branch into their
main. This is a request against upstream, not an automatic merge. - CI: an automated pipeline (commonly GitHub Actions, triggered by
.github/workflows/*.ymlon thepull_requestevent) runs lint, type checks, and the test suite against your branch. - Review: a human maintainer reads the diff, asks for changes or approves.
- Merge: your commits — or a rewritten version of them — become part of upstream's permanent history.
Every step from "fork" to "merge" is really just operations on a graph of hashed objects. Understanding that graph is what separates "I copy-pasted a fix" from "I understand what I just did to the project's history" — and it is what the rest of this chapter is actually about.
Content-addressable storage: why a commit's identity is its hash
Git does not store files as diffs against a previous version the way you might assume from watching git diff output. It stores three kinds of objects, each identified by the cryptographic hash of its own content:
- blob — the raw bytes of one file's content at one point in time.
- tree — a directory listing: names mapped to blob or subtree hashes.
- commit — a tree hash, one or more parent commit hashes, an author, a timestamp, and a message.
The critical fact, which the worked example below will make concrete: a commit's hash is computed over all of these fields, including its parent pointer. Two commits with byte-identical file changes and byte-identical messages will still get different hashes if their parent commits differ. This single fact is the root cause of the misconception this chapter corrects.
Worked example: how Git computes a diff (and how you'll read one in review)
Suppose your first contribution is a genuinely typical one: a small geometry module is missing a docstring, and a maintainer has tagged the issue "good first issue." The file before your change (A, 5 lines) and after (B, 6 lines):
A (before) B (after)
1 import math 1 import math
2 def area(r): 2 def area(r):
3 return math.pi * r * r 3 return math.pi * r * r
4 def circumference(r): 4 def circumference(r):
5 return 2 * math.pi * r 5 """Return the circumference of a circle with radius r."""
6 return 2 * math.pi * r
When you run git diff, Git does not "notice" line 5 was inserted by magic — it computes the longest common subsequence (LCS) of the two line sequences and treats everything outside that subsequence as added or removed. This is the exact same LCS dynamic program you already know from DSA, applied to lines of text instead of characters. Let dp[i][j] be the length of the LCS of the first i lines of A and the first j lines of B, with the usual recurrence: dp[i][j] = dp[i-1][j-1] + 1 if line i of A equals line j of B, else dp[i][j] = max(dp[i-1][j], dp[i][j-1]), with dp[0][*] = dp[*][0] = 0. Filling the full table by hand:
| ∅ | B1 | B2 | B3 | B4 | B5 | B6 | |
|---|---|---|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| A1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
| A2 | 0 | 1 | 2 | 2 | 2 | 2 | 2 |
| A3 | 0 | 1 | 2 | 3 | 3 | 3 | 3 |
| A4 | 0 | 1 | 2 | 3 | 4 | 4 | 4 |
| A5 | 0 | 1 | 2 | 3 | 4 | 4 | 5 |
(B1–B6 are import math, def area(r):, return math.pi*r*r, def circumference(r):, the new docstring, return 2*math.pi*r respectively.) The bottom-right cell, dp[5][6] = 5, is the LCS length. Backtracking from dp[5][6]: A5 = B6 (match, both are the final return line) → step diagonally to dp[4][5]. There, A4 ≠ B5, and dp[4][4] = dp[4][5] = 4 while dp[3][5] = 3, so the larger neighbor is dp[4][4] — this means B5 was consumed with no match, i.e. it is a pure insertion. Continue diagonally through matches at (4,4), (3,3), (2,2), (1,1), all equalities, down to dp[0][0]. The reconstructed alignment is: lines 1–4 of A map straight across to lines 1–4 of B, line 5 of B is unmatched (an insertion), and line 5 of A maps to line 6 of B. That is exactly the single-line insertion you'd expect — but now you've derived it, not eyeballed it. This is precisely what git diff prints, in the "unified diff" format:
@@ -1,5 +1,6 @@
import math
def area(r):
return math.pi * r * r
def circumference(r):
+ """Return the circumference of a circle with radius r."""
return 2 * math.pi * r
The hunk header @@ -1,5 +1,6 @@ reads: "starting at line 1, the old file contributes 5 lines and the new file contributes 6." Every context line (unmarked, leading space) is a matched pair from the LCS backtrack; every + line is an unmatched insertion. When your reviewer opens your PR, this is the algorithm producing what they see — and it's also the algorithm GitHub's merge machinery uses to decide whether two branches' changes conflict: if two edits touch the same LCS-alignment region, that's a merge conflict; if they touch disjoint regions, Git merges them automatically.
Reading the commit graph
A merged PR doesn't just add a diff — it adds nodes to a directed acyclic graph (DAG) of commit objects, each pointing at its parent(s) by hash. The diagram below traces the same two commits — the docstring fix above, plus a second commit fixing a typo — through two different ways a maintainer might land your branch: an ordinary merge, and a rebase.
Reading this as git log --graph --oneline would print it makes the branching explicit. Merge:
* 77cc88 Merge branch 'docstring-fix' into main
|\
| * 33bb44 fix typo in comment
| * 11aa22 add docstring to circumference()
* | 9a8b7c main: unrelated bug fix #212
|/
* d4e5f6 branch point
* a1b2c3 initial commit
Rebase, landed on the same two commits:
* 8c9d0e fix typo in comment
* 5f6e7d add docstring to circumference()
* 9a8b7c main: unrelated bug fix #212
* d4e5f6 branch point
* a1b2c3 initial commit
Common misconception: "merge and rebase produce the same thing"
Nearly every student's first reaction to seeing both graphs is that it doesn't matter which one a maintainer uses, since the final file contents in circumference.py are identical either way — and that part is true. What's wrong is concluding the two outcomes are interchangeable. They differ in three concrete, checkable ways: (1) commit count and shape — merge keeps 2 extra commits plus a merge commit (3 new nodes, 2 parents on one of them); rebase keeps exactly 2 linear commits, no merge commit; (2) hash identity — as the diagram shows, 11aa22 and 5f6e7d carry byte-identical trees but different hashes, because a commit's hash is computed over its parent field too, and rebase changes that field; (3) shared-history safety — because rebase manufactures new hashes for commits other people may already have pulled, running git push --force after a rebase on a branch others are also working from silently orphans their copies of the old commits. This is precisely why most large projects (the Linux kernel, Zulip, most Apache Software Foundation projects) publish an explicit policy in CONTRIBUTING.md stating whether they want contributors to rebase before opening a PR, and whether maintainers will merge-commit, squash-merge, or rebase-merge on landing — it is a graph-shape and hash-identity decision, not a cosmetic one.
Licenses: what you're allowed to touch, and what you owe back
Every file you fork carries a license, and it governs your patch too. The two families you'll meet constantly: permissive licenses (MIT, BSD, Apache-2.0) require only that you preserve the copyright notice — you may fold the code into a closed-source product. Copyleft licenses (GPL-2.0, GPL-3.0) require that if you distribute a work built on GPL code, the combined work's source must also be released under GPL; this is why most companies avoid embedding GPL-licensed code inside proprietary products they ship externally, while happily using MIT-licensed code the same way. Apache-2.0 adds an explicit patent grant on top of permissive terms, protecting downstream users from patent claims by contributors — a clause MIT lacks. When you submit a PR, the near-universal norm (formalized in most platforms' terms of service and most projects' contributor policies) is "inbound = outbound": your contribution is licensed under the project's existing license by default, not under whatever license you personally prefer. Some foundations go further and require a formal CLA (Contributor License Agreement) — a signed document assigning or licensing your copyright to the project, historically used by projects under corporate or foundation stewardship. Others use the lighter-weight DCO (Developer Certificate of Origin): you add Signed-off-by: Your Name <email> to each commit message (automatically via git commit -s), certifying you wrote the patch or have the right to submit it. The Linux kernel and Docker use DCO; check CONTRIBUTING.md before your first PR — a PR that fails a required DCO check gets auto-blocked by a bot before a human ever looks at it.
The gate before merge: CI, review, and versioning
Maintainers configure certain CI jobs as required status checks: until lint, type-checking, and the test suite all report green, the merge button stays disabled — regardless of whether a human has approved. This is a deliberate division of labor: machines catch mechanical defects (a missing import, a failing assertion, a style violation) so human reviewers spend their limited attention on things machines can't judge — is this the right abstraction, does this change match the issue, is the docstring actually correct. Your PR's diff is exactly the LCS-derived hunks from the section above; a good review comment references specific added lines, and a good response is a new commit (not a force-push that erases the commit the comment referred to, unless the project's workflow explicitly expects squashing). Once merged, a release is tagged under Semantic Versioning (MAJOR.MINOR.PATCH): your docstring addition, being backward-compatible and non-breaking, would bump PATCH; a new public function would bump MINOR; a change to a function's existing signature or behavior would bump MAJOR. This is how every downstream project decides, mechanically, whether it's safe to auto-upgrade to your merged change.
Bus factor and why maintainers gatekeep
"Bus factor" is the number of contributors who would need to disappear before a project stalls. A project with bus factor 1 has exactly one person who understands its architecture well enough to review and merge safely — every PR queue, every security patch, every release waits on that one person's availability. This is precisely why maintainers seem slow or strict with first-time contributors: every merge they approve is a commit they are now implicitly vouching for, permanently, in a graph they cannot cheaply rewrite once others have pulled it. Small, well-scoped, well-tested PRs (like the single-docstring example above) are reviewable in minutes and directly reduce the risk that lands on the maintainer; large, untested, multi-purpose PRs are exactly what raises a project's effective bus factor by making review a bottleneck only the most senior maintainer can clear.
Where to actually start
Pick a project you already use, not one you admire from a distance — you'll spot real bugs faster. Search its issue tracker for labels like good first issue or help wanted, standard conventions used across React, VS Code, Kubernetes, and most GSoC-participating projects including Zulip. Read CONTRIBUTING.md before writing code — it tells you the DCO/CLA requirement, the test command, and the commit-message convention (Git's own documentation recommends imperative mood: "Fix off-by-one in circumference," not "Fixed" or "Fixes"). Run the existing test suite locally before you touch anything, so you know your environment works before you start debugging your own change. Open small; a single-purpose PR that does one traceable thing, like the docstring fix above, is far more likely to be merged on your first attempt than a sweeping refactor.
Active recall
Attempt these before reading the answers.
- In the worked LCS table, why is
dp[5][6]exactly 5 and not 6, given thatBhas 6 lines and every one ofA's 5 lines has a match somewhere inB? - Why does
git rebasechange the hash of a commit whose file content diff is byte-for-byte identical to before? - Distinguish fork from clone in the contribution pipeline: what exists where, after each?
- You want to embed a GPL-licensed library inside a product you plan to sell as closed-source. What's the practical consequence, and how does that differ from using an MIT-licensed library the same way?
- Your PR's CI run fails on a lint check. Will a maintainer typically review your code changes before that's fixed? Why does that ordering make sense given the merge-gate discussion above?
- A project has bus factor 1. Name one concrete practice from this chapter that would raise it, and explain the mechanism by which it helps.
Answers.
1. An LCS between a sequence of length 5 and a sequence of length 6 can never exceed 5, since it must be a subsequence of the shorter one. Here every line of A does find an in-order match in B, so the LCS achieves that maximum of 5 — the extra line in B (the docstring) is simply excluded from the subsequence, not shortening it, because it doesn't need to consume a match slot; it's purely inserted.
2. A commit object's hash is computed over the entire object: its tree hash (file content), its message, its author/timestamp, and its parent commit hash(es). Rebase re-parents a commit onto a different base commit, which changes the parent field's bytes, which changes the hash of the commit object — even though the tree (the actual file diff) is unchanged. That new hash then propagates: every descendant commit also gets a new hash, since each one embeds its parent's hash too.
3. Forking creates a new repository on the server (GitHub) under your account, sharing history with upstream at the fork point — nothing is on your machine yet. Cloning copies a repository (your fork, in the standard flow) onto your local disk, giving you the full object database to branch and commit against. The flow is fork (server) → clone (local) → branch/commit (local) → push (local back to your fork on the server) → pull request (server, from your fork to upstream).
4. GPL is copyleft: distributing a product that incorporates GPL-licensed code generally obligates you to release the combined work's source under GPL too, which is incompatible with selling it closed-source — most companies avoid this by not embedding GPL code in shipped proprietary products. MIT is permissive: you can embed it in a closed-source product freely, provided you retain the original copyright notice; no obligation to release your own source.
5. No — most projects configure lint/type/test jobs as required status checks, so the merge button stays disabled and reviewers typically wait for a green CI run before spending time reading the diff closely. This division of labor makes sense because CI catches mechanical, objectively-checkable defects cheaply and instantly, while human review time is the scarcer resource better spent judging things a machine can't — correctness of intent, code quality, whether the change actually matches the issue.
6. Adding a second maintainer with merge rights, backed by a mandatory code-review requirement (no self-approval), directly raises bus factor from 1 to 2: now two people independently understand the codebase well enough to review and merge, so either one's unavailability no longer stalls the project. CI acts as a supporting mechanism, not a replacement — it lets the second maintainer safely trust mechanical checks instead of having to independently re-verify everything the first maintainer already knows by memory.
Think About It
Think about this: How would you explain open source contribution guide: join global development to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.
Key Takeaways — Summary and Recap
Let us recap what we covered: the core ideas behind open source contribution guide: join global development, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.