Introduction
In his book Software Design X-Rays, Adam Tornhill shows a nice metric to find out if some parts of your code are coupled regarding their conjoint changes: Temporal Coupling.
In this and the next blog posts, I’m playing around with Adam’s ideas (and more) to find hidden dependencies of code parts based on version control data.
In this part, we just want to spot co-changing files which are files that change within the same commit.
As almost always, we are using Python and pandas for this analysis.
Data
With the help of a little helper library, we extract relevant log data from a Git repository. In this case, we are just using a synthetic repository to easier check that everything is working as expected.
Here are all files and all the commits from the repository:
from lib.ozapfdis.git_tc import log_numstat
commits = log_numstat("../../synthetic_repo//")
commits
We see that some files change often together (like “a” and “b” or “b” and “d”) and some files are completely changing alone (like “e”).
Let’s get rid of all the unneeded columns first by just the columns that we really need for this analysis.
commits = commits[['file', 'sha']]
commits.head()
Idea
In this analysis, we need to create a relationship from each changed file to all changed file within the same commit.
I tried different things there with various data transformations, but in the end, the following stupid straightforward approach worked best: We just assign to each file in a commit all files of the same commit and count the occurrence of these relationships.
This gives us the perspectives on co-working changes that we want.
Analysis
To implement the idea of above, we can use the pd.merge command of pandas to combine the commits DataFrame with itself. The key here is to use an outer join to expand each file in a commit (designated by the value sha) to all the files of a commit (again, designated by the values in sha).
import pandas as pd
commit_counts = pd.merge(
commits,
commits,
left_on='sha',
right_on='sha',
suffixes=['','_other'],
how='outer')
commit_counts.head()