codefumes_harvester 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.1.0 / 09-08-09
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,17 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/harvest_repo_metrics
7
+ lib/codefumes_harvester.rb
8
+ lib/codefumes_harvester/errors.rb
9
+ lib/codefumes_harvester/harvester.rb
10
+ lib/codefumes_harvester/quick_metric.rb
11
+ lib/codefumes_harvester/source_control.rb
12
+ lib/harvest_repo_metrics/cli.rb
13
+ spec/codometer_harvester/harvester_spec.rb
14
+ spec/codometer_harvester/source_control_spec.rb
15
+ spec/harvest_repo_metrics_cli_spec.rb
16
+ spec/spec_helper.rb
17
+ tasks/codefumes.rake
data/PostInstall.txt ADDED
@@ -0,0 +1,7 @@
1
+
2
+ For more information on codometer-harvester, see http://codometer-harvester.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
data/README.txt ADDED
@@ -0,0 +1,114 @@
1
+ = codefumes-harvester
2
+
3
+ * http://codefumes.com
4
+ * http://cf-harvester.rubyforge.org/rdoc
5
+
6
+ == DESCRIPTION:
7
+
8
+ CodeFumesHarvester provides a set of high-level tools for gathering
9
+ history and common metrics from a local repository and sending them to
10
+ CodeFumes.com[http://codefumes.com].
11
+
12
+ == FEATURES/PROBLEMS:
13
+
14
+ === Features
15
+
16
+ * Executable +harvest_repo_metrics+ script which gathers a repository's
17
+ commit lineage
18
+ * Rake tasks for grabbing content from metric_fu (coverage,
19
+ lines of 'code code' vs 'test code' ratio).
20
+ * QuickMetric class which can be used to link a custom attribute to the
21
+ current repository commit identifier of a code base to a CodeFumes
22
+ project. This makes it trivial to send up custom attributes from
23
+ scripts, rake tasks, or even the console, if desired.
24
+
25
+ === Problems / Things to Note
26
+ * The only supported SCM right now is git. We know this sucks. Adding
27
+ in other SCMs shouldn't be hard, and is planned. It's just not done
28
+ yet.
29
+
30
+ == SYNOPSIS:
31
+
32
+ === Associating a repository to a CodeFumes.com project
33
+
34
+ # Use +harvest_repo_metrics+ to create a project on CodeFumes.com
35
+ # In your terminal (not IRB):
36
+
37
+ # If you don't care what the public key of your project is:
38
+ $ harvest_repo_metrics
39
+ => results in a project URI similar to: http://codefumes.com/p/uPnf
40
+
41
+ # If you want to customize the public_key of your project:
42
+ $ harvest_repo_metrics -k my_custom_key
43
+ => results in a project URI of: http://codefumes.com/p/my_custom_key
44
+ (assuming it was not already taken)
45
+
46
+ # If you are on another machine and want to update that project,
47
+ # you can pass both the public & private keys of the project for
48
+ # authentication:
49
+ $ harvest_repo_metrics -k my_custom_key -a some-generated-private-key
50
+
51
+
52
+ === Sending up custom metrics using QuickMetric
53
+
54
+ # Send up your own data and associate it with the current commit
55
+ # (HEAD, in git parlance) of your current directory. This makes the
56
+ # (bold) assumption that the repository is already set up as a CodeFumes
57
+ # project, and that you want to link the data provided to the current
58
+ # commit only (think 'rake task' and 'build server').
59
+ #
60
+ # An IRB example:
61
+ > require 'codefumes_harvester'
62
+ > QuickMetric.save(:name_of_metric => "some value")
63
+ => true # returns +false+ if it failed
64
+ ...that's all
65
+
66
+ === Sending up coverage info from metric_fu output
67
+ # Assuming you have run 'rake metrics:all'
68
+ $ rake cf:metric_fu:coverage
69
+
70
+ === Sending up lines of code code / lines of test code ratio
71
+
72
+ # This is only on Rails projects, I believe
73
+ $ rake cf:metric_fu:loc_lot_stats
74
+
75
+
76
+ == REQUIREMENTS:
77
+
78
+ * +codefumes+ gem
79
+
80
+ == INSTALL:
81
+
82
+ From RubyForge:
83
+
84
+ sudo gem install codefumes-harvester
85
+
86
+ Or, from Github:
87
+
88
+ sudo gem install cosyn-codefumes-harvester
89
+
90
+
91
+ == LICENSE:
92
+
93
+ (The MIT License)
94
+
95
+ Copyright (c) 2009 FIXME full name
96
+
97
+ Permission is hereby granted, free of charge, to any person obtaining
98
+ a copy of this software and associated documentation files (the
99
+ 'Software'), to deal in the Software without restriction, including
100
+ without limitation the rights to use, copy, modify, merge, publish,
101
+ distribute, sublicense, and/or sell copies of the Software, and to
102
+ permit persons to whom the Software is furnished to do so, subject to
103
+ the following conditions:
104
+
105
+ The above copyright notice and this permission notice shall be
106
+ included in all copies or substantial portions of the Software.
107
+
108
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
109
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
110
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
111
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
112
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
113
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
114
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ %w[rubygems rake rake/clean fileutils rubigen hoe].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/codefumes_harvester'
3
+
4
+ begin
5
+ require "hanna/rdoctask"
6
+ rescue LoadError
7
+ require 'rake/rdoctask'
8
+ end
9
+
10
+ # Load in the harvester ane metric_fu gems if available so we can collect metrics
11
+ begin
12
+ require "metric_fu"
13
+ rescue LoadError
14
+ end
15
+
16
+ $hoe = Hoe.spec('codefumes_harvester') do |p|
17
+ p.developer('Cosyn Technologies', 'tom.kersten@cosyntech.com')
18
+ p.rubyforge_name = 'codefumes'
19
+ p.extra_deps = [
20
+ ['codefumes','>= 0.1.0'],
21
+ ['mojombo-grit','>= 1.1.1']
22
+ ]
23
+ p.extra_dev_deps = [
24
+ ['mojombo-chronic', ">= 0.3.0"],
25
+ ['jscruggs-metric_fu', ">= 1.1.5"]
26
+ ]
27
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
28
+ #path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
29
+ #p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
30
+ p.remote_rdoc_dir = ''
31
+ p.rsync_args = '-av --delete --ignore-errors'
32
+ end
33
+
34
+ # load /tasks/*.rake, except for the codefumes.rake file, that will be
35
+ # done in lib/codefumes_harvester.rb (the task_defined? method didn't seem
36
+ # to catch it)
37
+ # TODO: Find out what is/was being done wrong that this is necessary
38
+ Dir['tasks/**/*.rake'].each { |t| load t unless t[/codefumes.rake/]}
39
+
40
+ task :default => [:spec]
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2009-9-2.
4
+ # Copyright (c) 2009. All rights reserved.
5
+
6
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/codefumes_harvester")
7
+
8
+ require "harvest_repo_metrics/cli"
9
+
10
+ HarvestRepoMetric::CLI.execute(STDOUT, ARGV)
@@ -0,0 +1,22 @@
1
+ require 'grit'
2
+ require 'rake'
3
+ require 'codefumes'
4
+ require 'codefumes_harvester/harvester'
5
+ require 'codefumes_harvester/source_control'
6
+ require 'codefumes_harvester/errors'
7
+ require 'codefumes_harvester/quick_metric'
8
+
9
+ include CodeFumesHarvester
10
+
11
+ module CodeFumesHarvester
12
+ VERSION = '0.1.0'
13
+ LIB_ROOT = File.dirname(__FILE__)
14
+ end
15
+
16
+ # Shamelessly borrowed from metric_fu setup (http://metric-fu.rubyforge.org/)
17
+ unless Rake::Task.task_defined? "cf:store_repo_metrics"
18
+ # Load the rakefile so users of the gem get the default cf tasks
19
+ # This makes a rash assumption that if you have the 'store_repo_metrics'
20
+ # task, all others will be defined...and that if you don't, you want them all.
21
+ load File.join(CodeFumesHarvester::LIB_ROOT, '..', 'tasks', 'codefumes.rake')
22
+ end
@@ -0,0 +1,6 @@
1
+ module CodeFumesHarvester
2
+ module Errors #:nodoc:
3
+ class UnsupportedScmToolError < StandardError #:nodoc:
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,110 @@
1
+ module CodeFumesHarvester
2
+ # Simple class responsible for creating a project on the CodeFumes
3
+ # website, storing project information in the CodeFumes config file,
4
+ # and synchronizing a repository's commit history with CodeFumes.com.
5
+ #
6
+ # NOTE: Technically this can be used by anything (obviously), but it
7
+ # was written with the intention of being used with the
8
+ # +harvest_repo_metrics+ script, and is essentially geared for that
9
+ # scenario.
10
+ class Harvester
11
+ attr_reader :path
12
+ DEFAULT_PATH = './' #:nodoc:
13
+
14
+ # Accepts the following options:
15
+ # * +:path+ - the path of the repository to gather information from
16
+ # (Defaults to './').
17
+ # * +:public_key+ - Sets the public key of the project. This
18
+ # property will be read from the CodeFumes config file if on
19
+ # exists for the repository supplied at +:path+.
20
+ # * +:private_key+ - Sets the private key of the project. This
21
+ # property will be read from the CodeFumes config file if on
22
+ # exists for the repository supplied at +:path+.
23
+ # * +:name+ - Sets the name of the project on the CodeFumes site
24
+ def initialize(passed_in_options = {})
25
+ options = passed_in_options.dup
26
+ @path = File.expand_path(options.delete(:path) || DEFAULT_PATH)
27
+ @repository = initialize_repository
28
+ options.merge!(:public_key => options[:public_key] || @repository.public_key)
29
+ options.merge!(:private_key => options[:private_key] || @repository.private_key)
30
+ @project = initialize_project(options)
31
+ end
32
+
33
+ # Creates or updates a project information on the CodeFumes site,
34
+ # synchronizes the repository's commit history, and prints the
35
+ # results to STDOUT.
36
+ #
37
+ # Returns +true+ if the process succeeded.
38
+ #
39
+ # Returns +false+ if the process failed.
40
+ def publish_data!
41
+ if @project.save
42
+ store_public_key_in_repository
43
+ update_codefumes_config_file
44
+ payload_results = generate_and_save_payload
45
+ if payload_results.nil?
46
+ puts "Local repository is in sync with server. No updates posted."
47
+ else
48
+ puts "Successfully saved #{payload_results[:successful_count]} of #{payload_results[:total_count]} payloads."
49
+ end
50
+ puts "Exiting."
51
+ puts
52
+ true
53
+ else
54
+ false
55
+ end
56
+ end
57
+
58
+ # Returns the CodeFumes public key of the project that is located
59
+ # at the supplied path.
60
+ def public_key
61
+ @project.public_key
62
+ end
63
+
64
+ # Returns the CodeFumes private key of the project that is located
65
+ # at the supplied path.
66
+ def private_key
67
+ @project.private_key
68
+ end
69
+
70
+ # Returns the CodeFumes 'short uri' of the project that is located
71
+ # at the supplied path. The 'short uri' is similar to a Tiny URL for
72
+ # a project.
73
+ def short_uri
74
+ @project.short_uri
75
+ end
76
+
77
+ private
78
+ def initialize_repository
79
+ begin
80
+ SourceControl.new(@path)
81
+ rescue Grit::InvalidGitRepositoryError
82
+ raise Errors::UnsupportedScmToolError
83
+ end
84
+ end
85
+
86
+ def initialize_project(options = {})
87
+ options = CodeFumes::ConfigFile.options_for_project(options[:public_key]).merge(options)
88
+ CodeFumes::Project.new(options)
89
+ end
90
+
91
+ def store_public_key_in_repository
92
+ @repository.store_public_key(@project.public_key)
93
+ end
94
+
95
+ def update_codefumes_config_file
96
+ CodeFumes::ConfigFile.save_project(@project)
97
+ end
98
+
99
+ def generate_and_save_payload
100
+ payload = @repository.payload(CodeFumes::Commit.latest_identifier(public_key), "HEAD")
101
+ if payload.empty?
102
+ nil
103
+ else
104
+ payloads = CodeFumes::Payload.prepare(:public_key => public_key, :private_key => private_key, :content => payload)
105
+ successful_requests = payloads.select {|payload| payload.save == true}
106
+ {:successful_count => successful_requests.size, :total_count => payloads.size}
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,19 @@
1
+ module CodeFumesHarvester
2
+ class QuickMetric
3
+ # Associates a Hash of +custom_attributes+ with the current commit
4
+ # identifier of the repository located at +repository_path+.
5
+ #
6
+ # Returns +true+ if the request succeeded.
7
+ #
8
+ # Returns +false+ if the request failed.
9
+ def self.save(custom_attributes, repository_path = './')
10
+ repo = SourceControl.new(repository_path)
11
+ commit = {:identifier => repo.local_revision_identifier,
12
+ :custom_attributes => custom_attributes
13
+ }
14
+ content = {:commits => [commit]}
15
+ payload_set = CodeFumes::Payload.prepare(:public_key => repo.public_key, :private_key => repo.private_key, :content => content)
16
+ payload_set.reject {|payload| payload.save}.empty?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,132 @@
1
+ module CodeFumesHarvester
2
+ # Defines the contract between CodeFumes and any local source control
3
+ # management system (SCM).
4
+ #
5
+ # *NOTE:* Git is currently the only supported SCM. We look
6
+ # forward to changing this soon.
7
+ class SourceControl
8
+ SUPPORTED_SCMS_AND_DETECTORS = {:git => '.git'} #:nodoc:
9
+
10
+ # Sets up a SourceControl object to read content from the repository
11
+ # located at +path+.
12
+ def initialize(path)
13
+ @repository = Grit::Repo.new(path)
14
+ end
15
+
16
+ # Returns a serialized Hash containing a single +:commits+ key
17
+ # associated with an Array of serialized commit information, ready
18
+ # to be sent to the CodeFumes service.
19
+ def payload_between(from = initial_commit_identifier, to = "HEAD")
20
+ start_commit = from || initial_commit_identifier
21
+ end_commit = to || "HEAD"
22
+ new_commits = commits_between(start_commit, end_commit)
23
+ new_commits.empty? ? {} : {:commits => new_commits}
24
+ end
25
+ alias :payload :payload_between
26
+
27
+ # Returns the first commit identifier of a repository's history.
28
+ def initial_commit_identifier
29
+ initial_commit.sha
30
+ end
31
+
32
+ # Returns an array of 'symbolized' executable names for all
33
+ # supported SCMs.
34
+ #
35
+ # The names are returned as symbols.
36
+ def self.supported_systems
37
+ SUPPORTED_SCMS_AND_DETECTORS.keys
38
+ end
39
+
40
+ # Accepts command-line executable name of SCM and returns whether it
41
+ # is a supported SCM or not. +tool_cli_name+ should be the
42
+ # name of the executable, not the 'full name' of the application
43
+ # (ex: 'svn' not 'subversion').
44
+ #
45
+ # Returns +true+ if the SCM is supported
46
+ #
47
+ # Returns +false+ if the SCM is not supported.
48
+ def self.supported_system?(tool_cli_name)
49
+ SUPPORTED_SCMS_AND_DETECTORS.keys.include?(tool_cli_name.to_sym)
50
+ end
51
+
52
+ # Stores the public_key of the project associated with the
53
+ # underlying local repository. This will not be necessary
54
+ # with all SCMs.
55
+ #
56
+ # For example, in a git repository, this method will store a
57
+ # +codefumes+ key in the +.git/config+ file. This value can be used
58
+ # as a lookup key for other tools to use in conjunction with the
59
+ # CodeFumes config file (see CodeFumes::Config) to interact with a
60
+ # CodeFumes project.
61
+ def store_public_key(public_key)
62
+ @repository.config["codefumes.public-key"] = public_key
63
+ end
64
+
65
+ # Removes any association to the CodeFumes service which would have
66
+ # been added using the +store_public_key+ method.
67
+ #
68
+ # This method does not remove anything from the user's global
69
+ # CodeFumes config file.
70
+ def unlink_from_codefumes!
71
+ @repository.git.config({}, "--remove-section", "codefumes")
72
+ end
73
+
74
+ # Returns the public key of the project associated with this
75
+ # repository.
76
+ def public_key
77
+ @repository.config["codefumes.public-key"]
78
+ end
79
+
80
+ # Returns the private key of the project assciated with this
81
+ # repository.
82
+ def private_key
83
+ CodeFumes::ConfigFile.options_for_project(public_key)[:private_key]
84
+ end
85
+
86
+ # Returns the current revision identifier of the underlying
87
+ # repository ('HEAD' of the supplied branch in git parlance).
88
+ def local_revision_identifier(branch_name = "master")
89
+ raise ArgumentError, "nil branch name supplied" if branch_name.nil?
90
+ @repository.get_head(branch_name).commit.sha
91
+ end
92
+
93
+ # Returns the full path of the underlying repository.
94
+ def path
95
+ @repository.path
96
+ end
97
+
98
+ private
99
+ def initial_commit
100
+ @repository.log.last
101
+ end
102
+
103
+ # Returns Array of serialized commit data. Each item in the Array
104
+ # contains attributes of a single commit.
105
+ def commits_between(from, to, including_from_commit = false)
106
+ commit_list = @repository.commits_between(from,to)
107
+
108
+ if including_from_commit == true || from == initial_commit_identifier
109
+ commit_list = [initial_commit] + commit_list
110
+ end
111
+
112
+ commit_list.map do |commit|
113
+ {
114
+ :identifier => commit.sha,
115
+ :author_name => commit.author.name,
116
+ :author_email => commit.author.email,
117
+ :committer_name => commit.committer.name,
118
+ :committer_email => commit.committer.email,
119
+ :authored_at => commit.authored_date,
120
+ :committed_at => commit.committed_date,
121
+ :message => commit.message,
122
+ :short_message => commit.short_message,
123
+ :parent_identifiers => commit.parents.map(&:sha).join(','),
124
+ :line_additions => commit.stats.additions,
125
+ :line_deletions => commit.stats.deletions,
126
+ :line_total => commit.stats.deletions,
127
+ :affected_file_count => commit.stats.files.count
128
+ }
129
+ end.reverse
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,63 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'optparse'
5
+
6
+ module HarvestRepoMetric
7
+ # Class by the +harvest_metric+ script for command line parsing and
8
+ # delegation to the Harvester class.
9
+ class CLI
10
+ include CodeFumesHarvester
11
+
12
+ # Parses command-line options passed into +harvest_repo_metrics+
13
+ # and sends data collected to CodeFumes website.
14
+ def self.execute(stdout, arguments=[])
15
+ parse_command_line_options!(stdout, arguments)
16
+ harvester = Harvester.new(@options)
17
+
18
+ if harvester.publish_data!
19
+ stdout.puts "Project saved to CodeFumes.com. Visit #{harvester.short_uri}"
20
+ else
21
+ stdout.puts "Error saving project...please try again or file a bug report."
22
+ exit(1)
23
+ end
24
+ end
25
+
26
+ private
27
+ def self.parse_command_line_options!(stdout, cl_arguments)
28
+ @options = {}
29
+ begin
30
+ OptionParser.new do |opts|
31
+ opts.banner = <<-BANNER.gsub(/^ /,'')
32
+ Tool used to upload code & repository statistics to
33
+ the CodeFumes website.
34
+
35
+ Usage: #{File.basename($0)} [options]
36
+
37
+ Options are:
38
+ BANNER
39
+ opts.separator ""
40
+ opts.on("-k", "--project_key [PROJECT_KEY]",
41
+ "The public CodeFumes API key for this project."
42
+ ) {|public_key| @options[:public_key] = public_key}
43
+ opts.on("-a", "--access_key [PROJECT_PRIVATE_KEY]",
44
+ "The private CodeFumes API access key for this project."
45
+ ) {|private_key| @options[:private_key] = private_key}
46
+ opts.on("-p", "--path [REPOSITORY_PATH]",
47
+ "The local path to the directory containing this project's code."
48
+ ) {|path| @options[:path] = Dir.new(File.expand_path(path)).path}
49
+ opts.on("-h", "--help",
50
+ "Show this help message."
51
+ ) { stdout.puts opts; exit }
52
+ opts.on("-n", "--name [PROJECT_NAME]",
53
+ "Name of project."
54
+ ) {|name| @options[:name] = name}
55
+ opts.parse!(cl_arguments)
56
+ end
57
+ rescue Errno::ENOTDIR
58
+ stdout.puts "Path supplied is not a directory. Please supply a path to a directory containing your project."
59
+ exit
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Harvester do
4
+ before(:each) do
5
+ this_files_dir = File.dirname(__FILE__)
6
+ @no_scm_path = "#{this_files_dir}/../fixtures/sample_project_dirs/no_scm"
7
+ @git_scm_path = "#{this_files_dir}/../fixtures/sample_project_dirs/git_repository"
8
+ end
9
+
10
+ describe "initialization" do
11
+ context "with a path to a directory that is using the git SCM tool" do
12
+ before(:each) do
13
+ @repository = Grit::Repo.new(@git_scm_path)
14
+ @project = CodeFumes::Project.new
15
+ Grit::Repo.stub!(:new).with(File.expand_path(@git_scm_path)).and_return(@repository)
16
+ CodeFumes::Project.stub!(:new).and_return(@project)
17
+ end
18
+
19
+ def initialize_with_git
20
+ Harvester.new(:path => @git_scm_path)
21
+ end
22
+
23
+ it "creates a new Grit::Repo instance" do
24
+ Grit::Repo.should_receive(:new).with(File.expand_path(@git_scm_path)).and_return(@repository)
25
+ initialize_with_git
26
+ end
27
+
28
+ it "creates a new CodeFumes::Project instance" do
29
+ CodeFumes::Project.should_receive(:new).and_return(@project)
30
+ initialize_with_git
31
+ end
32
+ end
33
+
34
+ context "with a path to a directory that is not using a supported SCM" do
35
+ it "raises an error" do
36
+ lambda {Harvester.new(:path => @no_scm_path)}.should raise_error(Errors::UnsupportedScmToolError)
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "path" do
42
+ it "returns the full path to a project" do
43
+ harvester = Harvester.new(:path => @git_scm_path)
44
+ harvester.path.should == File.expand_path(@git_scm_path)
45
+ end
46
+ end
47
+
48
+ describe "publish_data!" do
49
+ before(:each) do
50
+ @harvester = Harvester.new(:path => @git_scm_path)
51
+ @payload = mock("CodeFumes::Payload instance", :save => true)
52
+ CodeFumes::Payload.stub!(:new).and_return(@payload)
53
+ end
54
+
55
+ it "initializes a new Payload object" do
56
+ payload_params = {:public_key => anything(), :private_key => anything(), :content => anything()}
57
+ CodeFumes::Payload.should_receive(:new).with(payload_params).and_return(@payload)
58
+ @harvester.publish_data!
59
+ end
60
+
61
+ it "saves the Payload to the website" do
62
+ @payload.should_receive(:save).and_return(true)
63
+ @harvester.publish_data!
64
+ end
65
+
66
+ it "updates the config file's project information" do
67
+ CodeFumes::ConfigFile.should_receive(:save_project)
68
+ @harvester.publish_data!
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,199 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SourceControl do
4
+ after(:each) do
5
+ SourceControl.new(GIT_FIXTURE_REPO_PATH).unlink_from_codefumes!
6
+ unless CodeFumes::ConfigFile.path == File.expand_path('~/.codefumes_config')
7
+ File.delete(CodeFumes::ConfigFile.path) if File.exists?(CodeFumes::ConfigFile.path)
8
+ end
9
+ end
10
+
11
+ describe "creation" do
12
+ context "with a path to a directory that is not a git repository" do
13
+ it "raises an error" do
14
+ invalid_path = File.dirname(__FILE__)
15
+ lambda {SourceControl.new(invalid_path)}.should raise_error(Grit::InvalidGitRepositoryError)
16
+ end
17
+ end
18
+ end
19
+
20
+ describe "supported_systems" do
21
+ it "includes :git" do
22
+ SourceControl.supported_systems.should include(:git)
23
+ end
24
+ end
25
+
26
+ describe "supported_system?" do
27
+ SourceControl.supported_systems.each do |scm_tool|
28
+ it "returns true for the string '#{scm_tool.to_s}'" do
29
+ SourceControl.supported_system?(scm_tool.to_s)
30
+ end
31
+
32
+ it "returns true for the symbol ':#{scm_tool.to_s}'" do
33
+ SourceControl.supported_system?(scm_tool.to_sym)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "initial_commit_identifier" do
39
+ before(:each) do
40
+ git_repo_path = File.expand_path(File.dirname(__FILE__) + '/../fixtures/sample_project_dirs/git_repository')
41
+ @repository = SourceControl.new(git_repo_path)
42
+ end
43
+
44
+ it "returns the identifer (sha/revision #) of the first commit" do
45
+ @repository.initial_commit_identifier.should == "a8b3e73fc5e4bc46bbdf5c1cab38cb2ce47ba2d0"
46
+ end
47
+ end
48
+
49
+ describe "payload_between" do
50
+ before(:each) do
51
+ git_repo_path = File.expand_path(File.dirname(__FILE__) + '/../fixtures/sample_project_dirs/git_repository')
52
+ @repository = SourceControl.new(git_repo_path)
53
+ end
54
+
55
+ it "returns a Hash containing the key ':commits'" do
56
+ @repository.payload_between.keys.should include(:commits)
57
+ end
58
+
59
+ it "is aliased to 'payload'" do
60
+ @repository.payload_between.keys.should == @repository.payload.keys
61
+ # FIXME: Improve this...not a complete test. Object ids getting in the way
62
+ @repository.payload_between[:commits].each_with_index do |commit, index|
63
+ commit[:identifier].should == @repository.payload[:commits][index][:identifier]
64
+ end
65
+ end
66
+
67
+ it "returns the commits in the same order a standard 'log view' of the repository would" do
68
+ first_commit = @repository.payload_between[:commits].last
69
+ first_commit[:identifier].should == "a8b3e73fc5e4bc46bbdf5c1cab38cb2ce47ba2d0"
70
+ end
71
+
72
+ context "the ':commits' key returned" do
73
+ context "contains a list of data for each commit" do
74
+ before(:each) do
75
+ @first_commit = @repository.payload_between[:commits].last
76
+ end
77
+
78
+ it "contains 'id', which is the 'sha' of the commit" do
79
+ @first_commit[:identifier].should == "a8b3e73fc5e4bc46bbdf5c1cab38cb2ce47ba2d0"
80
+ end
81
+
82
+ it "contains 'author', which points to the 'email' & the 'name' of the commit (in a Hash)" do
83
+ @first_commit[:author_email].should == "tkersten@obtiva.com"
84
+ @first_commit[:author_name].should == "Tom Kersten"
85
+ end
86
+
87
+ it "contains 'committer', which points to the 'email' & the 'name' of the commit (in a Hash)" do
88
+ @first_commit[:committer_email].should == "tkersten@obtiva.com"
89
+ @first_commit[:committer_name].should == "Tom Kersten"
90
+ end
91
+
92
+ it "contains 'message', which holds the full message of the commit" do
93
+ @first_commit[:message].should == "Initial commit with description of directory"
94
+ end
95
+
96
+ it "contains 'short_message', which holds the first line of the message of the commit" do
97
+ @first_commit[:short_message].should == "Initial commit with description of directory"
98
+ end
99
+
100
+ it "contains 'committed_date' of the commit" do
101
+ @first_commit[:committed_at].should == Chronic.parse("Sat May 09 08:52:14 -0500 2009")
102
+ end
103
+
104
+ it "contains 'authored_date' of the commit" do
105
+ @first_commit[:authored_at].should == Chronic.parse("Sat May 09 08:52:14 -0500 2009")
106
+ end
107
+
108
+ it "contains 'parent_commits' of the commit" do
109
+ @first_commit[:parent_identifiers].should_not be_nil
110
+ end
111
+
112
+ it "contains a 'line_additions' key" do
113
+ @first_commit[:line_additions].should_not be_nil
114
+ end
115
+
116
+ it "contains an 'line_deletions' key" do
117
+ @first_commit[:line_deletions].should_not be_nil
118
+ end
119
+
120
+ it "contains an 'line_total' key" do
121
+ @first_commit[:line_total].should_not be_nil
122
+ end
123
+
124
+ it "contains a 'affected_file_count' key" do
125
+ @first_commit[:affected_file_count].should_not be_nil
126
+ end
127
+ end
128
+ end
129
+
130
+ context "when 'from' is specified but the value is nil" do
131
+ it "defaults to the initial commit identifier of the repository" do
132
+ @repository.should_receive(:commits_between).with("a8b3e73fc5e4bc46bbdf5c1cab38cb2ce47ba2d0", anything()).and_return([])
133
+ @repository.payload_between(nil, "something")
134
+ end
135
+ end
136
+
137
+ context "when 'to' is specified but the value is nil" do
138
+ it "defaults to the 'HEAD' commit identifier of the repository" do
139
+ @repository.should_receive(:commits_between).with(anything(), "HEAD").and_return([])
140
+ @repository.payload_between("something", nil)
141
+ end
142
+ end
143
+
144
+ context "when there is no new information to send up" do
145
+ it "returns an empty object" do
146
+ @repository.payload_between("HEAD", "HEAD").should be_empty
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "public_key=" do
152
+ before(:each) do
153
+ git_repo_path = File.expand_path(File.dirname(__FILE__) + '/../fixtures/sample_project_dirs/git_repository')
154
+ @public_key = "public_key_specified"
155
+ @repository = SourceControl.new(git_repo_path)
156
+ @project = CodeFumes::Project.new(:public_key => @public_key)
157
+ end
158
+
159
+ it "stores the supplied public key in the SCM tool's repository-specific configuration" do
160
+ @repository.store_public_key(@public_key)
161
+ @repository.public_key.should == @public_key
162
+ end
163
+ end
164
+
165
+ describe "public_key" do
166
+ before(:each) do
167
+ @public_key = "original_value"
168
+ git_repo_path = File.expand_path(File.dirname(__FILE__) + '/../fixtures/sample_project_dirs/git_repository')
169
+ @repository = SourceControl.new(git_repo_path)
170
+ @repository.store_public_key(@public_key)
171
+ end
172
+
173
+ it "returns the current value of 'codometer.public_key' from the repository-specific configuration" do
174
+ @repository.public_key.should == @public_key
175
+ end
176
+ end
177
+
178
+ describe "local_revision_identifier" do
179
+ before(:each) do
180
+ git_repo_path = File.expand_path(File.dirname(__FILE__) + '/../fixtures/sample_project_dirs/git_repository')
181
+ @repository = SourceControl.new(git_repo_path)
182
+ end
183
+
184
+ it "returns the current revision identifier for the local repository" do
185
+ @repository.local_revision_identifier.should == "7dc0e73fea4625204b7c1e6a48e9a57025be4d7e"
186
+ end
187
+ end
188
+
189
+ describe "path" do
190
+ before(:each) do
191
+ @git_repo_path = File.expand_path(File.dirname(__FILE__) + '/../fixtures/sample_project_dirs/git_repository')
192
+ @repository = SourceControl.new(@git_repo_path)
193
+ end
194
+
195
+ it "returns the full path to the .git directory of the repository" do
196
+ @repository.path.should == @git_repo_path + '/.git'
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'harvest_repo_metrics/cli'
3
+
4
+ describe HarvestRepoMetric::CLI, "execute" do
5
+ before(:each) do
6
+ @stdout_io = StringIO.new
7
+ HarvestRepoMetric::CLI.execute(@stdout_io, ["-p", GIT_FIXTURE_REPO_PATH])
8
+ @stdout_io.rewind
9
+ @stdout = @stdout_io.read
10
+ end
11
+
12
+ after(:each) do
13
+ unless CodeFumes::ConfigFile.path == File.expand_path('~/.codefumes_config')
14
+ File.delete(CodeFumes::ConfigFile.path)
15
+ SourceControl.new(GIT_FIXTURE_REPO_PATH).unlink_from_codefumes!
16
+ end
17
+ end
18
+
19
+ it "prints out the path of the project" do
20
+ @stdout.should =~ /Project saved/
21
+ end
22
+
23
+ it "creates a .codefumes_config file" do
24
+ File.exist?(CodeFumes::ConfigFile.path).should be_true
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+
3
+ gem 'ruby-debug'
4
+ require 'ruby-debug'
5
+ require 'chronic'
6
+
7
+ begin
8
+ require 'spec/autorun'
9
+ rescue LoadError
10
+ gem 'rspec'
11
+ require 'spec/autorun'
12
+ end
13
+
14
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
15
+ require 'codefumes_harvester'
16
+
17
+ GIT_FIXTURE_REPO_PATH = File.expand_path(File.dirname(__FILE__) + "/fixtures/sample_project_dirs/git_repository")
18
+ ENV['CODEFUMES_CONFIG_FILE'] = File.expand_path(File.dirname(__FILE__) + '/sample_codefumes_config.tmp')
19
+ CodeFumes::API.mode(:test)
@@ -0,0 +1,66 @@
1
+ namespace :cf do
2
+ desc "Send scm metrics to Codefumes.com"
3
+ task :store_repo_metrics, [:public_key, :private_key] do |tsk, args|
4
+ harvest_repo_metrics_executable = File.expand_path(File.dirname(__FILE__) + "/../bin/harvest_repo_metrics")
5
+ options = args.public_key.nil? ? "" : "-k #{args.public_key}"
6
+ options += args.private_key.nil? ? "" : " -a #{args.private_key}"
7
+ sh "#{harvest_repo_metrics_executable} #{options}"
8
+ end
9
+
10
+
11
+ namespace :metric_fu do
12
+ desc "Collect commit's overall code coverage statistic from metric_fu output"
13
+ task :coverage do |tsk, args|
14
+ err_msg = "No 'rcov' section found in '#{file_path}'"
15
+ puts rcov_section.nil? ? err_msg : save(:coverage => rcov_section[:global_percent_run])
16
+ end
17
+
18
+ desc "Collect current commit's \"lines of code\" & \"lines of test\" metrics from metric_fu output"
19
+ task :loc_lot_stats do |tsk, args|
20
+ if stats_section.nil?
21
+ puts "No 'line stats' section found in '#{file_path}'"
22
+ else
23
+ metrics = {:code_lines_of_code => stats_section[:codeLOC],
24
+ :test_lines_of_code => stats_section[:testLOC],
25
+ :code_to_test_ratio => "1:#{stats_section[:code_to_test_ratio]}"
26
+ }
27
+ puts save(metrics)
28
+ end
29
+ end
30
+
31
+ def file_path
32
+ @file_path ||= File.expand_path('tmp/metric_fu/report.yml')
33
+ end
34
+
35
+ def marshalled_mf_report
36
+ return @marshalled_content unless @marshalled_content.nil?
37
+
38
+ unless File.exists?(file_path)
39
+ raise "No report.yml file. Has 'rake metrics:all' been run yet?"
40
+ end
41
+ @marshalled_content = File.open(file_path) {|f| YAML.load(f)}
42
+ end
43
+
44
+ def rcov_section
45
+ @rcov_section ||= marshalled_mf_report[:rcov]
46
+ end
47
+
48
+ def stats_section
49
+ @stats_section ||= marshalled_mf_report[:stats]
50
+ end
51
+
52
+ def save(custom_attributes_hash)
53
+ nil_value_attributes = custom_attributes_hash.select {|name, value| value.nil?}
54
+ unless nil_value_attributes.empty?
55
+ return "No value supplied for the attributes: #{nil_value_attributes.keys.join(', ')}"
56
+ end
57
+
58
+ metric_names = custom_attributes_hash.keys.join(', ')
59
+ if QuickMetric.save(custom_attributes_hash)
60
+ "Successfully saved metric(s): #{metric_names}"
61
+ else
62
+ "Error saving metric(s): #{metric_names}"
63
+ end
64
+ end
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codefumes_harvester
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Cosyn Technologies
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-08 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: codefumes
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mojombo-grit
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.1
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: mojombo-chronic
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.3.0
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: jscruggs-metric_fu
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.5
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: hoe
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 2.3.2
64
+ version:
65
+ description: CodeFumesHarvester provides a set of high-level tools for gathering history and common metrics from a local repository and sending them to CodeFumes.com[http://codefumes.com].
66
+ email:
67
+ - tom.kersten@cosyntech.com
68
+ executables:
69
+ - harvest_repo_metrics
70
+ extensions: []
71
+
72
+ extra_rdoc_files:
73
+ - History.txt
74
+ - Manifest.txt
75
+ - PostInstall.txt
76
+ - README.txt
77
+ files:
78
+ - History.txt
79
+ - Manifest.txt
80
+ - PostInstall.txt
81
+ - README.txt
82
+ - Rakefile
83
+ - bin/harvest_repo_metrics
84
+ - lib/codefumes_harvester.rb
85
+ - lib/codefumes_harvester/errors.rb
86
+ - lib/codefumes_harvester/harvester.rb
87
+ - lib/codefumes_harvester/quick_metric.rb
88
+ - lib/codefumes_harvester/source_control.rb
89
+ - lib/harvest_repo_metrics/cli.rb
90
+ - spec/codometer_harvester/harvester_spec.rb
91
+ - spec/codometer_harvester/source_control_spec.rb
92
+ - spec/harvest_repo_metrics_cli_spec.rb
93
+ - spec/spec_helper.rb
94
+ - tasks/codefumes.rake
95
+ has_rdoc: true
96
+ homepage: http://codefumes.com
97
+ post_install_message:
98
+ rdoc_options:
99
+ - --main
100
+ - README.txt
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ version:
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ version:
115
+ requirements: []
116
+
117
+ rubyforge_project: codefumes
118
+ rubygems_version: 1.3.1
119
+ signing_key:
120
+ specification_version: 2
121
+ summary: CodeFumesHarvester provides a set of high-level tools for gathering history and common metrics from a local repository and sending them to CodeFumes.com[http://codefumes.com].
122
+ test_files: []
123
+