Git Log for Researchers: Step-by-Step Guide

By Dr. Zubair Khalid, DVM, MS, PhD ·

Git Log for Researchers: Step-by-Step Guide

By the end of this guide you will be able to:

  • Open any git repository and read its commit history with confidence
  • Condense thousands of commits onto one screen and filter them by author, date, message, or code change
  • Find the exact commit that introduced or removed a piece of code, even across renames and branches
  • Export a clean commit table for a manuscript, supplementary file, or data audit
  • Cite the precise code version behind a published result

You need three things on hand: a computer with git 2.x installed, a terminal (Terminal on macOS, Git Bash or PowerShell on Windows, any shell on Linux), and a repository that has commits in it. If you do not have a project handy, the setup section below shows how to clone a real research codebase in under a minute.

Git is a distributed version control system, a tool that records snapshots of a folder so that every change is attributable and reversible. Version control has become standard practice for scientists because reproducibility now extends past the data to the code that processed it. The git log command is the read-only window into that record. It changes nothing in your files, which makes it the safest git command to learn first.

Why git log matters for researchers

A commit is a snapshot of tracked files, stored together with the author name and email, a timestamp, a message, and a unique 40-character identifier called the commit hash. The hash is a SHA-1 checksum of the commit contents, which means any change to the snapshot produces a different hash. The chain of commits forms the provenance record of a project. When a reviewer asks which code produced a figure, or a collaborator asks why an analysis threshold changed, git log answers from the record instead of from memory.

Real research software shows how much weight this record carries. DEGAn, a Java application that automates Kaplan-Meier and log-rank survival analysis across large differentially expressed gene sets, distributes its source through a public Git repository so that other labs can inspect, reuse, and extend it [1]. The DrugUtilisation R package, built for drug utilisation studies on the OMOP Common Data Model, pairs standardized output formats with comprehensive unit tests, which kept results consistent across databases in the United Kingdom, Estonia, and the Netherlands and minimized coding errors [2]. Disciplined histories, tests, and standardized outputs are what make results comparable across teams and across years. git log is how you read that discipline back out of a repository.

A folder of backups is not the same thing. Copies named final, final2, and final_revised record nothing about who changed what, when, or why. A git history records all of it, and git log is the command that queries it.

Before you start

Check the installation and set your identity, because every commit you make later will carry it:

git --version
git config --global user.name "Jane Doe"
git config --global user.email "[email protected]"

The first command should print something like git version 2.47.1. Any recent 2.x release supports every command in this guide.

For practice material, use your own project if it has commits. Otherwise clone DEGAn, a real research codebase published in a public Git repository [1]:

git clone https://gitlab.com/giuseppeagapito/degan.git
cd degan
git log --oneline -n 5

The worked examples below use a small demonstration repository called rnaseq-pipeline, with seven commits on the main branch and one commit on a feature branch, so every output is predictable. Full option documentation lives in the official Git manual at git-scm.com/docs/git-log.

Git log step by step: the core workflow

Five step flow from reading a full entry to writing the log to a file, with the key option for each step.

Step 1. Open the repository and read a full entry

cd rnaseq-pipeline
git log

Expected output, truncated after three entries:

commit 8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7
Author: Jane Doe <[email protected]>
Date:   Mon Nov 9 10:12:45 2026 -0500

    Update README with run instructions

commit 3f2a9c1b8e4d7a6f5c0b1d2e3f4a5b6c7d8e9f0a
Author: Jane Doe <[email protected]>
Date:   Tue Nov 3 14:22:31 2026 -0500

    Add batch correction step to RNA-seq pipeline

commit 9c8b7a6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b
Author: Jane Doe <[email protected]>
Date:   Tue Oct 20 16:41:07 2026 -0400

    Fix duplicate sample IDs in metadata table

Four older entries follow in the real output. Each entry has four parts:

  1. The commit line with the full 40-character hash, the permanent identifier you can cite
  2. The author name and email, taken from the git configuration
  3. The date, with the UTC offset
  4. The message, indented four spaces

