Viewing the version history¶
The commit structure¶
A Git commit is made of three kinds of objects:
| Object | Role |
|---|---|
| Commit | Contains the metadata: author, log message, commit time. |
| Tree | Tracks the names and locations of files and directories in the repository. It works like a dictionary that maps names (keys) to files or subdirectories. |
| Blob | Binary Large OBject. It may contain data of any kind; for Git, it is a compressed snapshot of one file's contents. |
In short: the commit says who, when, and why, the tree says which files exist and where, and each blob holds what a file contains.
Precision from the official documentation
Besides its metadata, a commit object also points to its tree (the snapshot of the project) and to its parent commit(s), which is how Git links commits into a history. Source: Pro Git, Git Objects.
Visualizing the commit structure¶
The course follows three commits of the survey project, each containing a tree and its blobs:
| Commit | report.md |
mental_health_survey.csv |
summary_statistics.csv |
|---|---|---|---|
56daf65 (first) |
blob A: # Mental Health in Tech Survey |
blob B: 31,M,No,No,Never,... |
— |
3f5003f (second) |
same blob A (unchanged) | new blob C: 19,M,No,Yes,Sometimes,... |
new blob D: Column, n, Mean, Std ... |
b22eb75 (last) |
new blob E: TODO: cite funding sources |
new blob F: 37,F,No,No,Rarely,... |
same blob D (unchanged) |
commit 56daf65 ──▶ tree ── report.md ─────────────▶ blob A
└─ mental_health_survey.csv ▶ blob B
commit 3f5003f ──▶ tree ── report.md ─────────────▶ blob A (reused)
├─ mental_health_survey.csv ▶ blob C
└─ summary_statistics.csv ──▶ blob D
commit b22eb75 ──▶ tree ── report.md ─────────────▶ blob E
├─ mental_health_survey.csv ▶ blob F
└─ summary_statistics.csv ──▶ blob D (reused)
What the diagram shows:
- Each commit has its own tree that lists every file in the project at that moment.
- When a file changes, the new commit's tree points to a new blob with the new content.
- When a file is unchanged, the tree simply points to the existing blob. In the example,
report.mdin the second commit reuses blob A, andsummary_statistics.csvin the last commit reuses blob D. Git does not store a second copy of identical content.
This is why each commit is a full snapshot of the project, yet the repository does not grow with a complete copy of every file at each commit.
You can look at these objects yourself in any repository with git cat-file -p (optional, not required by the course):
$ git cat-file -p HEAD # the latest commit: tree, parent, author, message
$ git cat-file -p 'HEAD^{tree}' # its tree: one line per file, each pointing to a blob
The Git hash¶
Every commit, and every tree and blob, is identified by a hash, for example:
Last commit: b22eb75a82a68b9c0f1c45b9f5a9b7abe281683a
A hash is produced by a hash function from the content of the object. Hashes are what make it efficient to share data between repositories:
- if two files are the same, their hashes are the same;
- so Git only needs to compare hashes, not entire files, to know whether content has changed or is already present.
Correction: a hash is not a random number
The course describes the hash as a "pseudo-random number generator". This is imprecise. The hash looks random, but it is deterministic: the same content always produces the same hash, which is exactly why the comparison above works. By default, Git uses the SHA-1 hash function, which produces 40 hexadecimal characters; repositories can also be created with SHA-256. Sources: git init --object-format and hash function transition.
Git log¶
git log shows the commit history, from newest to oldest:
$ git log
commit ad8accfe94cb924444c488132bdef7c54b9bca68
Author: Rep Loop <[email protected]>
Date: Wed Jul 24 07:48:27 2022 +0000
Added reminder to cite funding sources.
:
Each entry contains the full commit hash, the author (name and email), the date, and the log message.
When the history is longer than the screen, Git displays it in a pager (usually less), shown by the : prompt at the bottom:
| Key | Action |
|---|---|
Space |
Show the next page, which contains older commits |
q |
Quit the log and return to the terminal |
Correction: Space shows older commits
The course says that pressing Space shows "more recent commits". Because git log lists commits from newest to oldest, scrolling down with Space shows older commits. By default, commits are shown in reverse chronological order (see git log, commit ordering).
Version history tips and tricks¶
As a project grows, it gets more commits, and the output of git log grows with it. These options narrow the output.
Restricting the number of commits¶
Add - followed by a number to show only the most recent commits:
git log -3 # only the 3 most recent commits
-3 is a short form of -n 3 or --max-count=3.
Restricting to one file¶
Add a file path to see only the commits that changed this file:
git log report.md
The path is relative to your current directory.
Combining techniques¶
Options and paths can be combined. Here, from the data directory, the two most recent commits that changed the survey file:
$ cd data
$ git log -2 mental_health_survey.csv
commit f35b9487c063d3facc853c1789b0b77087a859fa
Author: Rep Loop <[email protected]>
Date: Fri Jul 26 15:14:32 2024 +0000
Add two new participants' data.
commit 7f71eadea60bf38f53c8696d23f8314d85342aaf
Author: Rep Loop <[email protected]>
Date: Fri Jul 19 09:58:21 2024 +0000
Adding fresh data for the survey.
Customizing the date range¶
Restrict git log to a period with --since and --until:
git log --since='Month Day Year'
# Commits since 2 April 2024
git log --since='Apr 2 2024'
# Commits between 2 and 11 April 2024
git log --since='Apr 2 2024' --until='Apr 11 2024'
--after and --before are synonyms of --since and --until (git log documentation).
Acceptable filter formats¶
| Natural language | Date formats |
|---|---|
"2 weeks ago" |
"2024-07-15": ISO 8601 YYYY-MM-DD, recommended |
"3 months ago" |
"07-15-2024" |
"yesterday" |
"15 Jul 2024" or "15 July 2024" |
The course recommends the ISO 8601 format YYYY-MM-DD because it is unambiguous. With other numeric formats, a date such as 12-06-2024 could mean 6 December or 12 June.
Notes on date parsing
- The course lists
"15 Jul, 2024"(with a comma) as invalid. In a test with Git 2.55, Git accepted it and read it as 15 July 2024. Git's date parser is lenient and its accepted formats are not formally listed in the documentation. Do not rely on unusual formats: use ISO dates. - In the same test,
12-06-2024was read as 6 December 2024 (month first). This confirms the ambiguity: always prefer2024-06-12. - A date without a time, such as
--since='2024-04-02', uses the current time of day on that date. To start at midnight, add the time:--since='2024-04-02 00:00'.
Finding a particular commit¶
To inspect one commit, find its hash with git log, then pass it to git show. You do not need the full 40 characters; the course suggests the first 8 to 10:
git show c27fa856
The output has two parts:
commit c27fa85646794b92c5de310395493ebcc3e15cc0 (HEAD -> main)
Author: Rep Loop <[email protected]>
Date: Thu Aug 11 07:57:09 2022 +0000
Adding 50th participant's data
diff --git a/data/mental_health_survey.csv b/data/mental_health_survey.csv
index e034015..17ff40f 100644
--- a/data/mental_health_survey.csv
+++ b/data/mental_health_survey.csv
@@ -48,3 +48,4 @@ age,gender,family_history,treatment,work_interfere,benefits,mental_health_interv
29,F,No,Yes,Rarely,Don't know,No,Don't know
23,M,Yes,No,Sometimes,No,No,No
25,M,Yes,Yes,Sometimes,Yes,No,Don't know
+F,56,Yes,Rarely,No,Don't know,Often,No
- Log: the same information as in
git log(hash, author, date, message).(HEAD -> main)means this is the latest commit of themainbranch. - Diff: the changes introduced by this commit. The line starting with
+was added. Here it reveals a data entry error: the gender and age values are swapped (F,56instead of56,F). This is howgit showhelps find the commit that introduced a problem. How to read a diff in detail is explained in Comparing versions.
How short can a hash be?
Git accepts any leading part of a hash, as long as it is unique in the repository (gitrevisions). Git itself displays short hashes of at least 7 characters by default, as in git log --oneline. Using 8 to 10 characters, as the course suggests, keeps the prefix unique even in large repositories.
git log --oneline (not part of the course, but convenient) prints one line per commit with its short hash, which makes it easy to copy a hash for git show.
Challenge: investigate a project's history¶
Objective: use git log and git show options to answer questions about an existing history.
Prerequisites and initial state: Git installed. The setup script builds a practice repository in ~/git-practice/history with five commits at fixed dates in 2024.
Setup (copy the whole block):
mkdir -p ~/git-practice/history && cd ~/git-practice/history
git init -q -b main
git config user.name "Practice User"
git config user.email "[email protected]"
c() { GIT_AUTHOR_DATE="$1" GIT_COMMITTER_DATE="$1" git commit -q -m "$2"; }
mkdir data
echo "# Mental Health in Tech Survey" > report.md
echo "age,gender,treatment" > data/mental_health_survey.csv
git add . && c "2024-03-28T10:00:00" "Create report and survey file"
echo "31,M,No" >> data/mental_health_survey.csv
git add . && c "2024-04-03T10:00:00" "Add first participant"
echo "TODO: cite funding sources." >> report.md
git add . && c "2024-04-09T10:00:00" "Add reminder to cite funding sources"
echo "19,M,Yes" >> data/mental_health_survey.csv
git add . && c "2024-04-15T10:00:00" "Add second participant"
echo "F,56,Yes" >> data/mental_health_survey.csv
git add . && c "2024-07-26T10:00:00" "Add third participant"
unset -f c
Tasks:
- Display the full history. How many commits are there, and which one is the most recent?
- Show only the 2 most recent commits.
- Show only the commits that changed
report.md. - From inside the
datadirectory, show the 2 most recent commits that changedmental_health_survey.csv. - Back in the repository root, list the commits made between 1 and 10 April 2024 (inclusive).
- Display the content of the commit
Add third participantusing its hash, and find the data entry error it introduced.
Expected result and verification:
- 5 commits; the newest is
Add third participant. Add third participantandAdd second participant.Add reminder to cite funding sourcesandCreate report and survey file.Add third participantandAdd second participant.Add reminder to cite funding sourcesandAdd first participant.- The diff shows
+F,56,Yes: gender and age are in the wrong order.
Solution
# 1. Full history (press q to quit if a pager opens)
git log
# 2. Two most recent commits
git log -2
# 3. Commits that changed report.md
git log report.md
# 4. Combine a limit and a path, from the data directory
cd data
git log -2 mental_health_survey.csv
cd ..
# 5. Date range, with explicit times to include both full days
git log --since='2024-04-01 00:00' --until='2024-04-10 23:59'
# 6. Find the hash, then show the commit
git log -1 --oneline # e.g. 1a2b3c4 Add third participant
git show 1a2b3c4 # replace with your own hash
-2and a file path can be combined in the same command. The path in step 4 is relative todata/.- In step 5,
--since='2024-04-01'without a time would use the current time of day. That still works here, because the commits are made at 10:00 on days that are not on the boundary. Adding00:00and23:59makes the range exact. git showprints the log entry, then the diff. The added line+F,56,Yesbreaks theage,gender,treatmentcolumn order.- Your hashes differ from the example, because they depend on the author and the dates.
Clean up when you are done: rm -rf ~/git-practice/history.