metricfu / metric_fu

A fist full of code metrics
http://metricfu.github.com/metric_fu
MIT License
627 stars 96 forks source link

Feature Request: Allow getting metrics for Old Code via Source Control #107

Open Ajedi32 opened 11 years ago

Ajedi32 commented 11 years ago

I recently came across this gem and ran it against my codebase. The tool came up with a lot of useful information, but right now the graphs it generated only have one data point. It would be nice if there was some way to build up these graphs with data from old code I have in source control.

Even something as simple as a command-line flag for specifying the commit date of the current state of the repo would be useful. That way I could manually jump to various points in my project's history and generate metrics for those commits.

robincurry commented 11 years ago

Cool. You may be in luck. I spent the weekend working on something along these lines because I needed the same thing.

bf4 commented 11 years ago

Also see #96

I've used something like this in capistrano to generate a formatted list of commits that match a certain pattern. You can use this or a variant of this to run metric_fu against certain select commits, with the only changes needed in the code, I think, being where it calls Time.now, which we'd replace with run_date or some such

    def current_time
      Time.now.strftime('%Y-%m-%d %H:%M')
    end

    # see http://stackoverflow.com/questions/1404796/how-to-get-the-latest-tag-name-in-current-branch-in-git
    # see https://www.kernel.org/pub/software/scm/git/docs/git-for-each-ref.html
    # see https://www.kernel.org/pub/software/scm/git/docs/git-describe.html
    # see https://www.kernel.org/pub/software/scm/git/docs/git-log.html
    def change_log
      last_deploy_tag = `git for-each-ref #{pattern} --sort=-taggerdate --format='%(objectname:short)' --count=1`.strip
      `git log --pretty='%d %s <%an>' --abbrev-commit --graph --decorate #{last_deploy_tag}..HEAD`.strip
    end

    # matches either tags starting with the stage name
    # or containing 'ruby' e.g.
    # refs/tags/cruby* or ref/tags/*ruby*
    def pattern
      "refs/tags/#{defined?(stage) ? stage : '*ruby'}*"
    end

    def current_hash
      `git describe`.strip
    end
bf4 commented 11 years ago

e.g. on metric_fu

git log --max-count=1 --after=2013-07-24 --before=2013-07-25 --date=iso --format="%H, %ai"
=> 8506d56ffe277b5cbda291b72a42330d7c719040, 2013-07-25 15:48:27 +0200

so or in a ruby script

require 'date'

today=Date.today.strftime('%Y%m%d')
from_date=Date.parse('2013-06-29')
until_date=Date.parse('2013-07-29')
(from_date..until_date).each{|date|
  git_log_cmd = "git log --max-count=1 --before=#{date} --after=#{date - 1} --format='%H'"
  execute_cmd = "metric_fu && mv tmp/metric_fu/_data/#{today}.yml tmp/metric_fu/_data/#{date.strftime('%Y%m%d')}.yml"
  puts "git_log_cmd: #{git_log_cmd}"
  hash = `#{git_log_cmd}`.to_s.strip
  unless hash == ''
    puts "#{date},#{hash}"
    puts "execute_cmd: #{execute_cmd}"
    `git reset --hard HEAD && git checkout #{hash} && #{execute_cmd} && git checkout -`
  end
}; nil
# 
# attempt in bash
# ruby -rdate -e "today=Date.today.strftime('%Y%m%d');from_date=Date.parse('2013-06-29');until_date=Date.parse('2013-07-29'); (from_date..until_date).each{|date| `git log --max-count=1 --before=#{date} --after=#{date - 1} --format='%H' | xargs git checkout && metric_fu && mv tmp/metric_fu/_data/#{today}.yml tmp/metric_fu/_data/#{date.strftime('%Y%m%d')}.yml && git checkout -}; nil"
robincurry commented 11 years ago

Yeah, I used git rev-list -n 1 --before=#{date} master where date is end of day on a given day - which gives you the last commit for that day.

Also, remember, you can use a formatter now to save with the appropriate filename:

execute_cmd = "metric_fu --format yaml --out _data/#{date.strftime('%Y%m%d')}.yml"

bf4 commented 11 years ago

Totally forgot about that, sweet

# require 'date'
# 
# today=Date.today.strftime('%Y%m%d')
# from_date=Date.parse('2013-06-29')
# until_date=Date.parse('2013-07-29')
# (from_date..until_date).each{|date|
#   git_log_cmd = "git log --max-count=1 --before=#{date} --after=#{date - 1} --format='%H'"
#   puts "git_log_cmd: #{git_log_cmd}"
#   hash = `#{git_log_cmd}`.to_s.strip
#   process_commit(hash, date.strftime('%Y%m%d'))
# }; nil
def pattern(tag_pattern=nil)
  ["refs/tags",tag_pattern].compact.join('/')
end
TagRange = Struct.new(:output) do
  def tag_name
   output.split(',')[1]
  end
  def date
    output.split(',')[0].gsub(/[^\d]/,'')
  end
end
# @return
def tag_range(count,tag_pattern=nil)
  hash_format = '%(objectname:short)'
  ymd_format = '%(taggerdate:short)'
  tag_name_format = '%(refname:short)'
  format = "#{ymd_format},#{tag_name_format}"
  ` git for-each-ref #{pattern(tag_pattern)} --sort=-taggerdate --format='#{format}' --count=#{count}`.split(/\n/).map(&:strip).map{|output|TagRange.new(output)}
end
# matches either tags starting with the stage name
# or containing 'ruby' e.g.
# refs/tags/cruby* or ref/tags/*ruby*
def process_commit(hash, date)
  execute_cmd = "metric_fu --format yaml --out _data/#{date}.yml"
  unless hash == ''
    message = "#{date},#{hash}"
    puts "BEGIN #{message}"
    puts "execute_cmd: #{execute_cmd}"
    p `git reset --hard origin/master && git checkout #{hash} && #{execute_cmd}; git checkout master`
    puts "END #{message}"
  end
end
tag_range(10,'v*').each do |range|
  process_commit(range.tag_name,range.date)
end
bf4 commented 11 years ago

bump @robincurry

robincurry commented 11 years ago

Yeah, I have what was an almost done feature branch for this, but haven't looked at in a bit. I suspect I'll have some rebase work to do to get it up to speed with all the work you've been doing.

bf4 commented 11 years ago

Most likely :)