Skip to content

Instantly share code, notes, and snippets.

@rstacruz
Created March 28, 2011 23:33
Show Gist options
  • Save rstacruz/891546 to your computer and use it in GitHub Desktop.
Save rstacruz/891546 to your computer and use it in GitHub Desktop.

GIT ChangeLOG tool - git clog

Makes pretty changelogs

Installation

chmod +x git-clog
cp git-clog /usr/local/bin  # Or whereever that's in your path

Usage

git clog        # Prints out the changelog from the previous git tag
git clog -a     # Same as above, but for everything (since time immemorial)
git clog -w     # Writes it to the history file (like CHANGELOG)
git clog -a -w

Sample output

https://github.com/sinefunc/sinatra-support/raw/master/HISTORY.md

#!/usr/bin/env ruby
# Usage:
# git clog # prints
# git clog -w # writes
module Clog
extend self
# Returns the latest tag (assumed to be the current version).
def version
v = `git tag | tail -n 1`.strip
v.empty? ? nil : v
end
# Returns the history file of the repo.
def history_file
Dir['*HISTORY*'].first || Dir['*CHANGE*'].first
end
# Returns a list of git tags.
def tags
`git tag`.strip.split("\n")
end
# Returns the first parent commit SHA1.
def tail
`git rev-list HEAD | tail -n 1`.strip
end
# List of possible ranges
def all_ranges
arr = Array.new
['HEAD', tags.reverse, tail].flatten.each_cons(2) { |to, from| arr << [to, from] }
arr
end
# Returns a list of changesets for the given tags.
# @return Array of hashes.
def changesets(range=:all)
case range
when :latest
[ { name: 'HEAD', changes: categories(changes(version, 'HEAD')) } ]
when :all
all_ranges.map do |(to, from)|
{ name: to, changes: categories(changes(from, to)) }
end
end
end
# Returns an array of commit messages.
def changes(from=version, to=nil)
log = `git log --pretty="format:%s" #{from||tail}..#{to}`
log.strip.split("\n").sort
end
# From a list of commit messages -> categories
def categories(list=changes)
cats = Hash.new { |h, k| h[k] = Array.new }
list.each { |line|
case line
when /^Merge branch/i then nil
when /^Add(?:ed)? (.*)$/i then cats['Added'] << $1
when /^Fix(?:ed)? (.*)$/i then cats['Fixed'] << $1
when /^Change(?:d)? (.*)$/i then cats['Changed'] << $1
when /^(?:Remove|Delete)(?:d)? (.*)$/i then cats['Removed'] << $1
when /^\+ (.*)$/i then cats['Added'] << $1
when /^\! (.*)$/ then cats['Fixed'] << $1
when /^\- (.*)$/i then cats['Removed'] << $1
when /^\~ (.*)$/i then cats['Changed'] << $1
when /^\. (.*)$/i then cats['Misc'] << $1
else cats['Changed'] << line
end
}
cats
end
def log(sets)
sets.map do |set|
# Heading name
h = set[:name]
h = "Latest version - #{Time.now.strftime('%b %d, %Y')}" if h == 'HEAD'
h + "\n" + '-'*h.size + "\n\n" +
set[:changes].map { |category, list|
"### #{category}:\n" +
list.map { |item| " * #{item}" }.join("\n")
}.join("\n\n")
end.join("\n\n")
end
def log_old
h = "Since #{version || 'the beginning'} - #{Time.now.strftime('%b %d, %Y')}"
"#{h}\n#{'-'*h.size}\n\n" +
categories.map { |category, list|
"### #{category}:\n" +
list.map { |item| " * #{item}" }.join("\n")
}.join("\n\n")
end
end
options = {
:all => (ARGV.delete('-a') || ARGV.delete('--all'))
}
if ARGV == ['--help'] || ARGV == ['-h']
puts "Usage: git clog [-a] [-w]"
puts
puts " -a, --all: See the changelog since time immemorial"
puts " -w, --write: Write to a history file"
exit
end
unless File.directory?('.git')
puts "Try this command inside a repo's root path."
exit
end
sets = if options[:all]
Clog.changesets(:all)
else
Clog.changesets(:latest)
end
if ARGV == []
puts Clog.log(sets)
$stderr << "\nUse 'git clog -w' to write.\n"
elsif ARGV == ['-w'] || ARGV == ['--write']
# Create the history file
system 'touch HISTORY.md' if Clog.history_file.nil?
# Concat
history = File.open(Clog.history_file) { |f| f.read }
output = Clog.log(sets)
output += "\n\n#{'-'*80}\n\n" + history unless history.empty?
# Write
File.open(Clog.history_file, 'w') { |f| f.write output }
if ENV['EDITOR']
exec "#{ENV['EDITOR']} #{Clog.history_file}"
else
puts "Done writing to #{Clog.history_file}."
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment