Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Git & GitHub  Module 3 – Exploring history

Read history with git log, show and diff

Filter and format git log, name commits with HEAD~2, HEAD^2 and @{u}, compare A..B with A...B, and inspect any commit or old file with git show.

  • Beginner
  • 30 minutes
  • Examples run with git 2.54.0, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 30 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Filter git log by author, date, path and message, and format it as one line per commit, a graph or your own fields
  • Name commits with revision syntax such as HEAD~2, HEAD^2, @{upstream} and tag names
  • Compare ranges with A..B and A...B in git log and git diff
  • Inspect a commit with git show and summarise contributors with git shortlog

Before you start

What you’ll learn · Before you start

What you will learn

  • Filter git log by author, date, path and message, and format it as one line per commit, a graph or your own fields
  • Name commits with revision syntax such as HEAD~2, HEAD^2, @{upstream} and tag names
  • Compare ranges with A..B and A...B in git log and git diff
  • Inspect a commit with git show and summarise contributors with git shortlog

Before you start

On this page

Git keeps every commit you make, and three commands read them back. git log lists commits, newest first, and narrows the list by author, date, message or file. git show prints one commit: who made it, when, why and the change itself; given a path, it prints a file as it was in that commit. git diff compares two snapshots, such as two branches, or a tag and the latest commit. All three accept the same names for commits: HEAD~2 for two commits back, v1.0 for a tag, @{u} for the shared branch yours follows. git log also accepts ranges, so main..reminders lists what reminders has and main does not.

You read history to answer practical questions: what changed since the last release, who knows this file, which commits a merge will bring in, what you have not pushed yet. This lesson runs every command on one small repository and shows what Git printed, so you can predict each answer before you type the command.

One repository for every example

The examples read the history of a class timetable that four students keep in Git: Asha, Ravi, Meera and Imran. The repository holds timetable.csv, exams.csv and a README on the main branch. Imran added lab sessions on a branch called labs, and Meera a holiday list on a branch called holidays; both branches were merged into main. The first finished timetable carries the tag v1.0. After the holidays merge, main was pushed to a shared copy named origin. Since then Imran has started a branch called reminders, not merged yet, and Ravi and Meera have made two more commits on main that nobody has pushed. In all, 19 commits.

timetable.mjs, the repository every example builds JavaScript · timetable.mjs
// Builds the repository every example of this lesson reads: a college class timetable kept in Git by four people,
// with two merged branches, one branch still open, a tag and a remote. Names, emails and dates are fixed (and the
// timezone is India's, +05:30), so the commit IDs come out the same on every computer: run any example with
// Node.js 24 and you get the IDs shown on the page. Nothing here uses the network; the remote is a folder.
import { spawnSync } from 'node:child_process';
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';

export const REPO = 'timetable';

const PEOPLE = {
  Asha: 'Asha Rao <asha@example.com>',
  Ravi: 'Ravi Kumar <ravi@example.com>',
  Meera: 'Meera Iyer <meera@example.com>',
  Imran: 'Imran Sheikh <imran@example.com>',
};

// Git reads no configuration of this computer, so the output does not depend on anyone's settings.
const BASE_ENV = { ...process.env, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null', GIT_TERMINAL_PROMPT: '0' };

// The git program in Git's own folder (`git --exec-path`): it skips the launcher that some systems put in front of
// git (macOS's /usr/bin/git), which makes the many commands below much faster. Any other system runs plain `git`.
const GIT = (() => {
  const dir = spawnSync('git', ['--exec-path'], { encoding: 'utf8' }).stdout?.trim();
  return dir && existsSync(join(dir, 'git')) ? join(dir, 'git') : 'git';
})();

/** The environment of a command run by `who` at `when` (India time). */
function as(who, when) {
  const [, name, email] = /^(.*) <(.*)>$/.exec(PEOPLE[who]);
  const date = `${when}:00+05:30`;
  return { ...BASE_ENV, GIT_AUTHOR_NAME: name, GIT_AUTHOR_EMAIL: email, GIT_AUTHOR_DATE: date, GIT_COMMITTER_NAME: name, GIT_COMMITTER_EMAIL: email, GIT_COMMITTER_DATE: date };
}

function run(args, env = BASE_ENV, cwd = REPO) {
  const r = spawnSync(GIT, args, { cwd, env, encoding: 'utf8' });
  if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed:\n${r.stderr}`);
  return r.stdout;
}

const files = {};
/** Writes the files of a step and commits them as `who` at `when`. */
function commit(who, when, message, changes) {
  for (const [path, text] of Object.entries(changes)) {
    files[path] = text;
    writeFileSync(join(REPO, path), text);
  }
  run(['add', '--all']);
  run(['commit', '--quiet', '--message', message], as(who, when));
}

const timetable = (rows) => ['day,period,course,room', ...rows].join('\n') + '\n';
const week1 = ['Mon,1,Data Structures,201', 'Mon,2,Discrete Maths,105', 'Tue,1,Operating Systems,203', 'Tue,2,Physics,Lab A'];
const week2 = ['Wed,1,Data Structures,201', 'Wed,2,Hindi,105', 'Thu,1,Operating Systems,203', 'Fri,1,Discrete Maths,201', 'Fri,2,Physics,201'];

/** Creates ./timetable (and ./origin.git, its remote) from nothing. */
export function buildTimetable() {
  rmSync(REPO, { recursive: true, force: true });
  rmSync('origin.git', { recursive: true, force: true });
  mkdirSync(REPO);
  run(['init', '--quiet', '--initial-branch=main']);

  commit('Asha', '2026-01-05T09:30', 'Start the timetable with a README', {
    'README.md': '# Timetable\n\nClasses and exams of the second-year computer science batch.\n',
  });
  commit('Asha', '2026-01-06T10:15', 'Add Monday and Tuesday classes', { 'timetable.csv': timetable(week1) });
  commit('Ravi', '2026-01-08T16:40', 'Add Wednesday to Friday classes', { 'timetable.csv': timetable([...week1, ...week2]) });
  commit('Meera', '2026-01-12T11:05', 'Add the mid-semester exam schedule', {
    'exams.csv': 'date,course,hall\n2026-03-16,Data Structures,Hall A\n2026-03-18,Operating Systems,Hall A\n2026-03-20,Physics,Hall C\n',
  });
  const moved = [...week1, ...week2].map((r) => r.replace('Operating Systems,203', 'Operating Systems,204'));
  commit('Asha', '2026-01-13T09:00', 'Move Operating Systems to room 204', { 'timetable.csv': timetable(moved) });
  run(['tag', '--annotate', 'v1.0', '--message', 'First timetable of the semester'], as('Asha', '2026-01-13T09:05'));

  // A branch for the lab sessions, merged a week later.
  run(['switch', '--quiet', '--create', 'labs']);
  commit('Imran', '2026-01-19T14:20', 'Add lab sessions for both batches', { 'labs.csv': 'day,batch,lab\nTue,A,Lab 1\nThu,B,Lab 1\n' });
  commit('Imran', '2026-01-21T15:10', 'Book Lab 2 for the Thursday batch', { 'labs.csv': 'day,batch,lab\nTue,A,Lab 1\nThu,B,Lab 2\n' });
  run(['switch', '--quiet', 'main']);
  const friday = moved.map((r) => r.replace('Fri,1,Discrete Maths,201', 'Fri,1,Discrete Maths,105'));
  commit('Ravi', '2026-01-20T10:30', 'Fix the Friday room numbers', { 'timetable.csv': timetable(friday) });
  run(['merge', '--quiet', '--no-ff', '--message', "Merge branch 'labs'", 'labs'], as('Asha', '2026-01-22T12:00'));

  commit('Meera', '2026-02-02T09:45', 'Shift the Physics exam to Hall B', {
    'exams.csv': files['exams.csv'].replace('Physics,Hall C', 'Physics,Hall B'),
  });
  commit('Ravi', '2026-02-09T18:20', 'List the exam rules in the README', {
    'README.md': `${files['README.md']}\nExam rules: bring your ID card; phones stay outside the hall.\n`,
  });

  // A second branch, for the holiday list.
  run(['switch', '--quiet', '--create', 'holidays']);
  commit('Meera', '2026-02-16T11:30', 'Add the holiday list', { 'holidays.csv': 'date,holiday\n2026-01-26,Republic Day\n' });
  commit('Meera', '2026-02-18T10:10', 'Add Ambedkar Jayanti to the holiday list', { 'holidays.csv': `${files['holidays.csv']}2026-04-14,Ambedkar Jayanti\n` });
  run(['switch', '--quiet', 'main']);
  commit('Asha', '2026-02-17T17:00', 'Add Saturday tutorial slots', { 'timetable.csv': timetable([...friday, 'Sat,1,Tutorial,105']) });
  run(['merge', '--quiet', '--no-ff', '--message', "Merge branch 'holidays'", 'holidays'], as('Asha', '2026-02-19T09:00'));

  // The shared copy: a bare repository in a folder, pushed to once.
  run(['init', '--quiet', '--bare', 'origin.git'], BASE_ENV, '.');
  run(['remote', 'add', 'origin', '../origin.git']);
  run(['push', '--quiet', '--set-upstream', 'origin', 'main']);

  // Work after the push: a branch that is not merged yet, and two commits on main that are not pushed yet.
  run(['switch', '--quiet', '--create', 'reminders']);
  commit('Imran', '2026-03-02T20:15', "Add a script that lists tomorrow's classes", {
    'reminders.mjs': "import { readFileSync } from 'node:fs';\n\nconst day = process.argv[2] ?? 'Mon';\nfor (const row of readFileSync('timetable.csv', 'utf8').trim().split('\\n').slice(1)) {\n  if (row.startsWith(`${day},`)) console.log(row);\n}\n",
  });
  commit('Imran', '2026-03-04T21:00', 'Send the reminders at 7 AM India time', {
    'reminders.mjs': `${files['reminders.mjs']}\n// Scheduled for 07:00 IST (01:30 UTC) by the college server.\n`,
  });
  run(['switch', '--quiet', 'main']);
  commit('Ravi', '2026-03-03T10:00', 'Move the Data Structures exam a week later', {
    'exams.csv': files['exams.csv'].replace('2026-03-16,Data Structures', '2026-03-23,Data Structures'),
  });
  commit('Meera', '2026-03-05T12:30', 'Add the end-semester exam schedule', {
    'exams.csv': `${files['exams.csv']}2026-05-04,Data Structures,Hall A\n2026-05-06,Operating Systems,Hall B\n`,
  });
}

/**
 * Prints a command the way a terminal shows it, then runs it in the repository and prints what Git printed.
 * Quotes work as in a shell: show(`git log --format='%h %s'`).
 */
export function show(command) {
  const args = words(command);
  if (args.shift() !== 'git') throw new Error(`not a git command: ${command}`);
  const { status, text } = capture(args);
  console.log(`$ ${command}`);
  if (text) console.log(text);
  if (status !== 0) console.log(`(exit status ${status})`);
  console.log('');
}

/** Runs git with its output and its error messages going to one file, so they stay in the order Git wrote them. */
function capture(args) {
  const file = 'git-output.tmp';
  const fd = openSync(file, 'w');
  try {
    const r = spawnSync(GIT, args, { cwd: REPO, env: BASE_ENV, stdio: ['ignore', fd, fd] });
    return { status: r.status, text: readFileSync(file, 'utf8').replace(/\s+$/, '') };
  } finally {
    closeSync(fd);
    rmSync(file, { force: true });
  }
}

/** Splits a command into words the way a shell does with quotes: --format='%h %s' is one word, --format=%h %s. */
function words(command) {
  const out = [];
  let word = null;
  for (let i = 0; i < command.length; i++) {
    const c = command[i];
    if (c === "'" || c === '"') {
      const end = command.indexOf(c, i + 1);
      word = (word ?? '') + command.slice(i + 1, end);
      i = end;
    } else if (/\s/.test(c)) {
      if (word !== null) out.push(word);
      word = null;
    } else {
      word = (word ?? '') + c;
    }
  }
  if (word !== null) out.push(word);
  return out;
}

Each example is a short Node.js program that builds this repository with real Git, then runs Git commands in it and prints each one after a $ sign, as a terminal shows it, followed by what Git printed. Names, email addresses and dates are fixed, in India Standard Time (UTC+05:30), so the commit IDs come out the same on every computer. To follow along, put timetable.mjs and any example in an empty folder, run it with node, and then work in the timetable folder it leaves behind.

