Skip to content

Metrics and analyses

GitProjectHealth provides three levels of analysis:

  • built-in metrics (about 50 of them), computed per user or per project over periods, and exported in CSV with GitMetricExporter;
  • code churn, computed on a commit and the commits that follow it;
  • ownership analyses: degree of authorship of a file, and truck factor of a set of directories.

GitMetricExporter imports what it needs through an importer, computes every metric for each user or project and each period, and writes CSV files. It was designed for GitLab: user metrics rely on importUserByUsername: and importContributedProjectsOfUser:.

  1. Create an importer (see Importers).

    model := GLHModel new name: 'metrics'.
    importer := GitModelImporter
    forGitlabHostOn: '<GITLAB-URL>/api/v4'
    withToken: '<TOKEN>'
    andModel: model.
    importer withCommitDiffs: true. "needed by code addition, deletion and churn metrics"
  2. Create the exporter and choose what to analyze: users, projects, or both.

    exporter := GitMetricExporter new
    glhImporter: importer;
    label: 'my-team'; "used in the CSV file names"
    withEmailDomain: '@mycompany.com'; "to match Git users with Jira accounts"
    maxCommitWindow: 3; "number of following commits considered for churn"
    yourself.
    "Users, by platform username: imports each user and the projects they contributed to"
    exporter setupAnalysisForUsersWithNames: #( 'alice' 'bob' ).
    "Projects, by ID"
    exporter setupAnalysisForProjectsWithIds: #( 1234 5678 ).
  3. Add one or more periods. Pass Dates, or strings in the mm-dd-yyyy format.

    exporter
    addAPeriodFrom: (Date year: 2026 month: 1 day: 1)
    to: (Date year: 2026 month: 3 day: 31).
    exporter addAPeriodFrom: '04-01-2026' to: '06-30-2026'.
  4. Compute and export. The argument of generateAnalysesOver: is the granularity used to average the metrics: Date, Week, Month or Year.

    exporter generateAnalysesOver: Week.
    exporter exportInCSV.

    Two files are written in your home directory: GitlabHealth-'my-team'-Week-'users'-<timestamp>.csv and …-'projects'-….csv, with one row per user (or project) and one column per metric and period.

To compute only some metrics, set them before generating the analyses:

exporter metrics: { CommitFrequencyMetric. CodeChurnMetric. MergedMergeRequestMetric }.

Metrics about Jira (UserJiraMetric subclasses, …LinkedToJira…) also need a Jira importer: exporter jiraImporter: aJiraPharoImporter (see Jira connector).

User metrics are computed for each user given to setupAnalysisForUsersWithNames:; project metrics for each project given to setupAnalysisForProjectsWithIds:.

Metric classScopeDescription
CommitFrequencyMetricusercommits frequency (avg)
TimeBetweenCommitMetricuseraverage time between commits (seconds)
CodeAdditionMetricusercode addition (avg)
CodeDeletionMetricusercode deletion (avg)
CodeChurnMetricuserchurn % (window of maxCommitWindow commits)
DelayUntilFirstChurnMetricuserdelay until first churn
CommentContributionMetricusercomment contribution (avg)
ContributedProjectMetricusernumber of projects with at least one commit of the user
OpenedMergeRequestMetricusernumber of opened merge requests
MergedMergeRequestMetricusernumber of merge requests created by the user and merged
ClosedMergeRequestMetricusernumber of closed merge requests
PendingMergeRequestMetricusermerge requests opened during a period and still open at its end
MergeRequestDurationMetricuseraverage time to merge a merge request (seconds)
MergeRequestMergedByUserMetricusernumber of merge requests merged by the user
ReviewedByUserMergeRequestMetricusernumber of merge requests reviewed by the user
SelfMergedUnderAMinuteMergeRequestMetricusermerge requests merged by their author less than a minute after creation
CodeAdditionByMRMetric, CodeDeletionByMRMetricuseraverage code addition / deletion by merge request
CommitsLinkWithTicketMetricuser (Jira)number of commits linked to a Jira ticket
CommitsNumberByJiraTicketsMetricuser (Jira)average number of commits by merge request linked to a Jira ticket
ClosedTicketsMetricuser (Jira)number of closed merge requests linked to a Jira ticket
CodeAdditionByTicketMetric, CodeDeletionByTicketMetricuser (Jira)average code addition / deletion by merge request linked to a Jira ticket
JiraTimeMRTimeDifferenceMetricuser (Jira)difference between Jira estimate and merge request time (seconds)
CommitFrequencyProjectMetricprojectcommits frequency (avg)
ContributorsProjectMetricprojectnumber of contributors (avg)
CommitWithoutUsernameProjectMetricprojectcommits without a real username (avg)
CodeAdditionByCommitProjectMetricprojectcode addition by commit (avg)
CodeDeletionProjectMetricprojectcode deletion
CodeChurnOnCommitsProjectMetricprojectchurn % in project
CodeChurnOnMergedMergeRequestProjectMetricprojectchurn % made by a merge request (avg)
CommentLinesAddedProjectMetricprojectcomment lines added (avg)
ActiveBranchesProjectMetric, InactiveBranchesProjectMetricprojectnumber of active / inactive branches in the period (avg)
CreatedMergeRequestProjectMetricprojectcreated merge requests (avg)
MergedMergeRequestProjectMetric, ClosedMergeRequestProjectMetricprojectmerged / closed merge requests (avg)
MergedMergeRequestDurationProjectMetric, ClosedMergeRequestDurationProjectMetric, OpenedMergeRequestDurationProjectMetricprojectduration of merged, closed, and still open merge requests (seconds)
CodeAdditionByMergeRequestProjectMetric, CodeDeletionByMergeRequestProjectMetricprojectaverage code addition / deletion by merge request
CodeAdditionDuringAndByMergeRequestProjectMetric, CodeDeletionDuringAndByMergeRequestProjectMetricprojectaverage code addition / deletion during and by merge requests
SelfMergedUnderAMinuteMergeRequestProjectMetricprojectmerge requests merged by their author less than a minute after creation
CreatedNoteInMergedOrClosedMergeRequestprojectnotes created in merged or closed merge requests
AutoCodeReviewCommitFrequencyProjectMetric, CreatedACRPipelineByProject, CreatedNotesByACRByProjectMetricprojectactivity of an automatic code review bot (commits, pipelines, notes)
CommitLinkedToJiraProjectMetric, MergeRequestLinkedToJiraProjectMetricproject (Jira)commits / merged merge requests linked to a Jira ticket
JiraTimeMRTimeDifferenceProjectMetricproject (Jira)difference between Jira estimate and merge request time (seconds)

All metrics are subclasses of UserMetric or ProjectMetric; new subclasses are picked up automatically by GitMetricExporter.

The churn of a commit is the share of the lines it added that are modified again by the following commits. It needs commits with diff ranges: with the local importer, use withDiffRanges: true.

model := GLHModel new name: 'churn'.
repository := GLHRepository new.
repository cacheAt: #localImporterReference put: '<PATH-TO-CLONE>' asFileReference.
project := GLHProject new name: 'my-project'; repository: repository; yourself.
model add: project; add: repository.
(GitLocalModelImporter new
glhModel: model;
withCommitsSince: 90 days;
withCommitDiffs: true;
withDiffRanges: true;
yourself) importRepository: repository.
commit := (model allWithType: GLHCommit) detect: [ :each | each short_id = '<SHORT-SHA>' ].
"Churn of the commit, looking at the 3 following commits"
churn := commit computeChurnForMaxCommits: 3.
churn at: #totalContribution. "lines added by the commit and the following ones"
churn at: #churns. "per file: churned lines of code"

The degree of authorship (DOA) estimates how much each developer knows a file, from the history of its changes. It is based on the paper A Novel Approach for Estimating Truck Factors and requires an import with withFiles: true and withCommitDiffs: true.

"model: a model imported with the local importer, withFiles: true"
file := (model allWithSubTypesOf: GLHFile) detect: [ :each | each path = 'src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st' ].
file degreeOfAuthorship. "normalized DOA of every contributor, between 0 and 1"
file degreeOfAuthorshipAuthors. "only the authors (normalized DOA above 0.75)"
file mainAuthor. "same as degreeOfAuthorship"

The blog post Building an ownership cartography of a system uses it to draw an ownership tree map.

The truck factor is the minimal number of developers that have to leave a project before more than half of its files have no author left.

"model: a model imported with the local importer, withFiles: true, on the whole history"
root := (model allWithType: GLHFileDirectory) detect: [ :directory | directory path = 'src' ].
info := GLPTruckFactor new
directories: { root };
truckFactor.
info truckFactor. "the truck factor"
info coverage. "share of files still covered by an author after removing them"
info totalFiles. "number of files considered"