Entries appear newest first. When output fills more than one screen, git pipes it to a pager, a program that scrolls text one screen at a time. The default pager is less. Inside the pager, press the spacebar to page forward, press b to go back, type / followed by text to search, press n for the next match, and press q to quit. To print without a pager, which is useful in scripts, run git, no-pager log -n 3. You can also configure a quieter pager with git config, global core.pager "less -FRX".

Step 2. Get a compact overview

git log --oneline

Expected output:

8e9f0a1 Update README with run instructions
3f2a9c1 Add batch correction step to RNA-seq pipeline
9c8b7a6 Fix duplicate sample IDs in metadata table
b7c8d9e Normalize counts with DESeq2 size factors
d4e5f6a Add read counting script
1a2b3c4 Add sample metadata table
5e6f7a8 Initial commit of RNA-seq pipeline

-oneline condenses each commit to the abbreviated hash plus the subject, which is the first line of the message. Abbreviated hashes are 7 characters by default and grow longer automatically in very large repositories. Limit the list with a number:

git log --oneline -3

To recover the full hash behind an abbreviation at any time:

git rev-parse 9c8b7a6

which prints 9c8b7a6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b. To read the story of the project from its beginning, add -reverse:

git log --oneline --reverse

Step 3. Filter by author and date

Find one person's commits by matching the name or email. The pattern is a regular expression, so partial matches work:

git log --oneline --author="[email protected]"

Expected output:

8e9f0a1 Update README with run instructions
3f2a9c1 Add batch correction step to RNA-seq pipeline
9c8b7a6 Fix duplicate sample IDs in metadata table
d4e5f6a Add read counting script
5e6f7a8 Initial commit of RNA-seq pipeline

Restrict the log to a date window:

git log --oneline --since="2026-10-01" --until="2026-11-01"

Expected output:

9c8b7a6 Fix duplicate sample IDs in metadata table
b7c8d9e Normalize counts with DESeq2 size factors
d4e5f6a Add read counting script

-since and -until accept calendar dates, ISO 8601 timestamps, and relative phrases such as "2 weeks ago". The filters run on the commit date, the moment the snapshot entered the history. The author date, when the change was originally written, can differ after an amend or a rebase. Two synonyms exist: -after equals -since, and -before equals -until. A related filter, -committer="pattern", selects commits by who committed them, which can differ from the author when patches are applied by someone else.

Step 4. Search messages and code changes

Decision path asking what you know about a change and routing to grep, pickaxe, path log or git grep.

Search commit messages, case-insensitively with -i:

git log --oneline --grep="fix" -i

Expected output:

9c8b7a6 Fix duplicate sample IDs in metadata table

-grep reads the full commit message and nothing else. To find where a piece of code appeared or disappeared, use the pickaxe options, named for the way they dig through diffs. -S lists commits where the number of occurrences of a string changed:

git log --oneline -S"DESeq2"

Expected output:

b7c8d9e Normalize counts with DESeq2 size factors

That single result tells you the DESeq2 call entered the pipeline in exactly one commit. -G takes a regular expression and lists commits whose added or removed lines match it:

git log --oneline -G"batch"

Expected output:

3f2a9c1 Add batch correction step to RNA-seq pipeline

Add -i to make -grep and -G case-insensitive. Multiple -grep patterns match commits that satisfy any pattern, and -all-match tightens that to all patterns. Combine a pickaxe search with a path to narrow it further:

git log --oneline -S"DESeq2" -- analysis/counts.R

Step 5. See what each commit changed

Add -stat for a per-commit summary of files and line counts:

git log --stat -1 9c8b7a6

Expected output:

commit 9c8b7a6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b
Author: Jane Doe <[email protected]>
Date:   Tue Oct 20 16:41:07 2026 -0400

    Fix duplicate sample IDs in metadata table

 metadata/samples.csv | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

Add -p to see the patch, the line-by-line diff, which is the record of what changed between two snapshots:

git log -p -1 9c8b7a6 -- metadata/samples.csv

The patch prints in unified diff format. A hunk header such as @@ -34,3 +34,3 @@ means the excerpt starts at line 34 in both the old and new versions and spans 3 lines. Lines starting with - were removed, lines starting with + were added, and unchanged context lines surround them:

@@ -34,3 +34,3 @@
 sample_id,condition,batch
-CTL_04,control,B2
+CTL_004,control,B2
 CTL_05,control,B2

Reading a handful of patches is the fastest way to understand why a result changed between two analysis runs.

Step 6. Trace one file across renames

A pathspec is a pattern that limits a command to specific files or directories. Limit the log to commits that touched a path, and use the - separator to tell git that everything after it is a path, not a revision:

git log --oneline -- analysis/counts.R

Expected output:

3f2a9c1 Add batch correction step to RNA-seq pipeline
b7c8d9e Normalize counts with DESeq2 size factors
d4e5f6a Add read counting script

A directory pathspec covers everything under it, so , analysis/ includes every file in that folder. If a file was renamed at some point, a plain path log stops at the rename. -follow detects renames and keeps tracing backwards:

git log --follow --oneline -- analysis/counts.R

In this repository the path never changed, so the output matches the plain path log. In a repository where counts.R was once called counting.R, -follow adds the older commits that the plain log would hide.

Two failure modes to know. -follow accepts exactly one path:

$ git log --follow --oneline -- analysis/counts.R metadata/samples.csv
fatal: --follow requires exactly one pathspec

A path that does not exist behaves differently with and without -. With the separator, git prints nothing and exits cleanly. Without it, git reports:

fatal: ambiguous argument 'analysis/no_such_file.R': unknown revision or path not in working tree

Step 7. Visualize branches and locate HEAD

git log --oneline --graph --all --decorate

Expected output:

* 8e9f0a1 (HEAD -> main) Update README with run instructions
* 3f2a9c1 Add batch correction step to RNA-seq pipeline
| * a1b2c3d (feature-qc) Draft QC report template
|/
* 9c8b7a6 Fix duplicate sample IDs in metadata table
* b7c8d9e Normalize counts with DESeq2 size factors
* d4e5f6a Add read counting script
* 1a2b3c4 Add sample metadata table
* 5e6f7a8 Initial commit of RNA-seq pipeline

Each * is a commit. The vertical bar shows the feature-qc branch diverging after 9c8b7a6, and the slash marks the fork point. The parenthetical labels are decorations on refs, where a ref is any named pointer to a commit, such as a branch, a tag, or a remote-tracking name like origin/main. HEAD is the pointer to the commit you are currently on, here the tip of main. -all includes commits reachable from every ref, not just HEAD, which is why the feature branch commit appears.

On a repository with many merges, add -first-parent to follow only the mainline through merge points, or -no-merges to hide merge commits entirely. To list just the merges, run git log, merges, oneline.

Step 8. Format output for reports and spreadsheets

The -pretty=format option builds custom layouts with placeholders: %h abbreviated hash, %H full hash, %an author name, %ae author email, %ad author date, %cd commit date, %s subject. Pair it with -date=short for YYYY-MM-DD dates, or -date=iso for full ISO 8601 timestamps. The %x09 placeholder inserts a tab, which keeps the output safe for spreadsheet import even when messages contain commas:

git log --pretty=format:"%h%x09%an%x09%ad%x09%s" --date=short

Expected output, with tabs shown here as spacing:

8e9f0a1  Jane Doe    2026-11-09  Update README with run instructions
3f2a9c1  Jane Doe    2026-11-03  Add batch correction step to RNA-seq pipeline
9c8b7a6  Jane Doe    2026-10-20  Fix duplicate sample IDs in metadata table
b7c8d9e  Ravi Patel  2026-10-12  Normalize counts with DESeq2 size factors
d4e5f6a  Jane Doe    2026-10-01  Add read counting script
1a2b3c4  Ravi Patel  2026-09-15  Add sample metadata table
5e6f7a8  Jane Doe    2026-09-08  Initial commit of RNA-seq pipeline

Redirect the output to a file for a supplementary table or audit trail:

git log --pretty=format:"%h%x09%an%x09%ad%x09%s" --date=short > commit_history.tsv
echo >> commit_history.tsv

The echo appends the final newline that format output omits. For a per-author summary, use git shortlog:

git shortlog -sne HEAD

Expected output:

     5  Jane Doe <[email protected]>
     2  Ravi Patel <[email protected]>

The flags break down as -s for summary, -n for sort by count, and -e for email. Counts split when one person committed under two identities, a common side effect of unconfigured git on a shared cluster.

Key options at a glance

OptionWhat it doesExample
, -, -, -
(none)Full history, newest firstgit log
-onelineOne line per commitgit log, oneline
-n <number>Limit to the last n commitsgit log -n 5
-reverseOldest firstgit log, oneline, reverse
-author="<pattern>"Commits by matching author or emailgit log, author="[email protected]"
-since, -untilDate window on the commit dategit log, since="2026-10-01"
-grep="<text>"Search commit messagesgit log, grep="fix" -i
-S"<string>"Commits where the string count changedgit log -S"DESeq2"
-G"<regex>"Commits with matching changed linesgit log -G"batch"
-pShow the full diff per commitgit log -p -1 9c8b7a6
-statFiles and line counts per commitgit log, stat -3
-followTrace one path across renamesgit log, follow, analysis/counts.R
-graphDraw branch structure next to commitsgit log, graph, oneline, all
-pretty=format:"..."Custom output layoutgit log, pretty=format:"%h %ad %s", date=short
<A>..<B>Commits in B but not in Agit log v1.0.0..v1.1.0

Tags, ranges, and changelogs for manuscripts

A tag is a named pointer to one commit. An annotated tag also stores a message, author, and date, which makes it the right choice for anything you plan to cite. Create one at the commit that produced a manuscript's results:

git tag -a v1.0.0 -m "Code used for the November 2026 manuscript submission"

Rerun the step 7 command and the commit line for 3f2a9c1 now carries a (tag: v1.0.0) decoration. A plain git tag v1.0.1-rc1 without -a creates a lightweight tag with no metadata, fine for scratch use and weaker for citation.

Ranges answer the question "what changed between two versions". A range A..B lists commits reachable from B but not from A:

git log --oneline v1.0.0..v1.1.0

The same syntax works with branches, which is how you review the commits a pull request would introduce:

git log --oneline main..feature-qc

Expected output:

a1b2c3d Draft QC report template

Add -no-merges to keep generated changelogs free of merge noise.

For researchers on GitHub, GitLab, or an institutional server, the history you see locally is the same history the web interface renders. Two practices make that history citable. First, record the full hash in your methods section or repository README, for example "analysis run at commit 3f2a9c1b8e4d7a6f5c0b1d2e3f4a5b6c7d8e9f0a". Second, pair the tag with an archived copy of the release through a long-term preservation service that issues a DOI, because tags live inside the repository and repositories can move or disappear. DEGAn illustrates the distribution side of this arrangement: its source and binaries sit in a public Git repository, and its updated release adds enhanced reporting aimed at reproducible exploratory screening, which only pays off when the code version is pinned [1].

How to verify the history is complete and correct

  1. Confirm where you are. git status prints the branch name on its first line, and git branch, show-current prints just the branch. Most reports of a "wrong" log come from the wrong clone or the wrong branch.
  1. Cross-check counts. git rev-list, count HEAD prints 7 for the demonstration repository, matching the seven lines from step 2. git rev-list, count, all prints 8 because the feature branch commit is included. If a collaborator's count differs from yours, one of you holds commits the other has not fetched.
  1. Expand and inspect. git rev-parse <short-hash> prints the full hash. git show, stat 9c8b7a6 prints the message and diffstat for one commit, which must match what step 5 showed. git show prints the full patch as well.
  1. Locate the nearest tag. git describe, tags prints v1.0.0-1-g8e9f0a1, read as "one commit after v1.0.0, ending at abbreviated hash 8e9f0a1". This is a quick sanity check that HEAD sits where you think it does.
  1. Sync the remote view. git fetch, all downloads new commits from hosting remotes without touching your working files. Then git log, oneline origin/main -n 5 shows the server's version of main. A local log can never show commits that were never fetched.

Git log tips for research repositories

  • Alias the view you use most, so a two-word command replaces a long one:
git config --global alias.lg "log --oneline --graph --decorate --all"
git lg
  • Use -no-pager in scripts and automated reports so output flows into files instead of stopping at an interactive pager.
  • Write messages for the version of you that reads the log in two years. "Fix bug" answers nothing. "Fix sample ID collision in metadata merge" answers a reviewer's question. One logical change per commit keeps -S searches precise.
  • Standardize user.name and user.email in git configuration before the first commit on any shared cluster, because author filters and shortlog counts split identities that differ by capitalization or email.
  • Pair history with tests. The DrugUtilisation project combined versioned releases with comprehensive unit tests and standardized outputs so the same pipeline produced comparable results across databases in three countries [2]. The commit hash tells you which code ran, and the tests tell you it behaved as intended.

Common Mistakes and Limitations

  1. Reading the wrong repository or branch. The symptom is a history that looks unfamiliar. Run git status and git branch, show-current before interpreting anything.
  1. Treating an empty result as an error. Filters such as -author, -since, and -grep legitimately return nothing when nothing matches. Relax one filter at a time until commits reappear, then tighten again.
  1. Recording short hashes in permanent documents. A 7-character abbreviation is unique only within one repository at one point in time. In manuscripts, READMEs, and audit records, store the full 40-character hash or a tag.
  1. Assuming history is immutable. Amending, rebasing, or force-pushing rewrites hashes and dates, so a cited hash can stop resolving. Never rewrite shared branches, and prefer tags plus archived releases for anything you cite.
  1. Expecting git log to search file contents. -grep reads commit messages, -S and -G read the changes between snapshots, and none of them search the current files. To search the working tree, the files as they are checked out right now, use git grep "pattern".
  1. Fighting the pager. Output that appears to hang after git log is usually less waiting for input. Press q. In scripts, use -no-pager.
  1. Misusing -follow. It takes exactly one pathspec, failing with fatal:, follow requires exactly one pathspec, and it works on files, not directories.
  1. Trusting a local log as the whole story. git log shows only commits your clone knows about. Fetch before comparing with collaborators, and remember that unpushed local commits are invisible to everyone else.
  1. Reading merge commits wrong. git log -p shows no diff for merge commits by default. Pass -m to diff against each parent, or -cc for the combined view.
  1. Ignoring the privacy embedded in history. Author names and emails are permanent parts of every commit. Before pushing to a public repository, run git log, format="%an <%ae>" | sort -u to see exactly what will be published.

Two limitations deserve plain language. git log records what was committed, not what was executed. A hash proves the state of the code at a moment, not that a particular analysis used it, unless you pair the hash with run logs, tags, or archived releases. And git cannot show history that was never committed. The commit is the unit of provenance, so the record is only as complete as the commits you make.

Frequently Asked Questions

What does the git log command do?

It prints the commit history of a repository, newest first, showing the commit hash, author, date, and message for each commit. Options filter by author or date, condense output to one line per commit, or reformat it for reports.

How do I exit the git log view?

Press q. Git sends long output through the less pager, where q quits, the spacebar pages forward, and a forward slash starts a text search.

How do I see the history of a single file?

Run git log, follow, path/to/file. The path limits the log to commits that touched the file, and -follow keeps tracing across renames.

How do I find who wrote a particular line of code?

Use git blame with a line range, for example git blame -L 10,20 analysis/counts.R. Each line is annotated with the commit and author that last changed it.

How do I search the history for a specific change?

Use git log, grep="text" for commit messages and git log -S"string" for code. The -S option lists commits where the number of occurrences of the string changed.

Why does git log return nothing?

Usually the command ran outside a repository or on a branch with no commits yet. Look for the message fatal: not a git repository, run git status, and confirm the project folder and branch.

How do I cite the exact code version used in a manuscript?

Record the full 40-character hash from git rev-parse HEAD, or create an annotated tag with git tag -a and archive the release. A tag plus a hash identifies the version unambiguously.

What is the difference between git log, git show, and git reflog?

git log walks committed history, git show prints one commit in full, and git reflog records where HEAD has moved, including commits that are no longer on any branch.

Further Reading

Sources

  1. Differentially Expressed Gene Annotator (DEGAn): automated annotation and analysis of DEGs datasets with OS and PFS data.
  2. DrugUtilisation: An R Package to Support Drug Utilisation Research Using the OMOP Common Data Model.