gitstats-ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. data/.gitignore +3 -0
  2. data/LICENSE +674 -0
  3. data/README.markdown +53 -0
  4. data/bin/gitstats +176 -0
  5. data/gitstats-ruby.gemspec +22 -0
  6. data/lib/gitstats.rb +18 -0
  7. data/lib/gitstats/author.rb +41 -0
  8. data/lib/gitstats/git.rb +158 -0
  9. data/lib/gitstats/renderer.rb +30 -0
  10. data/lib/gitstats/renderer/gnuplot.rb +150 -0
  11. data/lib/gitstats/renderer/haml.rb +70 -0
  12. data/lib/gitstats/renderer/sass.rb +33 -0
  13. data/lib/gitstats/statgen.rb +116 -0
  14. data/lib/gitstats/stats.rb +12 -0
  15. data/lib/gitstats/stats/commit.rb +48 -0
  16. data/lib/gitstats/stats/commit/author.rb +17 -0
  17. data/lib/gitstats/stats/commit/time.rb +92 -0
  18. data/lib/gitstats/stats/file.rb +15 -0
  19. data/lib/gitstats/stats/file/filetype.rb +14 -0
  20. data/lib/gitstats/yearmonth.rb +39 -0
  21. data/template/activity.haml +3 -0
  22. data/template/asc.gif +0 -0
  23. data/template/authors.haml +2 -0
  24. data/template/bg.gif +0 -0
  25. data/template/commits_per_hour.plot +14 -0
  26. data/template/commits_per_month.plot +15 -0
  27. data/template/commits_per_wday.plot +15 -0
  28. data/template/commits_per_year.plot +11 -0
  29. data/template/commits_per_yearmonth.plot +11 -0
  30. data/template/desc.gif +0 -0
  31. data/template/filechanges_by_yearmonth.plot +17 -0
  32. data/template/files.haml +2 -0
  33. data/template/files_by_yearmonth.plot +11 -0
  34. data/template/helpers/block.rb +26 -0
  35. data/template/helpers/config.rb +12 -0
  36. data/template/helpers/names.rb +76 -0
  37. data/template/helpers/utils.rb +16 -0
  38. data/template/index.haml +2 -0
  39. data/template/jquery.js +4 -0
  40. data/template/jquery.tablesorter.js +4 -0
  41. data/template/lastweeks.plot +14 -0
  42. data/template/layouts/default.haml +43 -0
  43. data/template/linechanges_by_yearmonth.plot +17 -0
  44. data/template/lines.haml +2 -0
  45. data/template/lines_by_yearmonth.plot +11 -0
  46. data/template/partials/authors.haml +30 -0
  47. data/template/partials/blockheader.haml +2 -0
  48. data/template/partials/blocktoc.haml +7 -0
  49. data/template/partials/commits_per_month.haml +27 -0
  50. data/template/partials/commits_per_year.haml +27 -0
  51. data/template/partials/commits_per_yearmonth.haml +27 -0
  52. data/template/partials/day_of_week.haml +28 -0
  53. data/template/partials/filechanges_by_yearmonth.haml +3 -0
  54. data/template/partials/files_by_yearmonth.haml +3 -0
  55. data/template/partials/filetypes.haml +23 -0
  56. data/template/partials/general.haml +34 -0
  57. data/template/partials/hour_of_day.haml +28 -0
  58. data/template/partials/hour_of_week.haml +24 -0
  59. data/template/partials/lastweeks.haml +24 -0
  60. data/template/partials/linechanges_by_yearmonth.haml +3 -0
  61. data/template/partials/lines_by_yearmonth.haml +3 -0
  62. data/template/partials/repos.haml +13 -0
  63. data/template/partials/top_authors_of_year.haml +30 -0
  64. data/template/partials/top_authors_of_yearmonth.haml +30 -0
  65. data/template/style.scss +132 -0
  66. metadata +187 -0
data/README.markdown ADDED
@@ -0,0 +1,53 @@
1
+ # gitstats-ruby
2
+
3
+ gitstats-ruby is a clone of http://gitstats.sourceforce.net written in ruby. It's written to support templates and should be easily extendable.
4
+
5
+ ## Installation
6
+
7
+ Right now you have to clone this repository until I upload a gem to rubygems.
8
+
9
+ git clone https://github.com/chrisistuff/gitstats-ruby.git
10
+
11
+ ## Getting started
12
+
13
+ The basic usage is quite simple. Just run gitstats with the git directory as parameter. If you want to generate stats of more than one repository just list them one after another. Note that this will generate only one statistic but consider the commits from all repositories.
14
+ Additionally you can also specify a name and a ref for each repository. To do this please use the following format: `<name>:<path to repository>:<ref>`.
15
+ For example:
16
+
17
+ gitstats gitstats-ruby:.:master
18
+
19
+ For more options please read sections about caching below or run `gitstats -h`.
20
+
21
+ ## Caching
22
+
23
+ gitstats-ruby implements two types of caches but one of them is just useful when developing new statistic classes. The one useful for the end-user is the stats-cache and the other one (for the devs) is the commit-cache.
24
+
25
+ ### stats-cache
26
+
27
+ The stats-cache caches the statistic objects used internally. This drastically improves the speed of incremental updates because only the new commits have to be taken into account. To activate this cache pass the `-c` command line flag. By default this creates the stats-cache file in the output directory. If you want to use another file you can specify it by using `--statcache <filename>`.
28
+
29
+ Please note that this cache can only be used when working on the same repositories as used in the previous run. Identification is done using the repository name or, if not specified, using the given (not relative!) path.
30
+
31
+ ### commit-cache
32
+
33
+ As already mentioned this cache is only useful if you want to develope new statistic classes. It works by caching the internal commit objects to a per repository file that can be reread when running again. This is especially useful if you experiment with big repositories (i.e. the linux kernel with ~275000 commits) where this cache is about twice as fast as the `git log` command used internally.
34
+
35
+ This cache can be activated with the `-C` command line flag. By default the commitcache is written into the output directory (./stats by default). If you want to use another directory you can specify it by using `--commitcache <directory name>`.
36
+
37
+ ## Examples
38
+
39
+ * [gitstats-ruby](http://chrisistuff.github.com/gitstats-ruby/gitstats-ruby/)
40
+ * [Linux](http://chrisistuff.github.com/gitstats-ruby/linux/)
41
+
42
+ ## Dependencies
43
+
44
+ * [HAML](http://haml-lang.com)
45
+ * [SASS](http://sass-lang.com)
46
+ * [Compass](http://compass-style.org)
47
+ * [Gnuplot (GEM)](http://rubygems.org/gems/gnuplot)
48
+ * [Gnuplot binary](http://www.gnuplot.info/)
49
+
50
+ ## License
51
+
52
+ See LICENSE
53
+
data/bin/gitstats ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'ftools'
5
+ require 'rubygems'
6
+
7
+ require 'haml'
8
+ require 'sass'
9
+ require 'compass'
10
+ require 'gnuplot'
11
+
12
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
13
+
14
+ require 'gitstats'
15
+
16
+ $options = {
17
+ :out => 'stats',
18
+ :template => nil,
19
+ :verbose => false,
20
+ :debug => false,
21
+ :quiet => false,
22
+ :cache => false,
23
+ :statcache => nil,
24
+ :commitcache => nil,
25
+ :commitcache_dir => nil,
26
+ :future => true,
27
+ :maxage => 0,
28
+ :withmail => false,
29
+ }
30
+
31
+ parser = OptionParser.new do |opts|
32
+ opts.banner = 'Usage: gitstats.rb [options] <[name1:]gitdir1[:ref1]> [<[name2:]gitdir2[:ref2]> ...]'
33
+
34
+ opts.on('-o', '--out=arg', 'output directory') do |arg|
35
+ $options[:out] = arg
36
+ end
37
+
38
+ opts.on('-t', '--template=arg', 'template directory') do |arg|
39
+ $options[:template] = arg
40
+ end
41
+
42
+ opts.on('-c', '--[no-]cache', 'use the statcache file') do |arg|
43
+ $options[:cache] = arg
44
+ end
45
+
46
+ opts.on('-C', '--[no-]commitcache', 'use the commit cache') do |arg|
47
+ $options[:commitcache] = arg
48
+ end
49
+
50
+ opts.on('-s', '--statcache=arg', 'statcache file to use') do |arg|
51
+ $options[:statcache] = arg
52
+ end
53
+
54
+ opts.on('--commitcache=arg', 'commit cache directory to use') do |arg|
55
+ $options[:commitcache_dir] = arg
56
+ end
57
+
58
+ opts.on('--[no-]future', 'count future commits') do |arg|
59
+ $options[:future] = arg
60
+ end
61
+
62
+ opts.on('-m', '--max-age=arg', Integer, 'set max age of commit in days') do |arg|
63
+ $options[:maxage] = arg
64
+ end
65
+
66
+ opts.on('--[no-]mail', 'include mail in author names') do |arg|
67
+ $options[:withmail] = arg
68
+ end
69
+
70
+ opts.on('-v', '--[no-]verbose', 'verbose mode') do |arg|
71
+ $options[:verbose] = arg
72
+ end
73
+
74
+ opts.on('-q', '--[no-]quiet', 'quiet mode') do |arg|
75
+ $options[:quiet] = arg
76
+ end
77
+
78
+ opts.on('-d', '--[no-]debug', 'print debug messages') do |arg|
79
+ $options[:debug] = arg
80
+ end
81
+
82
+ opts.on_tail('-h', '--help', 'this help') do
83
+ puts opts
84
+ exit 0
85
+ end
86
+ end
87
+
88
+ parser.parse!
89
+
90
+ if $options[:quiet] && $options[:verbose]
91
+ STDERR.puts 'cannot specify --quiet and --verbose at the same time!'
92
+ exit 1
93
+ end
94
+
95
+ $options[:statcache] = File.join($options[:out], '.statcache') if $options[:statcache].nil?
96
+ $options[:commitcache_dir] = $options[:out] if $options[:commitcache_dir].nil?
97
+ $options[:template] = File.expand_path(File.join(File.dirname(__FILE__), '..', 'template')) if $options[:template].nil?
98
+
99
+ FileUtils.mkdir_p($options[:out])
100
+
101
+ stat = nil
102
+ cache_loaded = false
103
+ if $options[:cache]
104
+ begin
105
+ puts 'trying to load cache ...' unless $options[:quiet]
106
+ stat = Marshal::load(IO::readlines($options[:statcache]).join(''))
107
+ stat.clear_repos
108
+ cache_loaded = true
109
+ rescue
110
+ end
111
+ end
112
+
113
+ if stat.nil?
114
+ if ARGV.empty?
115
+ puts parser
116
+ exit 1
117
+ end
118
+
119
+ stat = StatGen.new
120
+ end
121
+
122
+ if cache_loaded
123
+ if stat.include_mail != $options[:withmail]
124
+ puts 'cannot change --[no-]mail option when using statcache'
125
+ exit 1
126
+ end
127
+
128
+ if stat.future != $options[:future]
129
+ puts 'cannot change --[no-]future option when using statcache'
130
+ exit 1
131
+ end
132
+
133
+ if stat.maxage != $options[:maxage] * 24 * 60 * 60
134
+ puts 'cannot change --max-age option when using statcache'
135
+ exit 1
136
+ end
137
+ end
138
+
139
+ Author::include_mail = $options[:withmail]
140
+ stat.include_mail = $options[:withmail]
141
+ stat.verbose = $options[:verbose]
142
+ stat.debug = $options[:debug]
143
+ stat.quiet = $options[:quiet]
144
+ stat.future = $options[:future]
145
+ stat.maxage = $options[:maxage] * 24 * 60 * 60
146
+ stat.commitcache = $options[:commitcache] ? $options[:commitcache_dir] : nil
147
+
148
+ ARGV.each do |path|
149
+ name, path, ref = path.split(':')
150
+ path ||= name
151
+ ref ||= 'HEAD'
152
+ stat << [name, path, ref]
153
+ end
154
+
155
+ if cache_loaded
156
+ unless stat.check_repostate
157
+ puts 'cannot use cache when working on different repositories!'
158
+ exit 1
159
+ end
160
+ end
161
+
162
+ puts 'fetching statistics ...' unless $options[:quiet]
163
+ begin
164
+ stat.calc
165
+ ensure
166
+ if $options[:cache]
167
+ puts 'writing cache ...' unless $options[:quiet]
168
+ cache = Marshal::dump(stat)
169
+ File.new($options[:statcache], 'w').write(cache)
170
+ end
171
+ end
172
+
173
+ puts 'rendering ...' unless $options[:quiet]
174
+ renderer = Renderer.new($options[:template], $options[:out], $options[:verbose])
175
+ renderer.render(stat)
176
+
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'gitstats-ruby'
5
+ s.version = '1.0.0'
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ['Christoph Plank']
8
+ s.email = ['chrisistuff@gmail.com']
9
+ s.homepage = 'http://rubygems.org/gems/gitstats-ruby'
10
+ s.summary = %q{Generates statistics of git repositories}
11
+ s.description = %q{Generates statistics of git repositories like http://gitstats.sourceforge.net but with a more extendable and flexible backend system}
12
+ s.has_rdoc = false
13
+
14
+ s.add_dependency 'haml'
15
+ s.add_dependency 'sass'
16
+ s.add_dependency 'compass'
17
+ s.add_dependency 'gnuplot'
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ['lib']
22
+ end
data/lib/gitstats.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'gitstats/author'
2
+ require 'gitstats/yearmonth'
3
+ require 'gitstats/git'
4
+
5
+ require 'gitstats/stats'
6
+ require 'gitstats/stats/commit'
7
+ require 'gitstats/stats/commit/author'
8
+ require 'gitstats/stats/commit/time'
9
+ require 'gitstats/stats/file'
10
+ require 'gitstats/stats/file/filetype'
11
+
12
+ require 'gitstats/statgen'
13
+
14
+ require 'gitstats/renderer'
15
+ require 'gitstats/renderer/haml'
16
+ require 'gitstats/renderer/sass'
17
+ require 'gitstats/renderer/gnuplot'
18
+
@@ -0,0 +1,41 @@
1
+ class Author
2
+ include Comparable
3
+
4
+ def self.include_mail=(include_mail)
5
+ @@include_mail = include_mail
6
+ end
7
+
8
+ def self.include_mail
9
+ @@include_mail ||= false
10
+ @@include_mail
11
+ end
12
+
13
+ attr_reader :name
14
+ attr_reader :email
15
+
16
+ def initialize(name, email)
17
+ @name = name
18
+ @email = email
19
+ end
20
+
21
+ def <=>(b)
22
+ to_i <=> b.to_i
23
+ end
24
+
25
+ def to_s
26
+ if self.class.include_mail
27
+ "#{name} <#{email}>"
28
+ else
29
+ name
30
+ end
31
+ end
32
+
33
+ def eql?(b)
34
+ to_s.hash == b.to_s.hash
35
+ end
36
+
37
+ def hash
38
+ to_s.hash
39
+ end
40
+ end
41
+
@@ -0,0 +1,158 @@
1
+ class Git
2
+ attr_reader :name
3
+ attr_reader :base
4
+ attr_reader :ref
5
+
6
+ def initialize(name, base, ref = 'HEAD', debug = false, cachefile = nil)
7
+ @name = name
8
+ @base = base
9
+ @ref = ref
10
+ @debug = debug
11
+ @cachefile = cachefile
12
+ end
13
+
14
+ def open_cache
15
+ @cache = File.new(@cachefile, 'a')
16
+ end
17
+
18
+ def close_cache
19
+ unless @cache.nil?
20
+ @cache.close
21
+ @cache = nil
22
+ end
23
+ end
24
+
25
+ def write_cache(commit)
26
+ obj = Marshal.dump(commit)
27
+ raise "Object too large" if obj.size > 65535
28
+
29
+ str = ((obj.size >> 8) & 0xff).chr
30
+ str += (obj.size & 0xff).chr
31
+ str += obj
32
+
33
+ @cache.write(str)
34
+ @cache.flush
35
+ end
36
+
37
+ def read_cache
38
+ f = File.new(@cachefile)
39
+ while(!f.eof?)
40
+ tmp = f.read(2)
41
+ len = (tmp[0] << 8) + tmp[1]
42
+ obj = f.read(len)
43
+ raise "Read short object" if obj.size != len
44
+ yield Marshal.load(obj)
45
+ end
46
+ f.close
47
+ end
48
+
49
+ def get_commits(last = nil, &block)
50
+ if last.nil?
51
+ range = @ref
52
+ unless @cachefile.nil?
53
+ begin
54
+ read_cache do |commit|
55
+ block.call(commit)
56
+ last = commit
57
+ end
58
+ range = "#{last[:hash]}..#{@ref}"
59
+ rescue
60
+ end
61
+ end
62
+ else
63
+ range = "#{last}..#{@ref}"
64
+ end
65
+
66
+ open_cache unless @cachefile.nil?
67
+
68
+ commit = nil
69
+ sh("git log --reverse --summary --numstat --pretty=format:\"HEADER: %at %ai %H %T %aN <%aE>\" #{range}") do |line|
70
+ if line =~ /^HEADER:/
71
+ unless commit.nil?
72
+ write_cache(commit) unless @cachefile.nil?
73
+ block.call(commit)
74
+ end
75
+
76
+ parts = line.split(' ', 8)
77
+ parts.shift
78
+
79
+ commit = Hash.new
80
+ commit[:time] = Time.at(parts[0].to_i)
81
+ commit[:timezone] = parts[3]
82
+ commit[:hash] = parts[4]
83
+ commit[:tree] = parts[5]
84
+ name = nil
85
+ email = ''
86
+ match = /^(.+) <(.+)>$/.match(parts[6])
87
+ if match.nil?
88
+ name = parts[6]
89
+ else
90
+ name, email = match.captures
91
+ end
92
+ commit[:author] = Author.new(name, email)
93
+ commit[:files_added] = 0
94
+ commit[:files_deleted] = 0
95
+ commit[:lines_added] = 0
96
+ commit[:lines_deleted] = 0
97
+ elsif line == ''
98
+ write_cache(commit) unless @cachefile.nil?
99
+ block.call(commit)
100
+ commit = nil
101
+ elsif line =~ /^ /
102
+ if line =~ /^ create/
103
+ commit[:files_added] += 1
104
+ elsif line =~ /^ delete/
105
+ commit[:files_deleted] += 1
106
+ end
107
+ else
108
+ match = /^(\d+)\s+(\d+)/.match(line)
109
+ unless match.nil?
110
+ added, deleted = match.captures
111
+ commit[:lines_added] += added.to_i
112
+ commit[:lines_deleted] += deleted.to_i
113
+ end
114
+ end
115
+ end
116
+
117
+ unless commit.nil?
118
+ write_cache(commit) unless @cachefile.nil?
119
+ block.call(commit)
120
+ end
121
+
122
+ ensure
123
+ close_cache unless @cachefile.nil?
124
+ end
125
+
126
+ def get_files(ref = nil, &block)
127
+ ref ||= @ref
128
+
129
+ sh("git ls-tree -r -l #{ref}").split(/\n/).each do |line|
130
+ parts = line.split(/\s+/, 5)
131
+ next if parts[1] != 'blob'
132
+
133
+ file = Hash.new
134
+ file[:hash] = parts[2]
135
+ file[:size] = parts[3].to_i
136
+ file[:name] = parts[4]
137
+
138
+ block.call(file)
139
+ end
140
+ end
141
+
142
+ private
143
+ def sh(cmd, &block)
144
+ puts cmd if @debug
145
+ Dir.chdir(@base) do
146
+ if block.nil?
147
+ `#{cmd}`
148
+ else
149
+ IO.popen(cmd) do |io|
150
+ io.each_line do |line|
151
+ block.call(line.chomp)
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+