The default log, and one line per commit

git log, long and short JavaScript · log_basics.mjs
// The first look at a history: git log in its default form, then one line per commit.
// Run it with Node.js 24 in a folder that holds timetable.mjs: node log_basics.mjs
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show('git --version');
show('git log -2');
show('git log --oneline');

Output

$ git --version
git version 2.54.0 (Apple Git-157)

$ git log -2
commit 482c24cfd77e26040cd9251b07de2e4bf7637d0e
Author: Meera Iyer <meera@example.com>
Date:   Thu Mar 5 12:30:00 2026 +0530

    Add the end-semester exam schedule

commit 7deb62fd768f7a03a0587af9b22a3922b228588e
Author: Ravi Kumar <ravi@example.com>
Date:   Tue Mar 3 10:00:00 2026 +0530

    Move the Data Structures exam a week later

$ git log --oneline
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later
f7c61f5 Merge branch 'holidays'
95ac0e7 Add Ambedkar Jayanti to the holiday list
5bc127d Add Saturday tutorial slots
74349c8 Add the holiday list
8936436 List the exam rules in the README
9b26674 Shift the Physics exam to Hall B
1ecdac6 Merge branch 'labs'
7b5b101 Book Lab 2 for the Thursday batch
a393545 Fix the Friday room numbers
8e5c05a Add lab sessions for both batches
b2bb47d Move Operating Systems to room 204
98fb14a Add the mid-semester exam schedule
a7d97a2 Add Wednesday to Friday classes
8b420d8 Add Monday and Tuesday classes
cebbba9 Start the timetable with a README

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node log_basics.mjs

git log -2 shows the two newest commits in the default format. Each starts with the commit’s full ID, 40 hexadecimal characters, then the author, the date with the author’s time zone, and the message, indented. Commits come newest first, by commit date. -2 is short for --max-count=2. When the output is longer than the screen, a terminal shows it in a pager, usually less, where Space moves down and q quits; git --no-pager log prints it straight through.

git log --oneline gives each commit one line: the first seven characters of its ID and the first line of its message. Any unique beginning of an ID names the commit, so git show 7deb62f works as well as the full ID.

Count the lines: 17, while the repository holds 19 commits. git log starts at HEAD, the commit you have checked out, and walks back through parents, so it shows only commits that HEAD can reach. The two commits of reminders are not among them.

Drawing branches with --graph

Every branch as a graph, then the main line only JavaScript · graph.mjs
// Every branch at once, drawn as a graph, and then only the main line of history.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show('git log --oneline --graph --all --decorate');
show('git log --oneline --first-parent');

Output

$ git log --oneline --graph --all --decorate
* 482c24c (HEAD -> main) Add the end-semester exam schedule
* 7deb62f Move the Data Structures exam a week later
| * 71727f4 (reminders) Send the reminders at 7 AM India time
| * 323c3ec Add a script that lists tomorrow's classes
|/  
*   f7c61f5 (origin/main) Merge branch 'holidays'
|\  
| * 95ac0e7 (holidays) Add Ambedkar Jayanti to the holiday list
| * 74349c8 Add the holiday list
* | 5bc127d Add Saturday tutorial slots
|/  
* 8936436 List the exam rules in the README
* 9b26674 Shift the Physics exam to Hall B
*   1ecdac6 Merge branch 'labs'
|\  
| * 7b5b101 (labs) Book Lab 2 for the Thursday batch
| * 8e5c05a Add lab sessions for both batches
* | a393545 Fix the Friday room numbers
|/  
* b2bb47d (tag: v1.0) Move Operating Systems to room 204
* 98fb14a Add the mid-semester exam schedule
* a7d97a2 Add Wednesday to Friday classes
* 8b420d8 Add Monday and Tuesday classes
* cebbba9 Start the timetable with a README

$ git log --oneline --first-parent
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later
f7c61f5 Merge branch 'holidays'
5bc127d Add Saturday tutorial slots
8936436 List the exam rules in the README
9b26674 Shift the Physics exam to Hall B
1ecdac6 Merge branch 'labs'
a393545 Fix the Friday room numbers
b2bb47d Move Operating Systems to room 204
98fb14a Add the mid-semester exam schedule
a7d97a2 Add Wednesday to Friday classes
8b420d8 Add Monday and Tuesday classes
cebbba9 Start the timetable with a README

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node graph.mjs

--graph draws the shape of the history at the left: each * is a commit, | continues a line of history, |\ opens a merge’s second parent and |/ joins a branch back to the commit it started from. --all adds every branch, tag and remote-tracking branch as a starting point, which brings in the reminders commits. --decorate writes the names that point at commits in brackets: HEAD -> main (you are on main), origin/main (where main was when it was last pushed), tag: v1.0 and the branch names. A terminal shows these names without the option, but output sent to a file or to another program leaves them out, which is why the example asks for them.

--first-parent follows only the first parent of each merge, the branch that was checked out when the merge was made. The labs and holidays commits drop out, and 13 commits remain: the story of main itself, with each merge as a single step. Release notes and audits of the main line are usually built this way.

Filtering by author, date, message and file

Narrowing the history down JavaScript · filters.mjs
// Narrowing the history down: by author, by date, by words in the message and by file, alone and together.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show('git log --oneline --author=Meera');
show('git log --oneline --author=Meera --author=Imran');
show('git log --oneline --since=2026-03-01');
show('git log --oneline --since=2026-03-01 --all');
show('git log --oneline -i --grep=exam');
show('git log --oneline -- exams.csv');
show('git log --oneline --author=Meera --since=2026-02-01 -- exams.csv');

Output

$ git log --oneline --author=Meera
482c24c Add the end-semester exam schedule
95ac0e7 Add Ambedkar Jayanti to the holiday list
74349c8 Add the holiday list
9b26674 Shift the Physics exam to Hall B
98fb14a Add the mid-semester exam schedule

$ git log --oneline --author=Meera --author=Imran
482c24c Add the end-semester exam schedule
95ac0e7 Add Ambedkar Jayanti to the holiday list
74349c8 Add the holiday list
9b26674 Shift the Physics exam to Hall B
7b5b101 Book Lab 2 for the Thursday batch
8e5c05a Add lab sessions for both batches
98fb14a Add the mid-semester exam schedule

$ git log --oneline --since=2026-03-01
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later

$ git log --oneline --since=2026-03-01 --all
482c24c Add the end-semester exam schedule
71727f4 Send the reminders at 7 AM India time
7deb62f Move the Data Structures exam a week later
323c3ec Add a script that lists tomorrow's classes

$ git log --oneline -i --grep=exam
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later
8936436 List the exam rules in the README
9b26674 Shift the Physics exam to Hall B
98fb14a Add the mid-semester exam schedule

$ git log --oneline -- exams.csv
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later
9b26674 Shift the Physics exam to Hall B
98fb14a Add the mid-semester exam schedule

$ git log --oneline --author=Meera --since=2026-02-01 -- exams.csv
482c24c Add the end-semester exam schedule
9b26674 Shift the Physics exam to Hall B

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node filters.mjs

  • --author=Meera keeps commits whose author line matches a regular expression; the line holds the name and the email address, so a part of either is enough. Given twice, --author keeps commits that match either pattern.
  • --since and --until, also spelled --after and --before, filter by commit date. They accept dates such as 2026-03-01 and phrases such as "2 weeks ago". With --since=2026-03-01 the log shows two commits; --all adds the two commits of reminders, which are just as recent.
  • --grep=exam searches commit messages; -i ignores case. Like --author, several --grep options keep commits that match any of them, unless --all-match asks for every one.
  • -- exams.csv keeps commits that changed that file. The -- tells Git that what follows are paths, not branch names. It matters when a file and a branch share a name, or when the file no longer exists.

Different kinds of filter combine: a commit must pass all of them. The last command asks for Meera’s commits since 2026-02-01 that changed exams.csv, and gets two.

Choosing what each commit shows

Your own format, file lists and the full change JavaScript · formats.mjs
// Choosing what each commit shows: your own format, the files it changed, or the whole change.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show("git log -4 --format='%h %ad %an: %s' --date=short");
show('git log -1 --stat');
show("git log -3 --name-status --format='%h %s'");
show('git log -1 --patch');

Output

$ git log -4 --format='%h %ad %an: %s' --date=short
482c24c 2026-03-05 Meera Iyer: Add the end-semester exam schedule
7deb62f 2026-03-03 Ravi Kumar: Move the Data Structures exam a week later
f7c61f5 2026-02-19 Asha Rao: Merge branch 'holidays'
95ac0e7 2026-02-18 Meera Iyer: Add Ambedkar Jayanti to the holiday list

$ git log -1 --stat
commit 482c24cfd77e26040cd9251b07de2e4bf7637d0e
Author: Meera Iyer <meera@example.com>
Date:   Thu Mar 5 12:30:00 2026 +0530

    Add the end-semester exam schedule

 exams.csv | 2 ++
 1 file changed, 2 insertions(+)

$ git log -3 --name-status --format='%h %s'
482c24c Add the end-semester exam schedule

M	exams.csv
7deb62f Move the Data Structures exam a week later

M	exams.csv
f7c61f5 Merge branch 'holidays'

$ git log -1 --patch
commit 482c24cfd77e26040cd9251b07de2e4bf7637d0e
Author: Meera Iyer <meera@example.com>
Date:   Thu Mar 5 12:30:00 2026 +0530

    Add the end-semester exam schedule

diff --git a/exams.csv b/exams.csv
index c32dc7b..2d9adec 100644
--- a/exams.csv
+++ b/exams.csv
@@ -2,3 +2,5 @@ date,course,hall
 2026-03-23,Data Structures,Hall A
 2026-03-18,Operating Systems,Hall A
 2026-03-20,Physics,Hall B
+2026-05-04,Data Structures,Hall A
+2026-05-06,Operating Systems,Hall B

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node formats.mjs

--format builds each line from placeholders: %h is the short ID and %H the full one, %an the author’s name and %ae their email address, %ad the author date, %s the subject (the first line of the message) and %b the rest of it. --date=short prints dates as year, month and day. Scripts should read history this way: the default output changes with settings, terminal width and Git version, while a format string prints exactly what it asks for. For a time you want to convert, %at gives the author date as a Unix timestamp.

The other options add what each commit changed:

  • --stat: the files, with a count of changed lines.
  • --name-status: the files, each with a letter: M modified, A added, D deleted, R renamed.
  • -p or --patch: the whole change, line by line, in the diff format that git diff also prints.

The merge f7c61f5 lists no files. git log shows no change for merge commits unless you ask for one, for example with --first-parent, which shows each merge as a change against its first parent.

Names for commits

Every command in this lesson takes a revision, a name for a commit. Branch and tag names are revisions, and so is any commit ID. Suffixes walk from one commit to another, and the diagram puts the names of the next example on the newest commits of the repository.

The newest commits of the timetable repository with their revision names, grouped into main..reminders and reminders..main.reminders..mainmain..remindersf7c61f5, a mergeHEAD~2 = @{u}merge base5bc127dHEAD~2^1= HEAD~395ac0e7HEAD~2^2= holidays74349c88936436 and 11older commits,v1.0 among them482c24cmain, HEAD7deb62fHEAD~171727f4reminders323c3ecreminders~11st parent2nd parent

Names for commits, and what each side has alone

Text description of the diagram

The diagram draws the newest commits of the timetable repository as boxes, newest at the top. Each arrow points from a commit to its parent.

  1. Two groups sit side by side at the top. The group labelled "reminders..main" holds the commits only main has: 482c24c, where main and HEAD point, and its parent 7deb62f, HEAD~1. The group labelled "main..reminders" holds the commits only the reminders branch has: 71727f4, its tip, and its parent 323c3ec, reminders~1.
  2. Both groups point down to f7c61f5, a merge commit drawn with a double border. It is HEAD~2, it is where the upstream @{u} (origin/main) points, and it is the merge base of main and reminders: the newest commit both branches share.
  3. The merge has two parents. Its first parent is 5bc127d, which HEAD~2^1 and HEAD~3 both name. Its second parent is 95ac0e7, which HEAD~2^2 names and the holidays branch points at; below it comes 74349c8.
  4. Both lines join at a dashed box for 8936436 and the 11 older commits, the v1.0 tag among them.

main...reminders, with three dots, is both groups at the top together: the four commits that only one of the two branches has.

Names for commits, checked by Git JavaScript · revisions.mjs
// Names for commits: HEAD and its ancestors, the parents of a merge, a tag, the upstream branch, and a file as it
// was at a commit.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
for (const name of ['HEAD', 'HEAD~1', 'HEAD~2', 'HEAD~2^1', 'HEAD~2^2', 'HEAD~3', 'v1.0', '@{u}']) {
  show(`git log -1 --format='%h %s' ${name}`);
}
show('git log -1 --format=%h HEAD^2');
show('git show v1.0:timetable.csv');

Output

$ git log -1 --format='%h %s' HEAD
482c24c Add the end-semester exam schedule

$ git log -1 --format='%h %s' HEAD~1
7deb62f Move the Data Structures exam a week later

$ git log -1 --format='%h %s' HEAD~2
f7c61f5 Merge branch 'holidays'

$ git log -1 --format='%h %s' HEAD~2^1
5bc127d Add Saturday tutorial slots

$ git log -1 --format='%h %s' HEAD~2^2
95ac0e7 Add Ambedkar Jayanti to the holiday list

$ git log -1 --format='%h %s' HEAD~3
5bc127d Add Saturday tutorial slots

$ git log -1 --format='%h %s' v1.0
b2bb47d Move Operating Systems to room 204

$ git log -1 --format='%h %s' @{u}
f7c61f5 Merge branch 'holidays'

$ git log -1 --format=%h HEAD^2
fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
(exit status 128)

$ git show v1.0:timetable.csv
day,period,course,room
Mon,1,Data Structures,201
Mon,2,Discrete Maths,105
Tue,1,Operating Systems,204
Tue,2,Physics,Lab A
Wed,1,Data Structures,201
Wed,2,Hindi,105
Thu,1,Operating Systems,204
Fri,1,Discrete Maths,201
Fri,2,Physics,201

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node revisions.mjs

  • ~n walks back n generations, always taking the first parent. HEAD~1 is the parent of HEAD, HEAD~2 its grandparent, which here is the holidays merge. HEAD~ alone means HEAD~1.
  • ^n picks the nth parent of one commit. It matters for merges, which have two: HEAD~2^1 is the commit main was on, the same commit as HEAD~3, and HEAD~2^2 is the tip of the merged holidays branch. HEAD^ alone means HEAD^1, so for an ordinary commit HEAD^ and HEAD~ are the same commit. Asking for HEAD^2 on a commit with one parent fails: Git cannot resolve the name and finds no file of that name either, so it reports an ambiguous argument.
  • A tag names the commit it was made on. v1.0 resolves to b2bb47d, the commit Asha tagged.
  • @{u}, short for @{upstream}, is the remote-tracking branch your branch follows, the one git pull merges from and, in the usual setup, git push sends to. Here that is origin/main, which still points at the holidays merge, because the two newest commits have not been pushed. main@{u} asks the same for a named branch.
  • <rev>:<path> names a file as it was at a revision. git show v1.0:timetable.csv prints the timetable as it was tagged, with Discrete Maths still in room 201 on Friday, before Ravi’s fix.

Two more forms look similar and mean something else. main@{1} reads your reflog, the local record of where a branch pointed before each move, and names where main was before its latest change; another computer’s reflog differs. HEAD@{2} does the same for HEAD. git rev-parse prints the full ID behind any name, which is the quickest way to check that a name means what you think.

Ranges: two dots and three dots

What each branch has, and what either has alone JavaScript · ranges.mjs
// Two dots and three dots in git log: what one branch has that another lacks, and what either has alone.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show('git log --oneline main..reminders');
show('git log --oneline reminders..main');
show('git log --oneline --left-right main...reminders');
show('git merge-base main reminders');
show('git log --oneline @{u}..');
show('git log --oneline ..@{u}');

Output

$ git log --oneline main..reminders
71727f4 Send the reminders at 7 AM India time
323c3ec Add a script that lists tomorrow's classes

$ git log --oneline reminders..main
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later

$ git log --oneline --left-right main...reminders
< 482c24c Add the end-semester exam schedule
> 71727f4 Send the reminders at 7 AM India time
< 7deb62f Move the Data Structures exam a week later
> 323c3ec Add a script that lists tomorrow's classes

$ git merge-base main reminders
f7c61f5175cb05827285695b09fd497809177938

$ git log --oneline @{u}..
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later

$ git log --oneline ..@{u}

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node ranges.mjs

A..B lists the commits that B can reach and A cannot. main..reminders gives the two commits of reminders; turn it round, reminders..main, and you get the two commits main has gained since reminders started. Order matters. A..B is a shorter way to write ^A B, meaning B, excluding what A reaches.

A...B lists the commits that exactly one of the two can reach, the four commits on either side of the fork. With --left-right, Git marks each one < when it comes from the left name and > when it comes from the right. git merge-base main reminders prints the newest commit both share, f7c61f5, the point where reminders started.

Leave out one end and Git puts HEAD there. @{u}.. therefore means @{u}..HEAD: your commits that the upstream does not have yet, the two you would push. ..@{u} lists the opposite, commits on the upstream that you have not merged; it is empty here. The upstream is only as fresh as your last git fetch, so fetch before you trust it.

Two and three dots in git diff

git diff takes the same two spellings, but compares two snapshots instead of listing commits, and that changes what the dots mean.

The same two branches, compared two ways JavaScript · diff_ranges.mjs
// The same two branches compared with git diff: two dots compare the tips, three dots start at the merge base.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show('git diff --stat main..reminders');
show('git diff --stat main...reminders');
show('git diff main..reminders -- exams.csv');

Output

$ git diff --stat main..reminders
 exams.csv     | 4 +---
 reminders.mjs | 8 ++++++++
 2 files changed, 9 insertions(+), 3 deletions(-)

$ git diff --stat main...reminders
 reminders.mjs | 8 ++++++++
 1 file changed, 8 insertions(+)

$ git diff main..reminders -- exams.csv
diff --git a/exams.csv b/exams.csv
index 2d9adec..f95f067 100644
--- a/exams.csv
+++ b/exams.csv
@@ -1,6 +1,4 @@
 date,course,hall
-2026-03-23,Data Structures,Hall A
+2026-03-16,Data Structures,Hall A
 2026-03-18,Operating Systems,Hall A
 2026-03-20,Physics,Hall B
-2026-05-04,Data Structures,Hall A
-2026-05-06,Operating Systems,Hall B

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node diff_ranges.mjs

git diff main..reminders is the same as git diff main reminders: it compares the snapshot at the tip of main with the snapshot at the tip of reminders. The result contains the new reminders.mjs, and also two exam changes, shown in reverse. reminders never touched exams.csv; main changed it after reminders started, so going from one tip to the other undoes those changes. Read as a review of the branch, this diff is wrong.

git diff main...reminders starts at the merge base: it compares f7c61f5 with the tip of reminders and shows only what the branch itself changed, the 8 lines of reminders.mjs. This is the comparison a pull request on GitHub shows. The Git manual says it plainly: git diff compares two endpoints, not ranges. To review a branch, use three dots; to compare two snapshots, use two names.

Git Patch and Diff Viewer (Apply, Reverse, Split) Save git diff or git show output to a .patch file and open it here to read each file's changes side by side.

One commit at a time: git show

A commit, a tag and a merge JavaScript · show.mjs
// git show for an ordinary commit, for a tag, and for a merge (which shows no change of its own).
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show('git show HEAD~1');
show('git show --no-patch v1.0');
show('git show HEAD~2');
show('git show --first-parent --stat HEAD~2');

Output

$ git show HEAD~1
commit 7deb62fd768f7a03a0587af9b22a3922b228588e
Author: Ravi Kumar <ravi@example.com>
Date:   Tue Mar 3 10:00:00 2026 +0530

    Move the Data Structures exam a week later

diff --git a/exams.csv b/exams.csv
index f95f067..c32dc7b 100644
--- a/exams.csv
+++ b/exams.csv
@@ -1,4 +1,4 @@
 date,course,hall
-2026-03-16,Data Structures,Hall A
+2026-03-23,Data Structures,Hall A
 2026-03-18,Operating Systems,Hall A
 2026-03-20,Physics,Hall B

$ git show --no-patch v1.0
tag v1.0
Tagger: Asha Rao <asha@example.com>
Date:   Tue Jan 13 09:05:00 2026 +0530

First timetable of the semester

commit b2bb47de17472f40d9100f88ca7fa93f41b6bc06
Author: Asha Rao <asha@example.com>
Date:   Tue Jan 13 09:00:00 2026 +0530

    Move Operating Systems to room 204

$ git show HEAD~2
commit f7c61f5175cb05827285695b09fd497809177938
Merge: 5bc127d 95ac0e7
Author: Asha Rao <asha@example.com>
Date:   Thu Feb 19 09:00:00 2026 +0530

    Merge branch 'holidays'

$ git show --first-parent --stat HEAD~2
commit f7c61f5175cb05827285695b09fd497809177938
Merge: 5bc127d 95ac0e7
Author: Asha Rao <asha@example.com>
Date:   Thu Feb 19 09:00:00 2026 +0530

    Merge branch 'holidays'

 holidays.csv | 3 +++
 1 file changed, 3 insertions(+)

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node show.mjs

For an ordinary commit, git show prints the same header as git log followed by the change as a patch. For an annotated tag, it prints the tag itself (who tagged, when and with what message) and then the commit the tag points at; --no-patch leaves out the change.

For a merge, git show prints no change at all. It uses a combined diff, which leaves out every part of the result that matches one of the parents, so it shows only what the merge itself changed, usually a conflict someone resolved by hand. A clean merge such as f7c61f5 has nothing to show. To see what a merge brought into main, compare it with its first parent: git show --first-parent does that, and with --stat it shows that the merge added three lines to holidays.csv.

Who did what: git shortlog

Commits per person JavaScript · shortlog.mjs
// Who made how many commits: git shortlog, without a revision, with HEAD, without merges and for every branch.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show('git shortlog -sn');
show('git shortlog -sn HEAD');
show('git shortlog -sn --no-merges HEAD');
show('git shortlog -sn --all');

Output

$ git shortlog -sn

$ git shortlog -sn HEAD
     6	Asha Rao
     5	Meera Iyer
     4	Ravi Kumar
     2	Imran Sheikh

$ git shortlog -sn --no-merges HEAD
     5	Meera Iyer
     4	Asha Rao
     4	Ravi Kumar
     2	Imran Sheikh

$ git shortlog -sn --all
     6	Asha Rao
     5	Meera Iyer
     4	Imran Sheikh
     4	Ravi Kumar

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node shortlog.mjs

git shortlog groups commits by author. -s prints only the count for each person, -n sorts by that count, and -e adds email addresses. Asha has 6 commits on main, but 2 of them are merges; --no-merges leaves those out. --all counts every branch, which adds Imran’s two commits on reminders.

The first command printed nothing, and that is the trap. When it gets no revision and its input does not come from a terminal, git shortlog reads git log output from its standard input instead of the repository. A script or a CI job therefore gets an empty summary, so pass HEAD or a range explicitly. When one person appears under two spellings, say a work and a personal email address, a .mailmap file at the top of the repository tells git shortlog to count them as one.

Version note

The examples ran on Git 2.54.0. Git 2.55 added --graph-lane-limit=<n> to git log, which caps the number of lanes the graph draws and marks the cut with ~, and --max-count-oldest=<n>, which keeps the oldest n commits instead of the newest. Git 2.56 draws root commits, those without a parent, indented in --graph output, so that a history started afresh, such as an unrelated branch, no longer looks attached to the commit above it; --no-graph-indent or the log.graphIndent setting turns that off. This repository’s one root commit is the last line of its graph, and Git 2.56 indents a root only when more commits follow it, so the graph above prints the same there.

Try it

Run any example once, change into the timetable folder it leaves, and answer these with one command each:

  1. Which commits changed labs.csv, and who made them?
  2. What did timetable.csv contain two commits before v1.0?
  3. Which commits came from the labs branch, counting from v1.0?
  4. What would git push send right now?
  5. What would merging reminders change, file by file?

One set of answers, run on the same repository: git log --format='%h %an %s' -- labs.csv, git show v1.0~2:timetable.csv, git log --oneline v1.0..labs, git log --oneline @{u}.. and git diff --stat main...reminders.

The answers, checked JavaScript · try_it.mjs
// One set of answers to the questions of "Try it", run on the same repository.
import { buildTimetable, show } from './timetable.mjs';

buildTimetable();
show("git log --format='%h %an %s' -- labs.csv");
show('git show v1.0~2:timetable.csv');
show('git log --oneline v1.0..labs');
show('git log --oneline @{u}..');
show('git diff --stat main...reminders');

Output

$ git log --format='%h %an %s' -- labs.csv
7b5b101 Imran Sheikh Book Lab 2 for the Thursday batch
8e5c05a Imran Sheikh Add lab sessions for both batches

$ git show v1.0~2:timetable.csv
day,period,course,room
Mon,1,Data Structures,201
Mon,2,Discrete Maths,105
Tue,1,Operating Systems,203
Tue,2,Physics,Lab A
Wed,1,Data Structures,201
Wed,2,Hindi,105
Thu,1,Operating Systems,203
Fri,1,Discrete Maths,201
Fri,2,Physics,201

$ git log --oneline v1.0..labs
7b5b101 Book Lab 2 for the Thursday batch
8e5c05a Add lab sessions for both batches

$ git log --oneline @{u}..
482c24c Add the end-semester exam schedule
7deb62f Move the Data Structures exam a week later

$ git diff --stat main...reminders
 reminders.mjs | 8 ++++++++
 1 file changed, 8 insertions(+)

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node try_it.mjs

In the second answer, Operating Systems is still in room 203: Asha moved it to 204 in the commit she then tagged. Other spellings work too: HEAD~10:timetable.csv and a7d97a2:timetable.csv name the same file.

Practise: names and ranges in code

Git works out every name and range in this lesson by walking parent links. Write that walk for the commit graph of the timetable repository; the sample tests compare your answers with what Git printed.

Exercise · Medium · JavaScript

Work out ~n, ^n, A..B and A...B on a commit graph

Git answers HEAD~2, HEAD^2, main..reminders and main...reminders by walking from commit to parent. Write that walk yourself, for the timetable repository of this lesson.

A commit graph is given as an object: each commit's short ID maps to the list of its parents, first parent first, so the merge f7c61f5 maps to ['5bc127d', '95ac0e7'] and the first commit maps to []. reachable(graph, id) is already written: it returns a Set of every commit you can reach from id, id included.

Write four functions in revisions.mjs and export them:

  • ancestor(graph, id, n) returns the commit that id~n names: follow first parents n times. It returns null when the walk runs past the first commit.
  • parent(graph, id, n) returns the commit that id^n names: the nth parent, or id itself when n is 0. It returns null when the commit has no such parent.
  • twoDot(graph, a, b) returns the commits git log a..b lists, as an array in any order.
  • threeDot(graph, a, b) returns the commits git log a...b lists, as an array in any order.

The sample tests use the 19 commits of the repository, with the IDs the examples printed, and the answers real Git gave for the same names and ranges.

Starter code · revisions.mjs

/** Every commit reachable from `id` by parent links, `id` included (already written). */
export function reachable(graph, id) {
  const seen = new Set();
  const todo = [id];
  while (todo.length > 0) {
    const commit = todo.pop();
    if (seen.has(commit)) continue;
    seen.add(commit);
    todo.push(...graph[commit]);
  }
  return seen;
}

/** id~n: follow first parents n times (null past the first commit). */
export function ancestor(graph, id, n) {
  // Replace this line with your code.
  return null;
}

/** id^n: the nth parent, or id itself for n = 0 (null when there is no such parent). */
export function parent(graph, id, n) {
  // Replace this line with your code.
  return null;
}

/** What git log a..b lists: reachable from b, not from a. */
export function twoDot(graph, a, b) {
  // Replace this line with your code.
  return [];
}

/** What git log a...b lists: reachable from a or from b, but not from both. */
export function threeDot(graph, a, b) {
  // Replace this line with your code.
  return [];
}
The sample tests · revisions.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { ancestor, parent, threeDot, twoDot } from './revisions.mjs';

// The timetable repository of the lesson: each commit's short ID and its parents, first parent first.
const graph = {
  '482c24c': ['7deb62f'],
  '7deb62f': ['f7c61f5'],
  '71727f4': ['323c3ec'],
  '323c3ec': ['f7c61f5'],
  'f7c61f5': ['5bc127d', '95ac0e7'],
  '95ac0e7': ['74349c8'],
  '74349c8': ['8936436'],
  '5bc127d': ['8936436'],
  '8936436': ['9b26674'],
  '9b26674': ['1ecdac6'],
  '1ecdac6': ['a393545', '7b5b101'],
  '7b5b101': ['8e5c05a'],
  '8e5c05a': ['b2bb47d'],
  'a393545': ['b2bb47d'],
  'b2bb47d': ['98fb14a'],
  '98fb14a': ['a7d97a2'],
  'a7d97a2': ['8b420d8'],
  '8b420d8': ['cebbba9'],
  'cebbba9': [],
};
// Where the branches, the tag and the upstream point (git rev-parse --short).
const main = '482c24c';
const reminders = '71727f4';
const labs = '7b5b101';
const holidays = '95ac0e7';
const v1 = 'b2bb47d';
const upstream = 'f7c61f5';
const sorted = (ids) => [...ids].sort();

test('~n follows first parents (main~2, main~3, main~12, reminders~2)', () => {
  assert.equal(ancestor(graph, main, 0), main);
  assert.equal(ancestor(graph, main, 2), 'f7c61f5');
  assert.equal(ancestor(graph, main, 3), '5bc127d');
  assert.equal(ancestor(graph, main, 12), 'cebbba9');
  assert.equal(ancestor(graph, reminders, 2), 'f7c61f5');
});

test('~n past the first commit is null (main~13)', () => {
  assert.equal(ancestor(graph, main, 13), null);
});

test('^n picks one parent of a merge (main~2^1, main~2^2, main~6^2, main^0)', () => {
  assert.equal(parent(graph, ancestor(graph, main, 2), 1), '5bc127d');
  assert.equal(parent(graph, ancestor(graph, main, 2), 2), '95ac0e7');
  assert.equal(parent(graph, ancestor(graph, main, 6), 2), '7b5b101');
  assert.equal(parent(graph, main, 0), main);
});

test('^2 of a commit with one parent, and ^1 of the first commit, are null', () => {
  assert.equal(parent(graph, main, 2), null);
  assert.equal(parent(graph, 'cebbba9', 1), null);
});

test('a..b lists what b reaches and a does not', () => {
  assert.deepEqual(sorted(twoDot(graph, main, reminders)), ['323c3ec', '71727f4']);
  assert.deepEqual(sorted(twoDot(graph, reminders, main)), ['482c24c', '7deb62f']);
  assert.deepEqual(sorted(twoDot(graph, upstream, main)), ['482c24c', '7deb62f']);
  assert.deepEqual(sorted(twoDot(graph, v1, upstream)), ['1ecdac6', '5bc127d', '74349c8', '7b5b101', '8936436', '8e5c05a', '95ac0e7', '9b26674', 'a393545', 'f7c61f5']);
  assert.deepEqual(sorted(twoDot(graph, main, main)), []);
});

test('a...b lists what exactly one side reaches', () => {
  assert.deepEqual(sorted(threeDot(graph, main, reminders)), ['323c3ec', '482c24c', '71727f4', '7deb62f']);
  // labs is merged into holidays' history already, so three dots give the same as labs..holidays.
  assert.deepEqual(sorted(threeDot(graph, holidays, labs)), ['1ecdac6', '74349c8', '8936436', '95ac0e7', '9b26674', 'a393545']);
  assert.deepEqual(sorted(threeDot(graph, labs, holidays)), sorted(twoDot(graph, labs, holidays)));
});
A hint

~n takes the first parent, [0], again and again; ^n reads one entry of the list, [n - 1]. For the ranges, compute reachable() from both ends: a..b keeps what b reaches and a does not, and a...b keeps what exactly one of the two reaches.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

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.

  1. Question 1 of 6 HEAD is a merge commit. Which name means its second parent, the tip of the branch that was merged in?

    Choose one answer.

    Show the answer to question 1

    Answer: HEAD^2

    ^2 picks the second parent of one commit. HEAD~2 and HEAD^^ both walk two generations back along first parents, to the grandparent. HEAD@{2} reads the reflog: where HEAD was two moves ago.

  2. Question 2 of 6 In the timetable repository, which commits does git log --oneline reminders..main list?

    Choose one answer.

    Show the answer to question 2

    Answer: The two commits on main that reminders does not have, 482c24c and 7deb62f

    A..B lists what B can reach and A cannot. Here B is main, so the answer is what main gained after reminders started. Swapping the names, main..reminders, gives the two commits of reminders; three dots give all four.

  3. Question 3 of 6 Before merging a branch called feature into main, you want to see exactly what feature changed. Which command shows that?

    Choose one answer.

    Show the answer to question 3

    Answer: git diff main...feature

    Three dots in git diff compare the merge base with the tip of feature, so only the branch's own changes appear, as in a pull request. Two dots, or two names, compare the two tips, which also shows every later change on main, in reverse. git log lists commits rather than changes.

  4. Question 4 of 6 Which commands list the commits that Meera Iyer (meera@example.com) made to exams.csv? Choose every one that does.

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • git log --author=meera@example -- exams.csv
    • git log --author=Meera -- exams.csv

    --author matches a regular expression against the name and the email address, so part of either works. Everything after -- is a path, so in the third command --author=Meera is read as a file name and Ravi's commit to exams.csv is listed too. --grep searches messages, not authors.

  5. Question 5 of 6 A CI job runs git shortlog -sn to count commits per person, and the result is always empty. Why?

    Choose one answer.

    Show the answer to question 5

    Answer: Without a revision and without a terminal on its input, git shortlog reads git log output from standard input, which is empty

    In a terminal, git shortlog with no revision summarises HEAD. When its input is a pipe or empty, as in most CI jobs, it summarises whatever git log output it is given on standard input. Pass the revision explicitly: git shortlog -sn HEAD.

  6. Question 6 of 6 The timetable repository has 19 commits. How many lines does git log --oneline --first-parent print on main?

    Type a number.

    Show the answer to question 6

    Answer: 13 lines

    HEAD reaches 17 commits; the 2 on reminders are out of reach. --first-parent also drops the 4 commits that reached main only as second parents of the two merges (2 from labs, 2 from holidays), which leaves 13.

Interview questions

What is the difference between HEAD~2 and HEAD^2?

HEAD~2 walks two generations back along first parents: the parent of the parent of HEAD. HEAD^2 stays one generation back and picks the second parent of HEAD, which exists only when HEAD is a merge; otherwise Git reports an unknown revision. The two can be combined: HEAD~2^2 is the second parent of the commit two steps back, the tip of a merged branch. For an ordinary commit with one parent, HEAD^ and HEAD~ both mean the first parent, so the difference shows up only around merges. In practice ~ moves along the main line of history and ^n chooses which side of a merge to follow.

What does git log main..feature list, and how is main...feature different?

main..feature lists commits reachable from feature but not from main: the work on the branch that main does not have yet. main...feature lists commits reachable from either but not both, so it adds the commits main gained after the branch started; --left-right marks which side each one is on. When main has not moved since the branch started, the two give the same list. In git diff the dots mean something else: two dots compare the two tips, while three dots compare the merge base with feature, which shows only the branch’s own changes, as a pull request does.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A teammate reviews a feature branch with git diff main..feature and asks why the branch reverts yesterday's hotfix on main. Nobody touched that code on the branch. Explain what happened, and give the command they should have run.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A release-notes script runs git log between the last two tags and prints commit subjects. It works on your laptop, but in CI it fails with an unknown revision or prints a single commit. What do you check, and how do you make the script reliable?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • git log lists commits newest first from HEAD; --all adds every branch, --graph --decorate draws the shape with names, and --first-parent keeps the main line.
  • Filters combine: --author and --grep take regular expressions, --since and --until take dates, and -- <path> keeps commits that changed a file. Every kind of filter must match.
  • For scripts, ask for --format placeholders instead of reading the default output, and give git shortlog an explicit revision such as HEAD.
  • ~n walks first parents, ^n picks a parent of a merge, a tag names its commit, @{u} is the upstream and <rev>:<path> is a file at a revision.
  • In git log, A..B is what B has and A lacks, and A...B is what either has alone. In git diff, two dots compare the tips and three dots start at the merge base, which is what reviewing a branch needs.
  • git show prints one commit with its change, a tag with its commit, or a file at a revision; for a clean merge it shows no change unless you add --first-parent.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.