1313import calendar
1414from collections import defaultdict
1515import matplotlib .pyplot as plt
16+ import matplotlib .dates as mdates
1617import numpy as np
1718import pandas as pd
1819import seaborn as sns
@@ -143,6 +144,24 @@ def get_contribution_data(year=None, month=None):
143144 except (ValueError , IndexError ):
144145 continue
145146
147+ # Track Generated-by usage over time
148+ cmd = (
149+ f'git log { date_filter } --grep="Generated-by:" '
150+ '--pretty=format:"%cd" --date=format:"%Y-%m"'
151+ )
152+ output = run_git_command (cmd )
153+
154+ generated_by_monthly = defaultdict (int )
155+
156+ for line in output .split ("\n " ):
157+ entry = line .strip ()
158+ if entry :
159+ try :
160+ month_date = datetime .strptime (f"{ entry } -01" , "%Y-%m-%d" ).date ()
161+ generated_by_monthly [month_date ] += 1
162+ except ValueError :
163+ continue
164+
146165 # Get total commits for period calculation
147166 cmd = f"git log { date_filter } --oneline | wc -l"
148167 total_commits = int (run_git_command (cmd ))
@@ -170,6 +189,7 @@ def get_contribution_data(year=None, month=None):
170189 total_commits ,
171190 period_desc ,
172191 ai_boom_marker ,
192+ dict (generated_by_monthly ),
173193 )
174194
175195
@@ -179,11 +199,15 @@ def create_contribution_graphs(
179199 total_commits ,
180200 period_desc ,
181201 ai_boom_marker = None ,
202+ generated_by_monthly = None ,
182203 year = None ,
183204 month = None ,
184205):
185206 """Create comprehensive visualization graphs"""
186207
208+ if generated_by_monthly is None :
209+ generated_by_monthly = {}
210+
187211 # Determine output filename
188212 if year :
189213 if month :
@@ -222,12 +246,19 @@ def create_contribution_graphs(
222246 title_suffix = " (All Time)"
223247
224248 # Create figure with multiple subplots and better styling
225- fig = plt .figure (figsize = (22 , 14 ), facecolor = '#f8f9fa' )
249+ fig = plt .figure (figsize = (24 , 16 ), facecolor = '#f8f9fa' )
250+ gs = fig .add_gridspec (
251+ 3 ,
252+ 3 ,
253+ height_ratios = [1 , 1 , 1.1 ],
254+ hspace = 0.35 ,
255+ wspace = 0.3 ,
256+ )
226257 fig .suptitle (
227258 f"kdevops Contribution Analysis{ title_suffix } " ,
228259 fontsize = 24 ,
229260 fontweight = "bold" ,
230- y = 0.98 ,
261+ y = 0.985 ,
231262 color = '#2c3e50'
232263 )
233264
@@ -236,7 +267,7 @@ def create_contribution_graphs(
236267 return None
237268
238269 # 1. Total Contributions Bar Chart
239- ax1 = plt . subplot ( 2 , 3 , 1 )
270+ ax1 = fig . add_subplot ( gs [ 0 , 0 ] )
240271 contributors = list (contributors_total .keys ())
241272 commits = list (contributors_total .values ())
242273
@@ -296,7 +327,7 @@ def create_contribution_graphs(
296327 )
297328
298329 # 2. Pie Chart of Contributions (top contributors only)
299- ax2 = plt . subplot ( 2 , 3 , 2 )
330+ ax2 = fig . add_subplot ( gs [ 0 , 1 ] )
300331 # Group smaller contributors together
301332 main_contributors = dict (list (contributors_total .items ())[:8 ]) # Top 8
302333 others_count = (
@@ -344,7 +375,7 @@ def create_contribution_graphs(
344375 )
345376
346377 # 3. Monthly Activity Heatmap
347- ax3 = plt . subplot ( 2 , 3 , 3 )
378+ ax3 = fig . add_subplot ( gs [ 0 , 2 ] )
348379
349380 # Get months that have activity
350381 active_months = set ()
@@ -427,7 +458,7 @@ def create_contribution_graphs(
427458 ax3 .set_title ("Monthly Activity Heatmap" , fontweight = "bold" , fontsize = 14 )
428459
429460 # 4. Monthly Timeline
430- ax4 = plt . subplot ( 2 , 3 , 4 )
461+ ax4 = fig . add_subplot ( gs [ 1 , 0 ] )
431462
432463 if active_months :
433464 # Calculate total commits per month
@@ -562,7 +593,7 @@ def create_contribution_graphs(
562593 ax4 .set_title ("Monthly Commit Activity" , fontweight = "bold" , fontsize = 14 )
563594
564595 # 5. Top Contributors Timeline
565- ax5 = plt . subplot ( 2 , 3 , 5 )
596+ ax5 = fig . add_subplot ( gs [ 1 , 1 ] )
566597
567598
568599 if active_months :
@@ -720,7 +751,7 @@ def create_contribution_graphs(
720751 )
721752
722753 # 6. Statistics Summary
723- ax6 = plt . subplot ( 2 , 3 , 6 )
754+ ax6 = fig . add_subplot ( gs [ 1 , 2 ] )
724755 ax6 .axis ("off" )
725756
726757 # Calculate stats
@@ -774,6 +805,11 @@ def create_contribution_graphs(
774805 else ("N/A" , 0 )
775806 )
776807
808+ generated_by_total = sum (generated_by_monthly .values ())
809+ generated_by_percentage = (
810+ (generated_by_total / total_commits ) * 100 if total_commits else 0
811+ )
812+
777813 # Add note about date anomaly if we detect future dates
778814 current_year = datetime .now ().year
779815 date_note = ""
@@ -794,6 +830,8 @@ def create_contribution_graphs(
794830
795831Active Months: { active_months_count }
796832
833+ Generated-by Tag Commits: { generated_by_total } ({ generated_by_percentage :.1f} % of total)
834+
797835Project: Linux Kernel DevOps Framework
798836Analysis Period: { period_desc }
799837Generated: { datetime .now ().strftime ('%Y-%m-%d %H:%M' )} { date_note }
@@ -816,7 +854,112 @@ def create_contribution_graphs(
816854 color = '#2c3e50'
817855 )
818856
819- plt .tight_layout (pad = 2.0 )
857+ # 7. Generated-by Tag Adoption Trend
858+ ax7 = fig .add_subplot (gs [2 , :])
859+ if generated_by_monthly :
860+ timeline_map = {k : v for k , v in generated_by_monthly .items ()}
861+ sorted_dates = sorted (timeline_map .keys ())
862+ min_date = sorted_dates [0 ]
863+ max_date = sorted_dates [- 1 ]
864+ date_range = pd .date_range (start = min_date , end = max_date , freq = "MS" )
865+ timeline_dates = [d .to_pydatetime () for d in date_range ]
866+ timeline_counts = [timeline_map .get (d .date (), 0 ) for d in date_range ]
867+
868+ ax7 .plot (
869+ timeline_dates ,
870+ timeline_counts ,
871+ marker = "o" ,
872+ linewidth = 3 ,
873+ markersize = 8 ,
874+ color = "#8e44ad" ,
875+ markerfacecolor = "#f39c12" ,
876+ markeredgecolor = "#2c3e50" ,
877+ markeredgewidth = 1.2 ,
878+ )
879+ if len (timeline_dates ) > 1 :
880+ ax7 .fill_between (
881+ timeline_dates ,
882+ timeline_counts ,
883+ alpha = 0.15 ,
884+ color = "#8e44ad" ,
885+ )
886+
887+ locator = mdates .AutoDateLocator ()
888+ formatter = mdates .ConciseDateFormatter (locator )
889+ ax7 .xaxis .set_major_locator (locator )
890+ ax7 .xaxis .set_major_formatter (formatter )
891+
892+ ax7 .set_title (
893+ "Generated-by Tag Adoption Over Time" ,
894+ fontweight = "bold" ,
895+ fontsize = 16 ,
896+ color = '#2c3e50' ,
897+ pad = 20 ,
898+ )
899+ ax7 .set_ylabel ("Commits with Generated-by" , fontsize = 12 , color = '#34495e' )
900+ ax7 .set_xlabel ("Month" , fontsize = 12 , color = '#34495e' )
901+ ax7 .spines ['top' ].set_visible (False )
902+ ax7 .spines ['right' ].set_visible (False )
903+ ax7 .grid (True , alpha = 0.3 )
904+ ax7 .set_ylim (bottom = 0 )
905+ ax7 .tick_params (axis = "x" , labelrotation = 45 )
906+
907+ ax7 .text (
908+ 0.01 ,
909+ 0.95 ,
910+ f"Total commits tagged: { generated_by_total } ({ generated_by_percentage :.1f} % of commits)" ,
911+ transform = ax7 .transAxes ,
912+ fontsize = 10 ,
913+ fontweight = "bold" ,
914+ color = "#2c3e50" ,
915+ bbox = dict (
916+ boxstyle = "round,pad=0.4" ,
917+ facecolor = "#f4ecf7" ,
918+ edgecolor = "#8e44ad" ,
919+ linewidth = 1.2 ,
920+ alpha = 0.6 ,
921+ ),
922+ verticalalignment = "top" ,
923+ )
924+
925+ if (year is None ) or (int (year ) == datetime .now ().year ):
926+ current_marker = datetime .now ()
927+ if timeline_dates and timeline_dates [0 ] <= current_marker <= timeline_dates [- 1 ]:
928+ ax7 .axvline (
929+ current_marker ,
930+ color = "#7f8c8d" ,
931+ linestyle = ":" ,
932+ linewidth = 1.2 ,
933+ alpha = 0.7 ,
934+ )
935+ else :
936+ ax7 .set_title (
937+ "Generated-by Tag Adoption Over Time" ,
938+ fontweight = "bold" ,
939+ fontsize = 16 ,
940+ color = "#2c3e50" ,
941+ pad = 20 ,
942+ )
943+ ax7 .spines ['top' ].set_visible (False )
944+ ax7 .spines ['right' ].set_visible (False )
945+ ax7 .set_ylabel ("Commits with Generated-by" , fontsize = 12 , color = "#34495e" )
946+ ax7 .set_xlabel ("Month" , fontsize = 12 , color = "#34495e" )
947+ ax7 .tick_params (axis = "x" , labelbottom = False )
948+ ax7 .tick_params (axis = "y" , labelleft = False )
949+ ax7 .set_ylim (0 , 1 )
950+ ax7 .text (
951+ 0.5 ,
952+ 0.5 ,
953+ "No Generated-by tag usage detected" ,
954+ ha = "center" ,
955+ va = "center" ,
956+ transform = ax7 .transAxes ,
957+ fontsize = 12 ,
958+ fontweight = "bold" ,
959+ color = "#7f8c8d" ,
960+ )
961+
962+ fig .tight_layout (rect = [0 , 0 , 1 , 0.97 ], pad = 2.5 )
820963
821964 # Ensure output directory exists and save the plots
822965 os .makedirs ("docs/contrib" , exist_ok = True )
@@ -897,9 +1040,14 @@ def main():
8971040
8981041 # Get contribution data
8991042 month_int = int (args .month ) if args .month else None
900- contributors_total , monthly_data , total_commits , period_desc , ai_boom_marker = (
901- get_contribution_data (args .year , month_int )
902- )
1043+ (
1044+ contributors_total ,
1045+ monthly_data ,
1046+ total_commits ,
1047+ period_desc ,
1048+ ai_boom_marker ,
1049+ generated_by_monthly ,
1050+ ) = get_contribution_data (args .year , month_int )
9031051
9041052 # Create visualization
9051053 fig = create_contribution_graphs (
@@ -908,6 +1056,7 @@ def main():
9081056 total_commits ,
9091057 period_desc ,
9101058 ai_boom_marker ,
1059+ generated_by_monthly ,
9111060 args .year ,
9121061 month_int ,
9131062 )
0 commit comments