Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/gnu-data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ jobs:
path: uutils-coreutils
fetch-depth: 1

- name: Checkout uutils/coreutils history for the activity graph
uses: actions/checkout@v7
with:
repository: uutils/coreutils
path: uutils-coreutils-history
fetch-depth: 0
filter: blob:none

- name: Checkout coreutils/coreutils history for the activity graph
uses: actions/checkout@v7
with:
repository: coreutils/coreutils
path: gnu-coreutils-history
fetch-depth: 0
filter: blob:none

- name: Download the GNU result
uses: dawidd6/action-download-artifact@v21
with:
Expand Down Expand Up @@ -157,6 +173,13 @@ jobs:
mv merged.json unsafe-result.json
rm -f unsafe-latest.json

- name: Regenerate the commit/contributor activity data
shell: bash
run: |
# Fully derived from the two git histories, so no merge needed.
python3 activity_count.py gnu-coreutils-history uutils-coreutils-history > activity-result.json
tail -30 activity-result.json

- name: Add & Commit
if: github.event_name != 'pull_request'
uses: EndBug/add-and-commit@v10.0.0
Expand All @@ -174,6 +197,7 @@ jobs:
python individual-size-graph.py individual-size-result.json
python size-graph.py size-result.json
python unsafe-graph.py unsafe-result.json
python activity-graph.py activity-result.json

- name: Add & Commit the GNU graph
if: github.event_name != 'pull_request'
Expand Down Expand Up @@ -228,3 +252,12 @@ jobs:
message: "Refresh the unsafe graph"
add: unsafe-results.svg
pull: '--rebase --autostash'

- name: Add & Commit the activity graph
if: github.event_name != 'pull_request'
uses: EndBug/add-and-commit@v10.0.0
with:
default_author: github_actions
message: "Refresh the activity graph"
add: activity-results.svg
pull: '--rebase --autostash'
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,16 @@ Tracks how much `unsafe` Rust the project relies on. The total counts

Data lives in [unsafe-result.json](unsafe-result.json). Counting logic is in
`unsafe_count.py`

## Development activity

Monthly commits and contributors for both GNU coreutils and uutils coreutils
since 2021. Non-merge commits only, authors deduplicated via `.mailmap` and
bots (dependabot, renovate, github-actions, …) excluded. The cumulative
contributor panel counts every contributor since each project's first commit,
not just those active since 2021.

![Activity evolution](activity-results.svg)

Regenerated from both git histories once a day by github actions
([activity-result.json](activity-result.json)).
142 changes: 142 additions & 0 deletions activity-graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# This file is part of the uutils coreutils package.
#
# For the full copyright and license information, please view the LICENSE
# file that was distributed with this source code.

"""Render activity-result.json with three stacked panels comparing GNU and
uutils coreutils: commits per month, active contributors per month and
cumulative distinct contributors. All panels share an x-axis so trends line
up vertically.
"""

import sys

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

from graph_common import (
add_gnu_release_markers,
apply_smoothing,
setup_theme,
style_axes,
style_legend,
)

if len(sys.argv) <= 1:
print("activity-graph.py: <json file>")
sys.exit()

raw = pd.read_json(sys.argv[1], orient="index", convert_dates=False)

# Flatten {"2021-01": {"gnu": {...}, "uutils": {...}}} into one row per
# (month, project).
rows = []
for month, projects in raw.iterrows():
for project, values in projects.items():
rows.append(
{
"date": pd.to_datetime(month, format="%Y-%m", utc=True),
"project": project,
**{k: int(v) for k, v in values.items()},
}
)

df = pd.DataFrame(rows).sort_values("date")

print(df)

setup_theme()

palette = {
"gnu": "#0066CC",
"uutils": "#10B981",
}

label_map = {
"gnu": "GNU coreutils",
"uutils": "uutils coreutils",
}


def plot_panel(ax, metric, ylabel, smooth=True):
"""Plot one metric for both projects on `ax`."""
data = df[["date", "project", metric]].copy()
if smooth:
# 3-month rolling mean: monthly counts are spiky (release crunches,
# hackathons) and the trend is what matters here.
data["value"] = apply_smoothing(data, "project", metric, window=3)
else:
data["value"] = data[metric]

sns.lineplot(
data=data,
x="date",
y="value",
hue="project",
palette=palette,
hue_order=["gnu", "uutils"],
linewidth=3,
ax=ax,
markers=False,
dashes=False,
alpha=1,
zorder=3,
)
style_axes(ax, xlabel="Date", ylabel=ylabel)

y_max = data["value"].max()
add_gnu_release_markers(ax, df["date"].min(), df["date"].max(), y_max)

handles, labels = ax.get_legend_handles_labels()
labels = [label_map.get(label, label) for label in labels]
style_legend(ax, handles, labels, ncol=2, loc="upper left")


fig, (ax_top, ax_mid, ax_bot) = plt.subplots(
3, 1, figsize=(18, 20), dpi=100, sharex=True
)

plot_panel(ax_top, "commits", "Commits / month")
plot_panel(ax_mid, "authors", "Active contributors / month")
plot_panel(ax_bot, "cumulative_authors", "Distinct contributors (since day 1)", False)

fig.suptitle(
"GNU vs uutils coreutils — Development Activity Since 2021",
fontsize=26,
fontweight="bold",
color="#1a1a1a",
y=0.995,
)
fig.text(
0.5,
0.972,
"Non-merge commits, bots excluded. Top two panels are 3-month rolling averages; "
"the cumulative panel counts every contributor since each project's first commit.",
ha="center",
va="top",
fontsize=13,
color="#6B7280",
style="italic",
alpha=0.9,
)

# Hide the upper panels' x-tick labels — they duplicate the bottom panel's.
for ax in (ax_top, ax_mid):
plt.setp(ax.get_xticklabels(), visible=False)
ax.set_xlabel("")

plt.tight_layout(rect=[0, 0, 1, 0.96])

plt.savefig(
"activity-results.svg",
format="svg",
dpi=300,
bbox_inches="tight",
facecolor="white",
edgecolor="none",
metadata={
"Creator": "uutils coreutils tracking",
"Title": "Development Activity Evolution",
},
)
Loading
Loading