AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

GitHub Collaboration: Working as a Team

📚 Version Control⏱️ 21 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: September 2026 CBSE-aligned · Peer-reviewed · 21 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

DIGIT is a piece of software that decides whether your family's property tax bill gets calculated correctly, or whether a pothole complaint filed with your municipal corporation ever reaches an engineer. It is built by the Bengaluru-based eGov Foundation, its code is public, and it has been adopted by several Indian state governments to run services like property tax collection and public grievance redressal. None of the people who write that code sit in one office. A developer in Pune might fix a bug in the tax-calculation module on a Tuesday while a developer in Hyderabad is, at the very same moment, adding a new grievance category to a different file in the same project. Neither of them asks permission first. Neither of them waits for the other to finish. And yet the two changes end up combined, correctly, in the version of DIGIT that municipalities actually run.

That is not a Git trick — you already know Git from Grade 8: git init, git add, git commit give you a private history of snapshots on your own machine. What DIGIT's workflow needs is something Git alone does not give you: a shared copy of that history that many people can push changes to and pull changes from, a way to work on a change without touching anyone else's work-in-progress, and a structured way to have someone else look at a change before it becomes part of the official codebase. That something is GitHub, and the discipline around using it is what this chapter teaches: branching, pushing, pull requests, code review, and — because it will happen to you in your very first week of teamwork — resolving a merge conflict correctly instead of panicking and overwriting someone's work.

From one history to a shared one: what GitHub adds to Git

Git tracks history. GitHub hosts a copy of that history on a server and wraps it in a website that a team can use to talk about the history. The two are not the same thing, and keeping them separate in your head will save you a lot of confusion: Git is the tool that takes snapshots; GitHub is one company's product for storing and sharing those snapshots. (GitLab and Bitbucket do the same job with different interfaces.)

When a repository exists on GitHub, your local copy of it has a name for that server copy: origin. Two commands move work between the two:

git push origin main     # send your local commits to GitHub
git pull origin main     # bring GitHub's commits into your local copy

If you're starting from nothing, you get your own local copy of a GitHub repository with git clone <url>. Clone gives you the entire commit history, not just the latest snapshot — every commit anyone has ever pushed, downloaded once, sitting on your disk. That matters later: it's why you can inspect history, switch branches, and resolve conflicts entirely offline.

There are two shapes this teamwork can take, and DIGIT's own workflow actually uses both, for different kinds of contributor. eGov Foundation's own engineers, who have write access to the repository, create branches directly inside it. An outside developer — a college student who spots a bug and wants to fix it, with no write access at all — instead makes a personal copy of the whole repository under their own GitHub account, called a fork, makes changes there, and opens a pull request asking the original project to pull those changes in. A fork is a full, independent copy of the repository; a branch is a line of work inside one repository. Small teams with shared write access — which is what you and your classmates will have on a school project — almost always just use branches. We'll use the branch model for the rest of this chapter, because it is simpler and it is what you will actually do first.

Misconception: "a new branch copies the whole project"

Here is the mental model most students bring into their first week of GitHub, and it is wrong in a way that causes real anxiety: they picture git branch feature-x as making a photocopy of every file in the project into some new hidden folder. If that were true, ten teammates each working on their own branch would mean eleven complete copies of the codebase sitting around, and merging them would mean somehow reconciling eleven separate folders.

A branch is not a copy of the files. A branch is a movable label — technically, a 40-character pointer — attached to one specific commit. When you run git checkout -b feature-x, Git does not duplicate a single file. It creates a new label called feature-x and points it at whatever commit you were standing on, right next to the main label already pointing at that same commit. Both labels point at the exact same history at that instant. The moment you make a new commit while on feature-x, only the feature-x label moves forward to the new commit; main stays exactly where it was. That is the entire mechanism. There is one shared pool of commits underneath, and branches are nothing more than names pointing into that pool — which is also precisely why Git can create a new branch instantly, regardless of whether the project has ten files or ten thousand, and why two branches can be merged later by comparing pointers and commits, not by diffing entire folders against each other.

The pull request workflow

With that model in place, the day-to-day loop a GitHub team runs looks like this, and it is the same loop whether the team is three students or the eGov Foundation's engineers:

  1. Start from an up-to-date main: git pull origin main.
  2. Create a branch named for the one thing you're about to do: git checkout -b add-strike-rate.
  3. Make commits on that branch as you work, exactly as you did solo in Grade 8.
  4. Push the branch to GitHub: git push origin add-strike-rate. This uploads your branch's commits and creates the branch pointer on the server too — main on GitHub is untouched.
  5. Open a pull request (PR) on GitHub's website: a request that says "please compare my branch against main and, if it looks good, merge it in."
  6. A teammate reviews the PR — GitHub shows the changed lines directly, and reviewers can leave comments on specific lines, approve, or request changes.
  7. Once approved, someone merges the PR. GitHub creates a new commit on main that combines both histories.
  8. Everyone else runs git pull origin main to bring that merged work onto their own machine before starting their next branch.

