Created
April 16, 2019 09:53
-
-
Save kbilsted/1fc05e68241cc9e87e2b446d740bcbae to your computer and use it in GitHub Desktop.
A simple program to parse git logs and produces change statistics grouped by month
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Globalization; | |
using System.IO; | |
using System.Linq; | |
namespace gitafterstat | |
{ | |
/// <summary> | |
/// A simple program to parse git logs and produces change statistics grouped by month | |
/// To run e.g. use | |
/// <code>C:\src\MyProject> git log --pretty=format:nyny%cI -u --no-color -U0 --full-history --date-order --no-merges --reverse -p | gitafterstat.exe</code> | |
/// | |
/// notes | |
/// * the cmd is much faster than running in powershell | |
/// * there is a delimiter 'nyny' in the above which is hard-coded into the parser | |
/// </summary> | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Dictionary<DateTime, List<Commit>> changes = null; | |
Console.WriteLine(""); | |
Time("Reading input", () => { changes = GitParser.Parse(Console.OpenStandardInput()); }); | |
Time("Filter", () => | |
{ | |
foreach (var commits in changes) | |
foreach (var commit in commits.Value) | |
commit.Changes = commit.Changes.Where(x => x.Fileextension == "cs" || x.Fileextension == "fs").ToList(); | |
changes = changes.Where(x => x.Value.Any()).ToDictionary(x => x.Key, x => x.Value); | |
}); | |
Console.WriteLine(""); | |
Console.WriteLine("CSV output"); | |
Console.WriteLine("----------"); | |
Console.WriteLine("date,delta,total,commits"); | |
var previousDelta = 0; | |
var g = changes.GroupBy(x => FirstInMonth(x.Key)); | |
foreach (var month in g) | |
{ | |
var sumDelta = month.Sum(x => x.Value.Sum(f => f.TotalDelta())); | |
previousDelta += sumDelta; | |
var commits = month.Sum(x => x.Value.Count); | |
Console.WriteLine($"{month.Key.ToShortDateString()}, {sumDelta}, {previousDelta}, {commits}"); | |
} | |
} | |
static DateTime FirstInMonth(DateTime d) => new DateTime(d.Year, d.Month, 1); | |
static void Time(string title, Action a) | |
{ | |
var s = Stopwatch.StartNew(); | |
a(); | |
Console.WriteLine($"{title}: {s.Elapsed.ToString()}"); | |
} | |
} | |
class GitParser | |
{ | |
const string Delimiter = "nyny"; | |
const char FirstDelimiter = 'n'; | |
public static Dictionary<DateTime, List<Commit>> Parse(Stream s) | |
{ | |
var changes = new Dictionary<DateTime, List<Commit>>(); | |
var top = Console.CursorTop; | |
using (var stream = new StreamReader(s)) | |
{ | |
Commit commit = null; | |
FileChange fileChange = null; | |
while (!stream.EndOfStream) | |
{ | |
var line = stream.ReadLine(); | |
if (line.Length == 0) | |
continue; | |
switch (line[0]) | |
{ | |
case '-': | |
if (!line.StartsWith("---", StringComparison.Ordinal)) | |
fileChange.DeltaLines--; | |
break; | |
case '+': | |
if (!line.StartsWith("+++", StringComparison.Ordinal)) | |
fileChange.DeltaLines++; | |
break; | |
case FirstDelimiter: | |
if (line.StartsWith(Delimiter, StringComparison.Ordinal)) | |
{ | |
var date = GetDate(line); | |
if (!changes.ContainsKey(date)) | |
{ | |
changes[date] = new List<Commit>(); | |
Console.SetCursorPosition(0, top); | |
Console.Write(date); | |
} | |
commit = new Commit(); | |
changes[date].Add(commit); | |
} | |
break; | |
case 'd': | |
const string diffGit = "diff --git "; | |
if (line.StartsWith(diffGit, StringComparison.Ordinal)) | |
{ | |
var posEnd = line.IndexOf(' ', diffGit.Length); | |
var posDot = line.LastIndexOf('.', posEnd - 1, 7) + 1; | |
var ext = line.Substring(posDot, posEnd - posDot); | |
fileChange = new FileChange(); | |
fileChange.Fileextension = string.Intern(ext); | |
commit.Changes.Add(fileChange); | |
} | |
break; | |
} | |
} | |
} | |
return changes; | |
} | |
private static DateTime GetDate(string line) | |
{ | |
var format = "yyyy-MM-dd"; | |
var datepart = line.Substring(Delimiter.Length, format.Length); | |
var date = DateTime.ParseExact(datepart, format, CultureInfo.InvariantCulture); | |
return date; | |
} | |
} | |
class FileChange | |
{ | |
public int DeltaLines; | |
public string Fileextension; | |
} | |
class Commit | |
{ | |
public List<FileChange> Changes = new List<FileChange>(); | |
public int TotalDelta() => Changes.Sum(x => x.DeltaLines); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment