23.8 Version Control with Git
Table of Contents
Why Use Version Control in ROOT Analyses
Version control with Git is essential for any nontrivial ROOT analysis. It tracks how your code evolves, records who changed what and when, and lets you move safely between different analysis ideas. This is especially important in scientific work because you must be able to reproduce results and justify every step that led to a published figure or number.
Without version control, analysis code tends to accumulate many copies of the same macro in different directories or with names like analysis_final.C, analysis_final2.C, and analysis_really_final.C. Git replaces this with a single clean project history. It also simplifies collaboration, because multiple people can work on different parts of the analysis in parallel and later combine their work in a controlled way.
In this chapter we focus on how to use Git specifically in the context of ROOT analyses. General Git tutorials can be found elsewhere, so here we only explain what is unique or particularly useful when your project contains ROOT macros, C++ source files, configuration files, and data.
Structuring a ROOT Project for Git
Git works best when your project directory has a clean and predictable structure. Earlier in the course you saw a recommended directory structure for ROOT projects, with clear separation between source code, input data, and generated output. That separation becomes especially helpful once you start using Git.
You should keep all files that define how your analysis runs under version control. This typically includes ROOT macros, C++ source and header files, Python scripts that use PyROOT, configuration files, and documentation. At the same time, you should not store large generated files such as plots or intermediate ROOT files in Git, because they change often and quickly increase the repository size.
A typical directory layout that works well with Git might look like the following.
| Directory / file | Typical contents | Track with Git? |
|---|---|---|
src/ | C++ source files, ROOT macros, analysis scripts | Yes |
include/ | Header files, common definitions | Yes |
cfg/ or config/ | Configuration files, cut values, file lists | Yes |
scripts/ | Helper scripts for running or submitting jobs | Yes |
data/ | Small example data, metadata, file lists | Yes, if small and essential |
output/ | Generated plots, ROOT files, logs, temporary files | No, normally ignore |
build/ | Compiled binaries, object files | No, ignore |
doc/ | Documentation, notes, analysis description | Yes |
.gitignore | Ignore rules for Git | Yes |
The exact structure is up to you and your collaboration, but the rule is simple: Git is for code and configuration, not for large or replaceable products of the analysis that can be regenerated from the inputs.
Important rule: Everything you need to reproduce your results should be in Git, except large raw data and large generated files that could be recreated from the tracked code and configuration.
Initializing a Git Repository for a ROOT Analysis
To use Git in a new or existing analysis directory, you first create a repository. Assuming your analysis code lives in a directory such as myroot_analysis, you move into that directory and run Git’s initialization command. After that, you select which files to track and make an initial commit that captures the starting point of your project.
In scientific work you often begin from a working directory that already contains some macros and helper code. That is fine. You can turn it into a Git repository at any time, then gradually bring files under version control. The key is to make sure the initial commit is in a sensible state, for example code that compiles or macros that run without obvious errors.
Once initialized, every further change to your ROOT macros or configuration files can be recorded with a short commit message that explains what you did. Over time you build a history that describes how your analysis evolved from simple tests to a full final result.
What to Track and What to Ignore
A well chosen set of tracked files keeps a Git repository small and easy to work with. For ROOT analyses the main decision is which files to exclude from Git because they are large, transient, or easy to regenerate. Ignoring such files is done through a file named .gitignore in the project root. Git checks this file to decide which paths to skip.
Typical ROOT related patterns that you should place into .gitignore include compiled objects, build directories, temporary macros, editor backup files, and large output. You also want to avoid tracking machine specific files such as .DS_Store on macOS or editor session files.
A typical .gitignore for a ROOT based analysis might contain entries similar to those in the following table.
| Pattern | Purpose |
|---|---|
.o, .so, .dylib, .dll | Compiled objects and shared libraries |
build/, cmake-build*/ | Build directories created by CMake or others |
*.root | Large ROOT files produced by the analysis |
output/ | Directory with generated plots and logs |
.png, .pdf, *.svg | Exported figures and plots |
~, .swp, *.tmp | Editor and temporary files |
.DS_Store | macOS metadata |
You may decide to track some very small example .root files if they are essential test inputs. In that case, you can list a broad pattern to ignore most ROOT files, then add exceptions for specific small files, for example by using ignore patterns together with negative patterns for particular paths.
If you run ROOT macros that generate many intermediate files, directing those outputs into a dedicated output/ directory and ignoring that directory in .gitignore keeps the repository clean. It also reduces the risk of accidentally committing gigabytes of test histograms or temporary TTrees.
Important rule: Never commit large raw datasets or large generated ROOT files to your Git repository. Use .gitignore to keep them out and store them in a separate data area or on shared storage.
Recording Changes: Commits for Analysis Code
Commits are the basic units of history in Git. Each commit records a snapshot of your tracked files, together with a message describing what changed and who made the change. For a ROOT analysis, you should think in terms of analysis steps: each commit should correspond to a small, meaningful change that you can describe in a sentence.
For example, you might commit after you add a new histogram definition, adjust a selection cut, implement a new derived variable, or change the plotting style for final figures. If you change several unrelated things at once, try to split them into separate commits, one for each logical modification.
Good commit messages describe the intent of the change, not just the files touched. In analysis work this is particularly important, because you later need to understand how the physics logic evolved. Messages like "tune electron energy cut" or "add invariant mass histogram for muon pairs" are more helpful than "small changes" or "fix stuff."
You should also commit code in a state that runs or at least compiles. It is acceptable to have intermediate commits during development that are imperfect, but for shared repositories and long term analysis projects it is helpful if most commits correspond to a clean state of the project where basic tests or checks pass.
Over time, your commit history becomes a narrative of the analysis journey. When a reviewer asks why a cut was chosen or when you introduced a rebinning of histograms, you can point to a specific commit where that decision was made.
Using Branches for Analysis Variants
Branches allow you to explore variations of your analysis without disturbing the main line of development. In a ROOT context, branches are especially useful when you want to try different cut values, alternative background models, or updated calibration methods. Instead of overwriting your existing macros, you create a branch, modify the code, and later decide whether to integrate the changes back into the main branch.
A simple approach is to keep a main or master branch that holds the official or current best version of your analysis, and create feature branches for experiments. Names such as test_new_cuts, photon_energy_recalib, or 2018_data_reprocessing make it clear what each branch is for.
If an experiment works well and you decide to adopt it, you merge the branch back into main. If the idea fails, you can either delete the branch or keep it for reference, but your primary analysis line remains unaffected. This method replaces the older practice of copying directories or macros and editing them independently.
Branches are also important in collaborative work. Different team members can work on separate branches, for example one on improved plotting, another on a new efficiency correction, and a third on code cleanup. Git then provides tools to merge these branches together and handle overlapping changes.
Important rule: Use branches instead of copying macros or directories when you want to try analysis variations. This keeps your project organized and your history understandable.
Linking Git History to Physics Results
One of the main reasons to use Git in ROOT based analyses is traceability. Every figure, table, or number that goes into a publication should be reproducible from a well defined state of the code and configuration. Git lets you identify that state precisely, typically using a commit hash or a tag.
When you produce a candidate set of "final" plots, it is good practice to record the Git commit from which they were generated. This can be done in several ways. For example, you might store the commit hash in a text file inside the output directory, include it in the plot file names, or write it directly on the canvas using a small text annotation in a corner.
When a result becomes final, you can mark the corresponding commit with a Git tag such as paper_v1 or thesis_final_plots. A tag is simply a named pointer to a specific commit. Later, if you need to regenerate the exact same histograms or check what code produced a figure, you can checkout that tag and run the analysis again.
If you change analysis choices during an internal review, you can create a new tag for each major revision. This connects the evolution of physics results to the Git history and makes it transparent which code was used at each step. It also makes it easier to reproduce older versions of the analysis if questions arise in the future.
You can also store small text descriptions or change logs in the repository explaining how analysis versions differ at the physics level. However, Git already captures much of this information through commit messages, branch names, and tags, as long as you write them with physics intent in mind.
Important rule: For every published or shared result, record the exact Git commit or tag that produced it so that the analysis can be reproduced exactly.
Sharing and Collaborating with Remote Repositories
Git becomes even more powerful when you use remote repositories, for example on GitHub, GitLab, or internal servers. A remote repository stores the shared version of your analysis project so that multiple collaborators can access it, clone it, push changes, and review each other’s work.
In a typical workflow, one person creates a repository on a hosting service and pushes the local project to that remote. Others then clone the repository, perform their changes locally, and push those changes back. Pull requests or merge requests allow you to propose changes and have them reviewed by colleagues before they enter the main branch.
For ROOT based analyses, remote repositories serve several important purposes. They act as a backup of the code and configuration, independent of any single machine. They ensure that the whole team works from the same analysis framework. They also form part of the documentation of the analysis, because commit history and comments on merge requests provide context for decisions.
When collaborating, it is important to agree on a few conventions. You might define which branches represent stable versions, how to name feature branches, and how detailed commit messages should be. You may also agree that changes affecting physics results must go through review, while minor style fixes can be merged quickly.
In addition, you should protect the remote repository from accidental uploads of large data by maintaining a shared .gitignore and by educating team members about what should and should not be committed. Some hosting services allow you to configure size limits or warnings for very large files, which can be useful for ROOT projects.
By using remote repositories and basic collaboration practices, a ROOT analysis becomes not just a set of macros on one laptop, but a shared, versioned project that can survive hardware failures, staff changes, and the passage of time while remaining reproducible and understandable.
Views: 16
KAHIBARO