Notice the name "pull request" is a common source of confusion worth heading off directly: it has nothing to do with the git pull command you run locally. A pull request does not pull anything onto your computer. It is a request, sitting on GitHub's website, asking a human reviewer to eventually pull your branch into main. You could go your entire career without ever typing the words "pull request" into a terminal — it exists only as a review step on the GitHub website, not as a Git command.

How the pieces fit together

Commit graph: two branches, one file, one conflict main C1 def batting_average(...) diya-precision D1 round(..., 1) C2 merge PR #1 (Diya) K1 guard dismissals != 0 kabir-fix-crash ! same line edited on both branches C3 merge PR #2 conflict resolved Branch names (blue, orange) are pointers to a single commit — not copies of the project. A conflict fires only when two branches edit the same lines since their shared ancestor, C1.

Worked example: tracing a real merge conflict, start to finish

Suppose your team is building a small Python tool, cricket_stats.py, for a school data-analytics project. The version everyone starts from — commit C1 on main — has one function:

def batting_average(runs, innings, not_outs):
    """Return the batting average: runs scored per dismissal."""
    dismissals = innings - not_outs
    return round(runs / dismissals, 2)

It has a real bug: if a batter was never dismissed in any innings they played (not_outs == innings), then dismissals is 0, and runs / dismissals raises ZeroDivisionError and crashes the program.

Diya doesn't know that bug exists yet. She just thinks two decimal places is more precision than a scoreboard needs, so from C1 she creates git checkout -b diya-precision and changes the return line to:

    return round(runs / dismissals, 1)

She commits, pushes, opens a PR, a teammate approves it, and it gets merged. main now sits at commit C2, with the 1-decimal version live.

Kabir, working at the same time, branched from the same starting point C1 — before Diya's merge — with git checkout -b kabir-fix-crash, because he noticed the crash while testing a never-dismissed player. He changes the very same return line to guard against it:

    return round(runs / dismissals, 2) if dismissals != 0 else None

He commits and pushes his branch, then opens a PR. GitHub compares his branch against the current main — which is now C2, not C1 — and immediately shows: "This branch has conflicts that must be resolved."

Here is exactly why. Git's merge algorithm is a three-way comparison: it looks at the common ancestor (C1), Kabir's branch tip, and main's tip (C2), and merges any line that changed on only one side automatically. The return line changed on both sides relative to C1 — Diya changed 2 to 1, Kabir changed the line into a conditional — so Git cannot pick a winner by itself. It stops and hands the decision to a human.

Kabir fixes this locally, never touching GitHub's website:

git fetch origin
git merge origin/main

Git rewrites his file, inserting conflict markers exactly where the two versions disagree:

def batting_average(runs, innings, not_outs):
    """Return the batting average: runs scored per dismissal."""
    dismissals = innings - not_outs
<<<<<<< HEAD
    return round(runs / dismissals, 2) if dismissals != 0 else None
=======
    return round(runs / dismissals, 1)
>>>>>>> origin/main

Everything between <<<<<<< HEAD and ======= is Kabir's own branch content; everything between ======= and >>>>>>> origin/main is what's currently on main. Neither one is "correct" — the tool is not telling him to pick one, it's telling him both people had a reason for their change, and a human needs to combine them. Kabir reads both, realizes they solve different problems, and writes the line that does both jobs, deleting all three marker lines by hand:

    return round(runs / dismissals, 1) if dismissals != 0 else None

The full resolved function:

def batting_average(runs, innings, not_outs):
    """Return the batting average: runs scored per dismissal."""
    dismissals = innings - not_outs
    return round(runs / dismissals, 1) if dismissals != 0 else None

He runs git add cricket_stats.py, then git commit — Git already has a default message ready, Merge remote-tracking branch 'origin/main' into kabir-fix-crash — and git push. His PR on GitHub now shows no conflicts; it gets approved and merged as commit C3.

Trace the resolved function against two calls, to confirm the merge did what both branches wanted:

print(batting_average(240, 12, 2))

dismissals = 12 - 2 = 10. That's not 0, so the conditional takes the first branch: round(240 / 10, 1) = round(24.0, 1) = 24.0. Output: 24.0 — Diya's one-decimal formatting, present.

print(batting_average(58, 5, 5))

dismissals = 5 - 5 = 0. The conditional's else branch fires, no division ever runs. Output: None — Kabir's crash fix, present, and the program never touches the old ZeroDivisionError path at all.

Active recall

Attempt each question before reading its answer below.

  1. What is the difference between running git pull and opening a pull request on GitHub?
  2. Why doesn't creating ten branches in a repository make the repository ten times bigger on disk?
  3. Aarav also branches from C1, before Diya's merge, with git checkout -b aarav-strike-rate. He adds a brand-new function, strike_rate(), at the very end of the file, and never touches the batting_average function. After Diya's branch is merged (main is now at C2), Aarav opens his PR. Will GitHub report a conflict? Justify your answer using the three-way comparison described above.
  4. Write the exact conflict markers Git inserts into Kabir's file, and the exact resolved line, using the versions given in the worked example.
  5. Suppose that between C2 and C3, before Kabir's PR is merged, a teammate opens a separate PR that renames the parameter not_outs to notouts_count everywhere it's used inside batting_average — including the line dismissals = innings - not_outs — and that PR gets merged into main first. Trace the full effect on Kabir's still-open PR: does his merge with the new main produce a text conflict on the lines he touched? What silently breaks elsewhere in the project that a clean, conflict-free merge would not warn anyone about? What should the team have done differently?
  6. A teammate says, "Merge conflicts mean someone made a mistake." Is that accurate? Explain using what triggers a conflict.

Answers.

1. git pull is a local terminal command that downloads commits from a remote (GitHub) and merges them into your current branch on your own machine — it moves code onto your computer. A pull request is a request created on GitHub's website asking a reviewer to merge one branch into another (typically into main) — it moves nothing by itself; it's a conversation and an approval gate that ends with someone else running the equivalent of a merge on the server. The shared word "pull" refers to two different directions of the same underlying idea (bringing branches together), which is exactly why the names collide and confuse people.

2. A branch is a pointer to a single commit, not a duplicate of the project's files. Creating a branch writes a few dozen bytes — a name and the hash it points to — into Git's internal records. The actual file contents already exist once, shared by every commit and every branch that includes them. Ten branches means ten pointers into the same pool of commits, not ten copies of the codebase.

3. No conflict. Git's three-way merge compares Aarav's branch, main (C2), and their common ancestor (C1) line by line. Diya's change touched the return line inside batting_average; Aarav's change only added new lines at the end of the file and touched nothing inside batting_average. Since the two branches changed different lines relative to C1, Git can apply both automatically — Diya's edited return line and Aarav's new function both land in the merged file with no marker needed.

4. Markers, exactly as Git writes them during git merge origin/main on Kabir's branch:

<<<<<<< HEAD
    return round(runs / dismissals, 2) if dismissals != 0 else None
=======
    return round(runs / dismissals, 1)
>>>>>>> origin/main

Resolved line, combining both intents:

    return round(runs / dismissals, 1) if dismissals != 0 else None

5. No text conflict on the lines Kabir touched. His branch only ever edited the return line; the rename PR edited the def line and the dismissals = innings - not_outs line. Different lines relative to the shared ancestor, so Git's three-way merge auto-applies both sides silently — Kabir's conditional return line merges in unchanged, and the parameter name on the lines around it flips to notouts_count with no marker and no warning. That silence is the danger: Git only checks whether the same lines changed, never whether the code still makes sense together. Anywhere else in the project that calls the function using the old keyword name — for example a test file with batting_average(runs=240, innings=12, not_outs=2), or a print statement elsewhere in cricket_stats.py itself using not_outs= — was not touched by either PR, so it merges in untouched too, and now raises TypeError: batting_average() got an unexpected keyword argument 'not_outs' the next time it runs. The rename and its ripple were never caught by a conflict, because a conflict only fires on overlapping lines, not on broken meaning. The team's actual mistake was doing a rename this wide as an isolated PR without first searching the whole repository for every call site (grep -rn "not_outs" . would have found them) and updating all of them in the same PR, or at minimum flagging in the PR description that any other open branch touching batting_average needs to rebase and update its call sites before merging.

6. Not accurate. A conflict fires whenever two branches independently change the same lines of the same file since their last common commit — it is a structural fact about two people editing the same spot, not a judgment about either person's code quality. Diya and Kabir both wrote correct, reasonable code; the conflict happened purely because they picked the same line to change without knowing about each other's branch. Conflicts are routine in real teams and are resolved by reading both changes and combining intent, exactly as shown above — not a sign anyone did anything wrong.

Think About It

Think about this: How would you explain github collaboration: working as a team 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.

Practice Exercises

Now it is time to practice! Complete these challenges to solidify your understanding:

  • Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
  • Exercise 2: Find a real-world example where github collaboration: working as a team is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
  • Exercise 3: Create a mind-map connecting github collaboration: working as a team to at least 3 other topics you have studied.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind github collaboration: working as a team, 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.

← Git Branching: Organizing Team DevelopmentResolving Git Merge Conflicts →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn