Created
March 12, 2024 13:38
-
-
Save mtancoigne/71b8291c4500385fa7ecc3d5ee390450 to your computer and use it in GitHub Desktop.
Display commits per branch, day by day
This file contains 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
#!/usr/bin/env ruby | |
# frozen_string_literal: true | |
# Gems: | |
# gem install tty-option rugged skittlize activesupport pastel | |
require 'tty-option' | |
require 'rugged' | |
require 'skittlize' | |
require 'active_support/deprecator' # Required for active_support to work | |
require 'active_support/deprecation' # Required for active_support to work | |
require 'active_support/core_ext/integer/time' | |
require 'pastel' | |
require 'i18n' | |
# Ruby standard libs | |
require 'date' | |
module ExperimentsLabs | |
class GitCal | |
class Error < StandardError; end | |
def initialize | |
@pastel = Pastel.new | |
@repository = Rugged::Repository.discover Dir.pwd | |
@committer_email = @repository.default_signature[:email] | |
end | |
def show_calendar(branches: [], main_branch: nil, days: nil) | |
raise 'You should provide an array of branches' unless branches.is_a? Array | |
days = days ? days.to_i : nil | |
@branches = process_branches_args branches | |
@main_branch = main_branch | |
today = Time.new.beginning_of_day | |
@start_date = days ? (today - days.days) : Time.new.beginning_of_month | |
@days_to_show = days || today.day | |
list | |
render | |
end | |
private | |
def list # rubocop:disable Metrics/AbcSize, Metrics/MethodLength | |
@commits_per_day = {} | |
puts "Commits by #{@committer_email}, from #{@start_date.to_date} to now" | |
@branches.each do |branch, branch_commit_id| | |
commit_list = commits_for branch_commit_id | |
@days_to_show.times do |index| | |
day = (@start_date + index.days).to_date | |
day_string = day.to_s | |
@commits_per_day[day_string] ||= {} | |
commit_list.select { |commit| day == commit.committer[:time].to_date } | |
.each do |commit| | |
message = commit.message.split("\n").first | |
@commits_per_day[day_string][message] ||= [] | |
@commits_per_day[day_string][message].push branch | |
end | |
end | |
end | |
end | |
def commits_for(head_hash) | |
walker = Rugged::Walker.new @repository | |
walker.sorting Rugged::SORT_DATE | |
walker.push head_hash | |
walker.select do |commit| | |
commit.author[:time].after?(@start_date) && commit.author[:email] == @committer_email | |
end | |
end | |
def render # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity | |
# Rendering | |
@commits_per_day.each do |day, commits| | |
next if commits.empty? | |
puts @pastel.bold Date.parse(day).strftime('%v (%a)') | |
commits.keys.sort.each do |message| | |
branches_list = if @branches.size > 1 | |
commits[message] | |
# Ignore other branches when a commit is already on the main branch | |
.reject { |branch| @main_branch && commits[message].include?(@main_branch) && branch != @main_branch } | |
.map(&:skittlize) | |
.join(', ') | |
end | |
branches_list = branches_list ? "[#{branches_list}] " : '' | |
if message.match?(/^merge /i) | |
message = @pastel.decorate(message, :dim, :strikethrough) | |
else | |
nibbles = message.split(':') | |
nibbles[0] = nibbles[0].skittlize if nibbles.size > 1 | |
message = nibbles.join(':') | |
end | |
puts " #{branches_list}#{message}" | |
end | |
puts "\n" | |
end | |
end | |
# Checks branches argument against repository branches and returns a hash | |
# with the git object id | |
# | |
# @return [Hash] - Like <branch>: <branch_commit_id> | |
def process_branches_args(branches) | |
return { @repository.head.name.sub(%r{^refs/heads/}, '').to_s => @repository.head.target_id } if branches.empty? | |
branches = {} | |
branches.each do |name| | |
branch = repository.branches[name] | |
raise GitCal::Error, "Invalid branch #{name}" unless branch | |
branches[name] = branch.target_id | |
end | |
branches | |
end | |
end | |
class GitCalCli | |
include TTY::Option | |
usage do | |
program 'git-cal' | |
desc 'Display commits per branch, day by day' | |
no_command | |
example <<~TXT | |
git cal # list commits of the current branch | |
git cal branch1, branch2 # list commits for the given branches | |
TXT | |
end | |
argument :branches do | |
optional | |
arity zero_or_more | |
default [] | |
desc 'Branch to use in listing' | |
end | |
option :main do | |
optional | |
long '--main string' | |
short '-m' | |
desc 'Main branch: commits present in this branch will be hidden from others' | |
end | |
option :days do | |
optional | |
long '--days int' | |
short '-d' | |
desc 'Optional amount of days to use, instead of the beginning of month' | |
validate(/^\d+$/) | |
end | |
flag :help do | |
short '-h' | |
long '--help' | |
desc 'Print usage' | |
end | |
def run | |
handle_help | |
cal = GitCal.new | |
cal.show_calendar branches: params[:branches], | |
main_branch: params[:main], | |
days: params[:days] | |
rescue GitCal::Error => e | |
puts e | |
exit 1 | |
end | |
private | |
def handle_help | |
return unless params[:help] | |
print help | |
exit 0 | |
end | |
end | |
end | |
cli = ExperimentsLabs::GitCalCli.new | |
cli.parse | |
cli.run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment