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

Version Control with Git: Never Lose Your Code Again

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

The Folder Full of "Final" Files

Picture a school project folder that has grown, over three weeks, into this: website.html, website_v2.html, website_v2_fixed.html, website_FINAL.html, website_FINAL_ACTUAL.html, website_FINAL_use_this_one.html. Somewhere in that pile is the version with the working navigation menu. Somewhere else is the version with the better colour scheme. Nobody remembers which file has both. This is not a hypothetical — it is what happens to every programmer, in every language, on every operating system, the moment a project outgrows a single sitting. The underlying problem has a name: you need to change your code without losing the ability to go back to how it was before the change.

The naive fix is to keep copying the whole file every time you're about to try something risky. This fails in three specific ways. First, it does not scale — a project with 40 files needs 40 copies for every "just in case" moment, and most of those files didn't even change. Second, it captures no information about why a version exists; a filename like v2_fixed tells you nothing about what was actually fixed. Third, and most seriously, it has no way to combine two lines of work — if you and a partner both copied website_v2.html and each improved a different part, merging your two copies back into one file has to be done by eye, comparing them line by line.

Git is a program, written by Linux creator Linus Torvalds in 2005, that solves exactly this problem. It does not store "copies of files." It stores a sequence of complete, labelled snapshots of your entire project, each one linked to the snapshot before it, each one carrying a short human-written note explaining what changed and why. Once you understand what a snapshot is and how Git links them together, features like "undo a change from two weeks ago" or "try an idea without touching the working version" stop being special tricks and become one or two lines typed into a terminal.

What a Git Repository Actually Is

The word "repository" (or "repo") means a project folder that Git is watching. You turn an ordinary folder into a repository with one command:

$ mkdir cbse-project
$ cd cbse-project
$ git init
Initialized empty Git repository in /cbse-project/.git/

git init creates a hidden subfolder named .git inside your project. That hidden folder is the entire database — every snapshot Git will ever store lives inside it. Delete .git and you have deleted the project's whole history, even though every ordinary file is still sitting right there untouched. This single fact — that history lives in .git, separately from your files — is worth remembering, because it explains almost everything else in this chapter.

Git tracks your project in three distinct places at once, and confusing them is the single most common source of "wait, why didn't my change get saved?" bugs. The three places are:

  • Working directory — the actual files on your disk, the ones you open and edit in your code editor. Any change you make here is invisible to Git until you explicitly tell Git about it.
  • Staging area (also called the "index") — a waiting room. When you run git add on a file, you are not saving it; you are marking it "include this in the next snapshot."
  • Repository — the permanent history inside .git. A file's content only becomes part of this permanent history when you run git commit.

Here is the diagram that ties those three together, followed by a worked example that walks through every arrow in it.

Where does my file actually live right now? Working directory app.js (edited) not tracked by Git yet Staging area app.js (staged) marked "ready", not saved yet Repository (.git) app.js (committed) permanently saved as a snapshot git add git commit A file can be edited, staged, and committed to different states at the same time — "git status" always tells you which of these three states each file is in. $ git status $ git add <file> $ git commit -m "message"

A Worked Example: Your First Commit

Suppose you create a single file inside the empty repository, app.js, containing one line:

console.log("Hello, World!");

Right after saving that file, run git status:

$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        app.js

nothing added to commit but untracked files present

Git has noticed the file exists, but it is sitting in the "working directory" box of the diagram above — completely outside Git's history. Move it into the staging area:

$ git add app.js
$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   app.js

