Git & GitHub Module 1 – Getting started with Git
What version control is and why Git won
What a version control system records, why Git became the standard, how it differs from GitHub and Subversion, and why every clone holds the full history.
What you will learn
- Explain what a version control system records and why teams rely on one
- Distinguish Git, a distributed version control system, from GitHub, a hosting and collaboration service
- Contrast centralised systems such as Subversion with Git's full-history local clones
- Describe Git's snapshot model and why commits are cheap to create and compare
What you’ll learn
What you will learn
- Explain what a version control system records and why teams rely on one
- Distinguish Git, a distributed version control system, from GitHub, a hosting and collaboration service
- Contrast centralised systems such as Subversion with Git's full-history local clones
- Describe Git's snapshot model and why commits are cheap to create and compare
On this page
Version control is software that keeps a record of every saved state of a project: which files changed, who changed them, when, and the reason they gave. With that record you can compare any two states, find the change that broke something and go back to an earlier version, and several people can work on the same files without overwriting each other’s work. Git is the version control system that most developers use, and it won on its design. Every copy of a project holds the complete history, so reading history, comparing versions, branching and recording changes are fast and need no server. It was built for one of the largest projects there is, the Linux kernel. It is open source. And hosting services such as GitHub were built around it. This lesson shows each of those points with real Git sessions you can run yourself.
What version control records
Most people start with copies. A lab report goes through three versions and ends up as three files:
from gitsession import session
session(r"""
# The old way: a new copy of the file for every version
mkdir report-copies
cd report-copies
printf 'Lab report\nResult: 9.8 m/s2\n' > report_final.txt
printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.04 m/s2\n' > report_final_v2.txt
printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.02 m/s2\nSigned: Asha\n' > report_final_REALLY.txt
ls
cd ..
# With Git: one file and three commits (the next lesson explains these three settings)
git config set --global user.name "Asha Rao"
git config set --global user.email "asha@example.com"
git config set --global init.defaultBranch main
git init -q report
cd report
printf 'Lab report\nResult: 9.8 m/s2\n' > report.txt
git add report.txt
git commit -q -m "Record the measured value"
printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.04 m/s2\n' > report.txt
git commit -q -am "Round to two decimals and add the uncertainty"
printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.02 m/s2\nSigned: Asha\n' > report.txt
git commit -q -am "Halve the uncertainty after a second run, and sign"
git log --format='%h %an: %s'
git diff HEAD~2 HEAD
""") Output
# The old way: a new copy of the file for every version $ mkdir report-copies $ cd report-copies $ printf 'Lab report\nResult: 9.8 m/s2\n' > report_final.txt $ printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.04 m/s2\n' > report_final_v2.txt $ printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.02 m/s2\nSigned: Asha\n' > report_final_REALLY.txt $ ls report_final.txt report_final_REALLY.txt report_final_v2.txt $ cd .. # With Git: one file and three commits (the next lesson explains these three settings) $ git config set --global user.name "Asha Rao" $ git config set --global user.email "asha@example.com" $ git config set --global init.defaultBranch main $ git init -q report $ cd report $ printf 'Lab report\nResult: 9.8 m/s2\n' > report.txt $ git add report.txt $ git commit -q -m "Record the measured value" $ printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.04 m/s2\n' > report.txt $ git commit -q -am "Round to two decimals and add the uncertainty" $ printf 'Lab report\nResult: 9.81 m/s2\nUncertainty: 0.02 m/s2\nSigned: Asha\n' > report.txt $ git commit -q -am "Halve the uncertainty after a second run, and sign" $ git log --format='%h %an: %s' cfae588 Asha Rao: Halve the uncertainty after a second run, and sign 0b13e80 Asha Rao: Round to two decimals and add the uncertainty 3d5a557 Asha Rao: Record the measured value $ git diff HEAD~2 HEAD diff --git a/report.txt b/report.txt index 901377e..773a442 100644 --- a/report.txt +++ b/report.txt @@ -1,2 +1,4 @@ Lab report -Result: 9.8 m/s2 +Result: 9.81 m/s2 +Uncertainty: 0.02 m/s2 +Signed: Asha
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 history.py
The copies hold the same text as the commits, but that is all they hold. Nothing says who wrote each version, why it
changed, or which one is really the latest; the names are guesses, and “REALLY” sorts before “v2”. The repository
answers those questions. git log lists one line per commit: a short form of its ID (cfae588), the author and the
message that says why the change was made. Each commit also records the time it was made. git diff HEAD~2 HEAD
compares the first version with the latest, line by line: a minus sign marks a line that went, a plus sign a line that
came in. HEAD is the commit you are on, and HEAD~2 the one two steps before it.
A commit is more than a save. Saving writes one file to disk and keeps no trace of the version before. A commit records the whole project as it was, with a name, a time, a message and a link to the commit before it, its parent, and it stays in the history after you change the files again.
Snapshots, not lists of changes
A commit does not store a list of edits. It points to a tree, a snapshot of every file in the project at that
moment, and each file’s content is stored as a blob. This session makes two commits and then prints the tree of
each, using git cat-file -p, which shows any stored object:
from gitsession import session
session(r"""
git config set --global user.name "Asha Rao"
git config set --global user.email "asha@example.com"
git config set --global init.defaultBranch main
git init -q report
cd report
printf 'Lab report\nResult: 9.8 m/s2\n' > report.txt
printf 'trial,seconds\n1,0.64\n2,0.63\n' > readings.csv
git add report.txt readings.csv
git commit -q -m "Record the measured value"
printf 'Lab report\nResult: 9.81 m/s2\n' > report.txt
git commit -q -am "Round to two decimals"
# Each commit's snapshot is a tree: one line per file, with the ID of its content
git cat-file -p HEAD~1^{tree}
git cat-file -p HEAD^{tree}
# The commit itself: its tree, its parent, who, when (seconds since 1970) and why
git cat-file -p HEAD
""") Output
$ git config set --global user.name "Asha Rao"
$ git config set --global user.email "asha@example.com"
$ git config set --global init.defaultBranch main
$ git init -q report
$ cd report
$ printf 'Lab report\nResult: 9.8 m/s2\n' > report.txt
$ printf 'trial,seconds\n1,0.64\n2,0.63\n' > readings.csv
$ git add report.txt readings.csv
$ git commit -q -m "Record the measured value"
$ printf 'Lab report\nResult: 9.81 m/s2\n' > report.txt
$ git commit -q -am "Round to two decimals"
# Each commit's snapshot is a tree: one line per file, with the ID of its content
$ git cat-file -p HEAD~1^{tree}
100644 blob f8044c612e2c4e2cd70b2fd89e1ebbf8fdeb677b readings.csv
100644 blob 901377ec5fb27535191b6525d9fab5986e439abf report.txt
$ git cat-file -p HEAD^{tree}
100644 blob f8044c612e2c4e2cd70b2fd89e1ebbf8fdeb677b readings.csv
100644 blob b1ae2ebbfde0ebe742baceb20a2beac146e18f0b report.txt
# The commit itself: its tree, its parent, who, when (seconds since 1970) and why
$ git cat-file -p HEAD
tree a053a0f889e2b7cdb7663f3ff5639d75dd21b982
parent cfa7b135cdee2975ab66cc5b6a036e29b10251f8
author Asha Rao <asha@example.com> 1767240000 +0530
committer Asha Rao <asha@example.com> 1767240000 +0530
Round to two decimals
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 snapshots.py
Each line of a tree gives a file’s mode (100644 is an ordinary file), the object type, the ID of the content and the
file name. report.txt changed, so the second tree names a new blob, b1ae2eb…. readings.csv did not change, and
both trees name the same blob, f8044c6…, which Git stored only once. The commit at the end records exactly what this
lesson started with: the tree, the parent, the author and committer, the time as seconds since 1970 with the time zone
(+0530 is India’s), and the message.
Two snapshots that share an unchanged file
Text description of the diagram
The diagram shows the two commits of the snapshots session and what each one points to.
- Commit 2, "Round to two decimals", points to its parent, commit 1 (cfa7b13), "Record the measured value".
- Each commit points to one tree, the snapshot of the whole project at that commit: tree a053a0f for commit 2, and a tree of its own for commit 1.
- Commit 1's tree lists report.txt as blob 901377e and readings.csv as blob f8044c6.
- Commit 2's tree lists report.txt as a new blob, b1ae2eb, because the file changed, and readings.csv as the same blob f8044c6, because it did not.
So the second snapshot is complete, yet only the changed file was stored again; the unchanged file is stored once and named by both trees.
Git’s manual on its data model is explicit that commits carry no diffs. git show and git diff work one out from two
snapshots each time you run them. Snapshots are still cheap, because an unchanged file costs nothing new: the manual’s
own example is a commit that changes 2 files in a repository of 1,000, which adds 2 blobs and reuses the IDs of the
other 998.
Why the same content always gets the same ID
An object’s ID is a hash of its type and its content. For a file, Git hashes a short header, the word blob and the
size in bytes, followed by the bytes themselves, with SHA-1. This Python program computes the IDs of the three file
contents above without using Git at all:
import hashlib
def blob_id(text):
"""The ID Git gives a file with this content: the SHA-1 of a short header and the file's bytes."""
data = text.encode("utf-8")
header = f"blob {len(data)}\0".encode("ascii")
return hashlib.sha1(header + data).hexdigest()
print("report.txt, first version: ", blob_id("Lab report\nResult: 9.8 m/s2\n"))
print("report.txt, second version:", blob_id("Lab report\nResult: 9.81 m/s2\n"))
print("readings.csv, both times: ", blob_id("trial,seconds\n1,0.64\n2,0.63\n")) Output
report.txt, first version: 901377ec5fb27535191b6525d9fab5986e439abf report.txt, second version: b1ae2ebbfde0ebe742baceb20a2beac146e18f0b readings.csv, both times: f8044c612e2c4e2cd70b2fd89e1ebbf8fdeb677b
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 blob_ids.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
They are the IDs Git printed in the trees. The same content gets the same ID on any computer, which is how two snapshots
share an unchanged file, and how Git can tell from the IDs alone that readings.csv did not change between two commits,
without reading it. Commits have IDs made the same way, from the tree, the parents, the author, the committer, the times
and the message. That is why the first commit of the first session and the first commit of the clone session below both
have the ID 3d5a557: same file, same name, same time, same message. Your own commits will get other IDs, because your
name and your time differ.
Centralised and distributed version control
For many years the standard was a centralised system such as CVS or Subversion. One server holds the repository, the only complete history; each person’s working copy holds one version of the files, and older versions are fetched from the server when someone asks for them. Recording a change means sending it to the server.
Git is distributed. A clone is a complete repository: every commit, every branch and the whole history, in the
.git folder next to your files. Reading history, comparing any two versions, making a branch and committing all happen
on your own computer. A shared repository is only the place a team agrees to exchange commits.
Where the history lives
Text description of the diagram
The diagram has two panels, one above the other.
- Centralised, such as Subversion: one server holds the only full history. Two working copies are connected to it; each holds one version of the files. A working copy sends commits to the server and gets updates from it, so both need the server.
- Distributed, Git: two clones and a shared repository each hold the full history. The clones fetch from and push to the shared repository, but they can do everything else on their own, and the shared repository is optional: it is only the place the team agrees to exchange commits.
Here Ravi clones Asha’s repository, works on his own copy, and then sends his commit back. The “server” is just another folder on the same computer: to Git, it is simply another repository.
from gitsession import session
session(r"""
git config set --global user.name "Asha Rao"
git config set --global user.email "asha@example.com"
git config set --global init.defaultBranch main
git init -q report
cd report
printf 'Lab report\nResult: 9.8 m/s2\n' > report.txt
git add report.txt
git commit -q -m "Record the measured value"
cd ..
# Ravi clones Asha's repository: the clone holds every commit
git clone -q report ravi-report
cd ravi-report
git config set user.name "Ravi Kumar"
git config set user.email "ravi@example.com"
git log --format='%h %an: %s'
# Ravi works on his own copy: a branch, a commit, a comparison
git switch -q -c conclusion
printf 'Conclusion: matches the expected 9.81 within the uncertainty\n' >> report.txt
git commit -q -am "Add the conclusion"
git diff --stat main conclusion
# Only commands such as push and fetch talk to another repository: here, a folder
git push -q origin conclusion
cd ../report
git log --format='%h %an: %s' conclusion
""") Output
$ git config set --global user.name "Asha Rao" $ git config set --global user.email "asha@example.com" $ git config set --global init.defaultBranch main $ git init -q report $ cd report $ printf 'Lab report\nResult: 9.8 m/s2\n' > report.txt $ git add report.txt $ git commit -q -m "Record the measured value" $ cd .. # Ravi clones Asha's repository: the clone holds every commit $ git clone -q report ravi-report $ cd ravi-report $ git config set user.name "Ravi Kumar" $ git config set user.email "ravi@example.com" $ git log --format='%h %an: %s' 3d5a557 Asha Rao: Record the measured value # Ravi works on his own copy: a branch, a commit, a comparison $ git switch -q -c conclusion $ printf 'Conclusion: matches the expected 9.81 within the uncertainty\n' >> report.txt $ git commit -q -am "Add the conclusion" $ git diff --stat main conclusion report.txt | 1 + 1 file changed, 1 insertion(+) # Only commands such as push and fetch talk to another repository: here, a folder $ git push -q origin conclusion $ cd ../report $ git log --format='%h %an: %s' conclusion 4186172 Ravi Kumar: Add the conclusion 3d5a557 Asha Rao: Record the measured value
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 clone_and_share.py
The clone arrived with Asha’s commit, and everything Ravi did next, the branch, the commit and the comparison, touched
only his own repository. Only git push reached another repository. That is why Git keeps working on a train with no
network. It is also why a clone can replace a lost server: the Git book on git-scm.com calls each clone a full backup.
| Criterion | Subversion | Git |
|---|---|---|
| Full history | On the server | In every clone |
| Older versions | From the server | In your clone |
| Commits | Need the server | Work offline |
| Server lost | History is lost | Any clone has it |
| When to choose | Files with locks | Most projects |
Files with locks are the one case in the table where Subversion fits better. No tool can merge two edited versions of an image, so Subversion lets one person lock such a file while editing it, and nobody else’s work on it is wasted.
Git and GitHub are not the same thing
Git is the program. It runs on your computer, keeps the history in the .git folder, works without an internet
connection, and is open-source software under the GNU General Public License, version 2. GitHub is a service that hosts
Git repositories online and adds tools for working together around them: pull requests to propose and discuss
changes, issues to plan work, and Actions to run tests and other jobs automatically. GitHub’s own documentation
describes it as built on Git.
| Criterion | Git | GitHub |
|---|---|---|
| What it is | Version control | A hosting service |
| Where it runs | Your computer | GitHub’s servers |
| Needs an account | No | Yes |
| Works offline | Yes | No |
| Adds | Commits, branches | Pull requests, issues |
| When to choose | Always | Sharing online |
GitHub is one host among several: GitLab, Bitbucket and Codeberg host Git repositories too, and a company can run its own Git server. The Git half of this track works with all of them; the lessons about pull requests, reviews and Actions use GitHub.
Not affiliated
Based on public information about GitHub. MySmartCoPilot is not affiliated with GitHub, and GitHub’s real features may differ from what this lesson describes.
Why Git won
Git was started in 2005 by the Linux kernel community, in particular Linus Torvalds, after the kernel lost its no-cost licence for BitKeeper, the system it had used since 2002. The Git book on git-scm.com lists what they wanted from the new tool: speed, a simple design, strong support for thousands of parallel branches, a fully distributed model, and the ability to handle a project the size of the kernel. Snapshots with content IDs deliver much of that: a commit adds only what changed, and a branch is cheap because it is just a name for a commit.
The rest followed from its open-source licence and from the services built around it. In Stack Overflow’s 2018 developer survey, 87.2 % of the 74,298 people who answered the question used Git, 16.1 % Subversion and 3.6 % Mercurial, the other distributed system of the time. Two years later, Bitbucket stopped hosting Mercurial repositories, after the share of its new users who chose Mercurial had fallen below 1 %.
What changes in Git 3.0
The Git project has announced Git 3.0, a release with new defaults for new repositories: SHA-256 instead of SHA-1 for
object IDs, a new format for storing references called reftable, and main as the name of the first branch. It has set
no release date, and existing repositories keep working: SHA-1 repositories are not being deprecated. The newest release
is Git 2.56.0, and the examples of this track run on Git 2.54.0; a lesson says so when something depends on the version.
Three misconceptions
- “Git stores diffs.” It stores snapshots and computes diffs when you ask. Inside the compressed pack files it makes for storage and transfer, Git may store an object as a difference from another one, but that is compression, invisible to every command you use.
- “GitHub is Git.” GitHub is a service that hosts Git repositories. You can use Git all your life without GitHub, and the history in your clone is the same either way.
- “Committing is saving.” Saving writes a file. A commit records a snapshot of the whole project, with a message, an author, a time and its parent, and it stays in the history.
Run the sessions yourself
Each session on this page is a short Python program that runs real Git commands one at a time and prints each one after
a $, followed by what Git printed. You can follow along by typing the commands into a terminal in a new, empty folder,
but leave out the git config lines: with --global they would replace your own settings, which the next lesson sets
up properly. Your commit IDs will differ, because they depend on your name and the time.
To replay a session exactly on macOS or Linux, install Git (the next lesson shows how) and Python 3, put gitsession.py
in the same folder as the session file and run, for example, python3 history.py. The helper starts every session in a
new, empty folder called learner, which it also uses as the home folder, so the git config lines change only that
folder and your own settings stay as they are; it also fixes the commit time, so your IDs match the ones on this page.
"""Plays a terminal session with real Git: each command is printed after "$ ", then everything it printed.
The session runs in a new, empty folder called learner/, which is also its home folder, so your own Git settings
change nothing, and GIT_CONFIG_NOSYSTEM=1 makes Git skip the computer's system-wide settings file too. Git never
looks for a repository above that folder, so a session cannot touch a repository you run it from. In the output,
"~" stands for that folder. Commit times are fixed, because a commit's ID covers the time it was made: with the
same files, message, name and time, every run makes the same commit IDs.
Each line of a script is one /bin/sh command (macOS and Linux), except "cd", which the session does itself. A line
that starts with "# " is printed as a comment. A command written as "! command" is meant to fail: its exit status
is printed and the session goes on. Any other command that fails stops the session with exit status 1.
"""
import os
import shutil
import subprocess
import sys
from pathlib import Path
HOME = Path("learner").resolve()
# One fixed moment in Git's own date format: seconds since 1970, then the time zone (India, UTC+05:30).
FIXED_TIME = "@1767240000 +0530"
def _environment():
# Git's own folder comes first, so every command runs that Git directly (on macOS, /usr/bin/git is a small
# launcher that looks Git up again on every call).
git_folder = subprocess.run(["git", "--exec-path"], capture_output=True, text=True, check=True).stdout.strip()
env = {
"PATH": os.pathsep.join([git_folder, os.environ.get("PATH", "/usr/bin:/bin")]),
"HOME": str(HOME),
"XDG_CONFIG_HOME": str(HOME / ".config"),
"GIT_CONFIG_NOSYSTEM": "1",
"GIT_CEILING_DIRECTORIES": str(HOME.parent),
"GIT_AUTHOR_DATE": FIXED_TIME,
"GIT_COMMITTER_DATE": FIXED_TIME,
"LANG": "C",
"LC_ALL": "C",
"TERM": "dumb",
}
if "TMPDIR" in os.environ:
env["TMPDIR"] = os.environ["TMPDIR"]
return env
def _with_tilde(text):
"""Writes the home folder as "~", however the operating system spells its path."""
for spelling in sorted({str(HOME), os.path.realpath(HOME)}, key=len, reverse=True):
text = text.replace(spelling, "~")
return text
def session(script):
"""Runs the script's commands one by one, starting in an empty home folder."""
shutil.rmtree(HOME, ignore_errors=True)
HOME.mkdir()
env = _environment()
folder = HOME
for line in script.strip().splitlines():
command = line.strip()
if not command:
continue
if command.startswith("# "):
print(command)
continue
meant_to_fail = command.startswith("! ")
if meant_to_fail:
command = command[2:]
print("$", command)
if command.startswith("cd "):
folder = (folder / command[3:].strip()).resolve()
continue
done = subprocess.run(command, shell=True, cwd=folder, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
print(_with_tilde(done.stdout), end="")
if done.returncode != 0:
print(f"[exit status {done.returncode}]")
if not meant_to_fail:
sys.exit(1)
elif meant_to_fail:
sys.exit(f"{command} was meant to fail, but it worked") Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
What is the difference between Git and GitHub?
Git is a version control program. It runs on your computer, records a project’s history as commits in the .git folder,
and needs no network or account except to exchange commits with another repository. It is open-source software under the
GNU GPL, version 2. GitHub is a hosting service built on Git: it keeps Git repositories on its servers and adds
collaboration features such as pull requests, issues, code review and Actions. A repository on GitHub is an ordinary Git
repository, so you can clone it, work offline and push back, and the same history can also live on GitLab, Bitbucket or
a team’s own server. In short, Git is the tool and GitHub is one place to share what it records: a team can use Git
without GitHub, but not GitHub without Git.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A teammate says Git saves only the changes between versions, so checking out an old commit means replaying every change since the first one. Is that right? Explain what Git actually stores, and how you would show it on a real repository.
-
Fresher to mid
Your team's Git hosting service is down for the whole working day before a release. What can each developer still do, what is blocked, and how would the answer differ if the team used Subversion? What would you set up so that the next outage costs less?
Key takeaways
- Version control records every saved state of a project with who, when and why, so you can compare, find and go back to any version.
- A commit points to a tree, a snapshot of every file; unchanged files keep their blob ID and are stored once, and diffs are computed when you ask.
- An object’s ID is a hash of its content, so the same content and the same details give the same ID on any computer.
- Subversion keeps the only full history on a server; every Git clone holds all of it, so only push and fetch need another repository.
- Git is the program; GitHub is one of several services that host Git repositories and add pull requests, issues and Actions.
- Git 3.0 will change the defaults for new repositories (SHA-256, reftable,
main), with no release date set.
References
- gitdatamodel: Git's core data model (The Git project)
- Pro Git, 1.1: About Version Control (Scott Chacon and Ben Straub (Apress), on git-scm.com)
- Pro Git, 1.2: A Short History of Git (Scott Chacon and Ben Straub (Apress), on git-scm.com)
- About Git (free and open source, the GPLv2 licence) (The Git project)
- Version Control with Subversion: Version Control Basics (Collins-Sussman, Fitzpatrick and Pilato (the Subversion book))
- Version Control with Subversion: Locking (Collins-Sussman, Fitzpatrick and Pilato (the Subversion book))
- About Git (GitHub Docs) (GitHub)
- What is GitHub? (GitHub Docs) (GitHub)
- Stack Overflow Developer Survey 2018 (version control) (Stack Overflow)
- Sunsetting Mercurial support in Bitbucket (Atlassian)
- git-pack-objects: packed archives and deltas (The Git project)
- Git for Windows (Git BASH) (The Git for Windows project)
- Upcoming breaking changes (Git 3.0) (The Git project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress