Tutorials › Pharmacometric Workflows and Reproducible Modeling › Small Commits as Reviewable Units

Project Structure, Version Control and Documentation · Tutorial 56 of 399

Small Commits as Reviewable Units

Learn how to build, inspect, and document small, coherent Git commits so each recorded change is understandable and intentional.

Intermediate 10 min read

What You'll Learn

  • Explain how Git’s working tree, staging area, and last commit determine what a new commit records.
  • Choose a coherent scope for a commit in a pharmacometric project.
  • Use Git status and diff commands to inspect staged and unstaged changes separately.
  • Stage selected files or changes without including unrelated edits.
  • Recognize and correct common staging mistakes before committing.
  • Write commit messages that state the change clearly and concisely.

Why Commit Size Matters

A commit is a recorded change to a project, but its value depends on how well a reader can understand what changed and why. A commit that combines an analysis-script correction, an unrelated README rewrite, and a generated run folder forces reviewers to disentangle separate decisions. A focused commit lets a reviewer assess one coherent unit of work and gives the project history a useful record of that change.

In a pharmacometric repository, changes may affect model code, data-preparation scripts, configuration, documentation, or outputs. A small commit does not necessarily mean changing only one file. It means grouping changes that belong to one understandable purpose, while keeping unrelated work separate. The goal is not to make history look busy; it is to make each recorded step clear enough to inspect and explain.

Definition: A focused commit records a coherent set of project changes that serve one identifiable purpose. A reviewable commit is focused enough that a reader can inspect its contents and understand what changed, why it changed, and what to check.

What Git Records

To make commits predictable, distinguish three states. The working tree is the set of files currently present in the project directory. The staging area (also called the index) is the proposed snapshot for the next commit. The current commit, often called HEAD, is the recorded project snapshot at the tip of the checked-out history. Git commits the staged snapshot, not every change currently visible in the working tree.

The usual sequence is to change files, select changes for staging, inspect that staged selection, and commit it. The commands git status, git diff, and git diff --cached answer different questions. Status summarizes tracked modifications, staged changes, and untracked files. The ordinary diff shows changes in the working tree that are not staged. The cached diff shows what is staged relative to HEAD—the contents that would be included in the next commit.

Key rule: A commit records the contents in the staging area at commit time. It does not automatically include unstaged edits or untracked files. Therefore, inspect the staged snapshot—not only the working files—before committing.

This separation is useful because selecting changes is a deliberate act. For example, a script and its corresponding documentation may belong in one commit if the documentation explains the script change. A temporary local setting or an unrelated correction may not. The question is whether the selected changes tell one coherent story, not whether they happen to be in the same directory.

Choosing and Inspecting a Commit

A practical way to decide scope is to state the intended change in one sentence before staging anything. If the proposed commit needs several unrelated “and also” clauses to describe it, it may contain more than one unit of work. Conversely, splitting a change too finely can make the history harder to follow: a script change whose required configuration is in a separate commit may leave an intermediate snapshot confusing or unusable.

Once the intended scope is clear, inspect the repository and stage only the relevant changes. When a whole file belongs in the commit, git add path/to/file stages it. When only part of a file belongs, git add -p path/to/file offers changes in sections, or “hunks,” for selective staging. This is useful when an intended edit and an unrelated edit are in the same file. After staging, run git diff --cached and read the staged changes as a reviewer would. Then check status again to see whether other changes remain unstaged or untracked.

1
State the purpose.
Describe the intended change in a short sentence. Use that sentence to decide what belongs together.
2
Inspect the repository.
Run git status and identify modified, staged, and untracked files. Check whether each item is relevant to the stated purpose.
3
Stage selectively.
Stage only the files or hunks that belong. Use partial staging when a file contains both relevant and unrelated edits.
4
Review the proposed snapshot.
Run git diff --cached and check the staged contents. Use git diff to examine remaining unstaged edits, and status to confirm the overall state.
5
Commit and verify.
Write a message that accurately summarizes the staged change, commit, then run git status to confirm what remains.

Worked Examples

Worked Example: Committing a Single Analysis Improvement

Suppose a hypothetical project has an analysis script at scripts/prepare_analysis.R. You correct a unit conversion in that script, and no other files have changed. The intended purpose is “correct the concentration unit conversion in the analysis preparation script.”

First, run git status. It reports scripts/prepare_analysis.R as modified. Since the whole file change belongs to the stated purpose, stage it with git add scripts/prepare_analysis.R. Then run git diff --cached and confirm that the staged edit is the unit-conversion correction, rather than an unrelated change that happened to be in the same file.

If the staged diff shows that the conversion now multiplies by 1,000 where the source and target units require that factor, and no other changes are included, the staged snapshot matches the purpose. Commit it with a message such as Correct concentration unit conversion. Finally, run git status. If the working tree is clean, no other changes remain; if not, inspect the reported changes rather than assuming the commit included them.

Worked Example: Keeping an Accidental Edit Out

Suppose a hypothetical project has two intended changes: a correction to a model-run configuration and an explanation of that configuration in the README. While working, you also make an unrelated formatting edit to a dataset-checking script. The first two changes form one coherent update; the formatting edit does not.

Run git status to identify the three modified files. Stage only the configuration and README with git add config/run.yml README.md. Now run git diff --cached. It should show only those two files. Run git diff as well: it should show the unstaged formatting change in the dataset-checking script. The two commands together establish what will be committed and what will not.

If the staged diff is accurate, a message such as Document the updated run configuration describes the combined purpose. The unrelated formatting edit remains in the working tree after the commit. It can be reviewed and handled separately; it should not be included merely because it is already present locally.

Worked Example: Handling an Edit Made After Staging

Suppose you stage a change to a hypothetical README using git add README.md. Before committing, you make a second edit to that same file to correct a spelling error. The working file now contains both edits, but the staging area still contains the version captured when you ran git add.

Run git diff --cached to inspect the staged version and git diff to inspect the later, unstaged spelling correction. If the planned commit should include only the first edit, commit the staged snapshot as intended; the spelling correction remains unstaged. If both edits belong in this commit, stage the later edit again with git add README.md, then run git diff --cached once more to verify that the staged snapshot now includes both.

The important check is not whether the working file looks right, but whether the staged contents match the intended commit. Staging a file does not freeze it permanently: later edits to that file can create a difference between the working tree and the index.

Why the Workflow Works

The logic follows from what a commit records. Let the working tree contain the current file contents and let the staging area contain the selected snapshot. Staging copies selected current contents into the index; committing then records the index. Therefore, if an unrelated edit is not staged, it is not part of that commit. If a change is made after staging, the index need not match the working tree, so checking both staged and unstaged differences is necessary to know exactly what will be recorded.

This is not a guarantee that the change is scientifically correct or that the analysis runs as intended. It is a guarantee about scope: the commit contains the staged snapshot. Review of code behavior, data quality, and model adequacy are separate checks. Keeping commit scope clear makes those other checks easier to interpret, because a reviewer can connect an observed change to a specific stated purpose.

Commit messages should summarize the change rather than merely announce an action. “Update files” gives little information; “Document the updated run configuration” gives a reader a useful clue. A concise imperative phrase is a common convention, but clarity and accuracy matter more than a particular grammatical form. If rationale is important and cannot fit in the summary, include a brief explanation in the message body.

Alternatives, Diagnostics, and Limitations

A single large commit may be quick to create, but it bundles unrelated decisions and makes it harder to review or identify which change is relevant when investigating a later problem. At the other extreme, making a separate commit for every tiny edit can fragment one logical change across several steps. Focused commits balance these approaches: each commit should be coherent on its own, and a set of related commits can together complete a larger task.

Staging whole files is simple when each file has one purpose. Partial staging with git add -p is an alternative when a file mixes related and unrelated edits. It requires careful review because it is possible to select the wrong hunk or create a staged combination that is difficult to understand. Regardless of staging method, inspect the cached diff before committing.

Common warning signs include a staged diff with files unrelated to the stated purpose, an unexpectedly large change, or a mismatch between the staged and unstaged diffs. A status report that still lists untracked files also deserves attention: those files are not automatically part of a commit. Some project files may be intentionally excluded by ignore rules; if an expected file is absent, investigate why rather than forcing it into the commit without considering the project’s file-handling conventions.

  • Wrong reasoning: “I reviewed the files on screen, so the commit will contain everything I saw.”
    Correction: The commit contains the index. Review git diff --cached and check for remaining working-tree changes.
  • Wrong reasoning: “These edits are in the same file, so they belong in the same commit.”
    Correction: Commit scope is determined by purpose, not by file boundaries. Select hunks if only part of a file belongs.
  • Wrong reasoning: “The commit succeeded, so the working tree must be clean.”
    Correction: Unstaged edits and untracked files can remain. Check status after committing.

Small commits improve traceability, but they do not replace clear project documentation, appropriate tests, or review of the analysis itself. Nor should a change be split so aggressively that an intermediate recorded state cannot be understood. Choose a scope that is both reviewable and meaningful as a project change.

Check Your Understanding

Answer each question using the working-tree, staging-area, and commit distinctions.

  1. A script is staged, then edited again before committing. Which version is included in the commit?
  2. A README change is staged, while an unrelated script edit is unstaged. What should you inspect to verify the proposed commit?
  3. Why might two changes in one file belong in separate commits?
  4. Status shows an untracked file after you stage and commit another file. Was the untracked file included? What should you do next?

Solutions: (1) The commit includes the version in the staging area at commit time; the later edit is excluded unless staged. (2) Inspect git diff --cached for the proposed commit, git diff for unstaged tracked changes, and git status for the overall state. (3) If the changes serve different purposes, separating them can make each commit easier to understand; partial staging can select the relevant hunks. (4) No. Untracked files are not included automatically. Inspect the file and decide whether it belongs in a later commit or should remain outside version control.

Clinical Biostats