The file is now staged — marked "ready," but still not part of any permanent snapshot. If your laptop crashed at this exact instant, the staging mark would be lost along with the uncommitted file (Git's staging area is stored in .git, but it only records which files are ready, not a permanent, independently recoverable copy the way a commit does). To actually save this snapshot into history:

$ git commit -m "Initial commit: add app.js"
[main (root-commit) c3f8b0e] Initial commit: add app.js
 1 file changed, 1 insertion(+)
 create mode 100644 app.js

The seven-character string c3f8b0e is the start of this commit's unique fingerprint. Git computes it by running the SHA-1 hashing algorithm over the snapshot's full content, its parent commit, its author, its timestamp, and its message — every one of those inputs together, all at once. SHA-1 always outputs 160 bits, written as 40 hexadecimal characters (each character has 16 possible values: 09 and af), so the total number of possible fingerprints is 1640 ≈ 1.46 × 1048. That number is so large that two different commits producing the same fingerprint by accident essentially never happens in practice — which is exactly why Git is willing to use that fingerprint, rather than a filename or a date, as the permanent, trustworthy identity of every snapshot it ever stores.

Misconception to Retire: "git add Saves My Work"

Many beginners assume git add is the save step and git commit is just a formality for writing a message. It is the reverse. git add only moves a file into the staging area — a to-do list for the next snapshot. Nothing is written permanently into .git's history until git commit runs. If you edit a file, stage it with git add, and then edit it again without re-running git add, the version that gets committed is the older, staged one — not what's currently on your screen. This is exactly why git status exists: run it before every commit, and it will tell you precisely which files are staged, which are modified-but-unstaged, and which aren't tracked at all.

Reading Your Project's History

Now edit app.js so it reads:

console.log("Hello, World!");
console.log("Learning Git!");

Before staging it, ask Git what changed:

$ git diff
diff --git a/app.js b/app.js
index 4f9a2c1..8e3d7b6 100644
--- a/app.js
+++ b/app.js
@@ -1 +1,2 @@
 console.log("Hello, World!");
+console.log("Learning Git!");

Read this line by line. index 4f9a2c1..8e3d7b6 100644 gives the abbreviated fingerprints of the file's content before and after the edit, plus its file permissions (100644 means "an ordinary, non-executable file"). --- a/app.js and +++ b/app.js label the old and new versions being compared. @@ -1 +1,2 @@ is the hunk header: it means the old file's shown region starts at line 1 and is 1 line long, while the new file's shown region starts at line 1 and is 2 lines long — which matches exactly what happened, since a one-line file became a two-line file. The unchanged first line is printed with a leading space; the newly added second line is printed with a leading +. Save this second snapshot:

$ git add app.js
$ git commit -m "Add learning message"
[main 7a1e9d2] Add learning message
 1 file changed, 1 insertion(+)

Now git log shows the growing chain of snapshots, newest first:

$ git log --oneline
7a1e9d2 Add learning message
c3f8b0e Initial commit: add app.js

Each commit stores a pointer to its one parent commit — that is the "link" mentioned earlier. 7a1e9d2 points back to c3f8b0e. That chain of pointers, not the filenames on your disk, is what "project history" means to Git.

Branching: Trying an Idea Without Risking the Original

Add one more file, greeting.js, containing:

console.log("Welcome, Class of 2026!");
$ git add greeting.js
$ git commit -m "Add greeting.js"
[main 5e9a2c7] Add greeting.js

Suppose you want to try a different wording for that greeting, but you're not sure it's an improvement, and you don't want to risk breaking the version that's already working. A branch is Git's answer: a movable label pointing at a commit, letting you build a second line of history that starts from the same point but can diverge freely. Creating one costs nothing — no files are copied:

$ git checkout -b experiment-greeting
Switched to a new branch 'experiment-greeting'

main and experiment-greeting now both point at commit 5e9a2c7. Edit greeting.js on this new branch so it reads:

console.log("Hi there, Grade 9!");
$ git commit -am "Try a new greeting for experiment-greeting"
[experiment-greeting 9f4d8a1] Try a new greeting for experiment-greeting
 1 file changed, 1 insertion(+), 1 deletion(-)

(The -am flag combines staging and committing for files Git is already tracking — it skips a separate git add step, but only for files that were already part of a previous commit.) Switch back to main — Git rewrites every file on disk to match whatever main points to, instantly:

$ git checkout main
Switched to branch 'main'

Open greeting.js now and it reads "Welcome, Class of 2026!" again — the experimental edit still exists, safely, on the other branch. Suppose, independently, you also improve the wording on main itself. Edit greeting.js on main so it reads:

console.log("Welcome to Grade 9 Computer Science!");
$ git commit -am "Update welcome message copy"
[main b6c2e05] Update welcome message copy
 1 file changed, 1 insertion(+), 1 deletion(-)

main and experiment-greeting have now genuinely diverged: both branches edited the exact same line of greeting.js starting from the same commit, 5e9a2c7, but in two different directions.

Merging Two Branches, and Resolving a Real Conflict

To bring the experiment back into main:

$ git merge experiment-greeting
Auto-merging greeting.js
CONFLICT (content): Merge conflict in greeting.js
Automatic merge failed; fix conflicts and then commit the result.

Git can automatically combine two branches when they changed different parts of a file — that's the common case, and it happens silently. It cannot guess what to do when both branches changed the same line differently, which is exactly what happened here. Opening greeting.js now shows conflict markers Git has inserted directly into the file:

<<<<<<< HEAD
console.log("Welcome to Grade 9 Computer Science!");
=======
console.log("Hi there, Grade 9!");
>>>>>>> experiment-greeting

Everything between <<<<<<< HEAD and ======= is what the current branch (main) has; everything between ======= and >>>>>>> experiment-greeting is what the incoming branch has. Git will not proceed on its own — a human has to decide what the merged file should actually say. Suppose you decide to keep both greetings. Delete the conflict markers and edit greeting.js so it reads:

console.log("Hi there, Grade 9!");
console.log("Welcome to Grade 9 Computer Science!");

Then stage the resolved file and commit:

$ git add greeting.js
$ git commit -m "Merge experiment-greeting into main, keep both greetings"
[main d4a7f19] Merge experiment-greeting into main, keep both greetings

This new commit, d4a7f19, is unlike every other commit so far: it has two parents, b6c2e05 (the tip of main) and 9f4d8a1 (the tip of experiment-greeting), because it is the point where both lines of history reunite. The full commit graph now looks like this:

main and experiment-greeting: diverge, then merge c3f8b0e 7a1e9d2 5e9a2c7 b6c2e05 9f4d8a1 d4a7f19 branch point add app.js learning msg add greeting.js update copy new greeting merge, keep both (two parents: b6c2e05, 9f4d8a1) main experiment-greeting

Why Not Just Use a Cloud Backup Folder?

A synced cloud folder keeps one current copy of every file and, at best, a rough recent-history log you can scroll through. It has no concept of a snapshot with a message explaining why a change was made, no way to give a set of experimental changes a name and develop them in isolation the way a branch does, and no principled way to combine two people's simultaneous edits to the same line — it will either overwrite one person's work or silently create a duplicate file. Git's commit graph, with its parent pointers and SHA-1 fingerprints, is what makes operations like "show me exactly what changed between these two specific points in history" or "combine these two diverging lines of work" well-defined operations instead of manual, error-prone folder comparison.

It's also worth separating two words students often merge into one idea: Git and GitHub are not the same thing. Git is the program that runs on your own computer and manages the .git folder described in this chapter — it works perfectly with no internet connection at all. GitHub is a website that hosts copies of Git repositories online, so a team can push their local history to a shared server and pull each other's changes down. Git itself is older than GitHub: Torvalds released Git in April 2005, and GitHub — the site most students associate with the word "Git" — wasn't founded until February 2008, almost three years later, as a hosting business built on top of a tool that already existed and was already widely used.

Quick Recap

A Git repository is a hidden .git folder holding a chain of snapshots (commits), each one fingerprinted with a 40-character SHA-1 hash and pointing to its parent. A file passes through three states — working directory, staging area, repository — and only git commit makes a change permanent; git add merely marks a file ready. git diff shows unstaged changes line by line using +/- markers and a hunk header describing old and new line ranges. A branch is a lightweight, movable pointer to a commit, letting history diverge safely; git merge reunites two branches, combining non-overlapping changes automatically and asking a human to resolve any line both branches edited differently, using <<<<<<</=======/>>>>>>> markers. A merge commit is the one place a commit has two parents instead of one.

Trace It Yourself

  • You run git add report.txt and then, before committing, open the file and add one more sentence. You run git commit -m "Add report" without running git add again. Will the new sentence be in the commit? Explain using the working directory / staging area / repository model.
  • A git diff hunk header reads @@ -5,3 +5,5 @@. Without seeing the actual lines, state how many lines the old version showed starting from line 5, how many lines the new version shows starting from line 5, and whether the file grew or shrank in that hunk.
  • Two branches, main and feature, both started from the same commit. main edited line 10 of styles.css; feature edited line 40 of the same file. When you merge feature into main, will Git report a conflict? Justify your answer using what you now know about how Git decides between an automatic merge and a conflict.
  • Explain, in your own words, why a merge commit is the only kind of commit in this chapter's example that has two parent hashes instead of one, and what those two parents each represent.
  • A classmate says, "I don't need Git — I just save my project as project_v1, project_v2, project_v3 folders." Give two specific things Git's commit history can do that this folder-copying approach cannot.

Think About It

Think about this: How would you explain version control with git: never lose your code again 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.

← AI Capstone Project: Indian Language DetectorFull Stack Capstone: Building a Complete Indian Weather App →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn