git-pivotal 0.1.3 → 0.2.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.
data/Rakefile CHANGED
@@ -12,14 +12,14 @@ begin
12
12
  gemspec.description = "A collection of git utilities to ease integration with Pivotal Tracker"
13
13
  gemspec.email = "jeff@trydionel.com"
14
14
  gemspec.homepage = "http://github.com/trydionel/git-pivotal"
15
- gemspec.authors = ["Jeff Tucker"]
16
-
17
- gemspec.executables = ["git-pick"]
15
+ gemspec.authors = ["Jeff Tucker", "Sam Stokes"]
18
16
 
19
17
  gemspec.add_dependency "nokogiri"
20
18
  gemspec.add_dependency "rest-client"
19
+ gemspec.add_dependency "builder"
21
20
 
22
21
  gemspec.add_development_dependency "rspec"
22
+ gemspec.add_development_dependency "rcov"
23
23
  gemspec.add_development_dependency "mocha"
24
24
  end
25
25
 
@@ -29,7 +29,7 @@ rescue
29
29
  end
30
30
 
31
31
  Spec::Rake::SpecTask.new do |t|
32
- t.warning = true
32
+ t.spec_opts = ['--color']
33
33
  t.rcov = true
34
34
  t.rcov_opts = ['--exclude', 'gems']
35
35
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.2.0
data/bin/git-bug ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'pivotal'
6
+ require 'commands/bug'
7
+
8
+ exit Commands::Bug.new(STDIN, STDOUT, *ARGV).run!
data/bin/git-feature ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'pivotal'
6
+ require 'commands/feature'
7
+
8
+ exit Commands::Feature.new(STDIN, STDOUT, *ARGV).run!
data/bin/git-finish ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'pivotal'
6
+ require 'commands/finish'
7
+
8
+ exit Commands::Finish.new(STDIN, STDOUT, *ARGV).run!
data/bin/git-pick CHANGED
@@ -3,6 +3,7 @@
3
3
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
4
 
5
5
  require 'pivotal'
6
- require 'commands/pick'
6
+ require 'commands/feature'
7
7
 
8
- exit Pick.new(*ARGV).run!
8
+ STDOUT.puts "DEPRECATION WARNING: git pick has been deprecated. Please use git feature instead."
9
+ exit Commands::Feature.new(STDIN, STDOUT, *ARGV).run!
data/git-pivotal.gemspec CHANGED
@@ -5,15 +5,14 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{git-pivotal}
8
- s.version = "0.1.3"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Jeff Tucker"]
12
- s.date = %q{2010-01-30}
13
- s.default_executable = %q{git-pick}
11
+ s.authors = ["Jeff Tucker", "Sam Stokes"]
12
+ s.date = %q{2010-02-14}
14
13
  s.description = %q{A collection of git utilities to ease integration with Pivotal Tracker}
15
14
  s.email = %q{jeff@trydionel.com}
16
- s.executables = ["git-pick"]
15
+ s.executables = ["git-bug", "git-feature", "git-finish", "git-pick"]
17
16
  s.extra_rdoc_files = [
18
17
  "LICENSE"
19
18
  ]
@@ -23,20 +22,33 @@ Gem::Specification.new do |s|
23
22
  "LICENSE",
24
23
  "Rakefile",
25
24
  "VERSION",
25
+ "bin/git-bug",
26
+ "bin/git-feature",
27
+ "bin/git-finish",
26
28
  "bin/git-pick",
27
29
  "git-pivotal.gemspec",
30
+ "lib/commands/base.rb",
31
+ "lib/commands/bug.rb",
32
+ "lib/commands/feature.rb",
33
+ "lib/commands/finish.rb",
28
34
  "lib/commands/pick.rb",
29
35
  "lib/pivotal.rb",
30
36
  "lib/pivotal/api.rb",
31
37
  "lib/pivotal/associations.rb",
38
+ "lib/pivotal/attributes.rb",
32
39
  "lib/pivotal/base.rb",
33
40
  "lib/pivotal/collection.rb",
34
41
  "lib/pivotal/project.rb",
35
42
  "lib/pivotal/story.rb",
36
43
  "readme.markdown",
37
- "spec/commands/pick_spec.rb",
44
+ "spec/commands/base_spec.rb",
45
+ "spec/commands/bug_spec.rb",
46
+ "spec/commands/feature_spec.rb",
47
+ "spec/factories.rb",
48
+ "spec/factory.rb",
38
49
  "spec/pivotal/api_spec.rb",
39
50
  "spec/pivotal/associations_spec.rb",
51
+ "spec/pivotal/attributes_spec.rb",
40
52
  "spec/pivotal/base_spec.rb",
41
53
  "spec/pivotal/collection_spec.rb",
42
54
  "spec/pivotal/project_spec.rb",
@@ -49,9 +61,14 @@ Gem::Specification.new do |s|
49
61
  s.rubygems_version = %q{1.3.5}
50
62
  s.summary = %q{A collection of git utilities to ease integration with Pivotal Tracker}
51
63
  s.test_files = [
52
- "spec/commands/pick_spec.rb",
64
+ "spec/commands/base_spec.rb",
65
+ "spec/commands/bug_spec.rb",
66
+ "spec/commands/feature_spec.rb",
67
+ "spec/factories.rb",
68
+ "spec/factory.rb",
53
69
  "spec/pivotal/api_spec.rb",
54
70
  "spec/pivotal/associations_spec.rb",
71
+ "spec/pivotal/attributes_spec.rb",
55
72
  "spec/pivotal/base_spec.rb",
56
73
  "spec/pivotal/collection_spec.rb",
57
74
  "spec/pivotal/project_spec.rb",
@@ -66,18 +83,24 @@ Gem::Specification.new do |s|
66
83
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
67
84
  s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
68
85
  s.add_runtime_dependency(%q<rest-client>, [">= 0"])
86
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
69
87
  s.add_development_dependency(%q<rspec>, [">= 0"])
88
+ s.add_development_dependency(%q<rcov>, [">= 0"])
70
89
  s.add_development_dependency(%q<mocha>, [">= 0"])
71
90
  else
72
91
  s.add_dependency(%q<nokogiri>, [">= 0"])
73
92
  s.add_dependency(%q<rest-client>, [">= 0"])
93
+ s.add_dependency(%q<builder>, [">= 0"])
74
94
  s.add_dependency(%q<rspec>, [">= 0"])
95
+ s.add_dependency(%q<rcov>, [">= 0"])
75
96
  s.add_dependency(%q<mocha>, [">= 0"])
76
97
  end
77
98
  else
78
99
  s.add_dependency(%q<nokogiri>, [">= 0"])
79
100
  s.add_dependency(%q<rest-client>, [">= 0"])
101
+ s.add_dependency(%q<builder>, [">= 0"])
80
102
  s.add_dependency(%q<rspec>, [">= 0"])
103
+ s.add_dependency(%q<rcov>, [">= 0"])
81
104
  s.add_dependency(%q<mocha>, [">= 0"])
82
105
  end
83
106
  end
@@ -0,0 +1,63 @@
1
+ require 'optparse'
2
+
3
+ module Commands
4
+ class Base
5
+
6
+ attr_accessor :input, :output, :options
7
+
8
+ def initialize(input=STDIN, output=STDOUT, *args)
9
+ @input = input
10
+ @output = output
11
+ @options = {}
12
+
13
+ parse_gitconfig
14
+ parse_argv(*args)
15
+ end
16
+
17
+ def put(string, newline=true)
18
+ @output.print(newline ? string + "\n" : string) unless options[:quiet]
19
+ end
20
+
21
+ def sys(cmd)
22
+ put cmd if options[:verbose]
23
+ system cmd
24
+ end
25
+
26
+ def get(cmd)
27
+ put cmd if options[:verbose]
28
+ `#{cmd}`
29
+ end
30
+
31
+ def run!
32
+ unless options[:api_token] && options[:project_id]
33
+ put "Pivotal Tracker API Token and Project ID are required"
34
+ return 1
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def parse_gitconfig
41
+ token = get("git config --get pivotal.api-token").strip
42
+ id = get("git config --get pivotal.project-id").strip
43
+ name = get("git config --get pivotal.full-name").strip
44
+
45
+ options[:api_token] = token unless token == ""
46
+ options[:project_id] = id unless id == ""
47
+ options[:full_name] = name unless name == ""
48
+ end
49
+
50
+ def parse_argv(*args)
51
+ OptionParser.new do |opts|
52
+ opts.banner = "Usage: git pick [options]"
53
+ opts.on("-k", "--api-key=", "Pivotal Tracker API key") { |k| options[:api_token] = k }
54
+ opts.on("-p", "--project-id=", "Pivotal Trakcer project id") { |p| options[:project_id] = p }
55
+ opts.on("-n", "--full-name=", "Pivotal Trakcer full name") { |n| options[:full_name] = n }
56
+ opts.on("-q", "--quiet", "Quiet, no-interaction mode") { |q| options[:quiet] = q }
57
+ opts.on("-v", "--[no-]verbose", "Run verbosely") { |v| options[:verbose] = v }
58
+ opts.on_tail("-h", "--help", "This usage guide") { put opts; exit 0 }
59
+ end.parse!(args)
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+ require 'commands/pick'
2
+
3
+ module Commands
4
+ class Bug < Pick
5
+
6
+ def type
7
+ "bug"
8
+ end
9
+
10
+ def plural_type
11
+ "bugs"
12
+ end
13
+
14
+ def branch_suffix
15
+ "bugfix"
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require 'commands/pick'
2
+
3
+ module Commands
4
+ class Feature < Pick
5
+
6
+ def type
7
+ "feature"
8
+ end
9
+
10
+ def plural_type
11
+ "features"
12
+ end
13
+
14
+ def branch_suffix
15
+ "feature"
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ require 'commands/base'
2
+
3
+ module Commands
4
+ class Finish < Base
5
+
6
+ def run!
7
+ super
8
+
9
+ # clunky way to get branch name... need better method
10
+ current_branch = get('git status | head -1').gsub(/^.+On branch /, '').chomp
11
+ story_id = current_branch[/\d+/]
12
+ unless story_id
13
+ put "Branch name must contain a Pivotal Tracker story id"
14
+ return 1
15
+ end
16
+
17
+ api = Pivotal::Api.new(:api_token => options[:api_token])
18
+ project = api.projects.find(:id => options[:project_id])
19
+ story = project.stories.find(:id => story_id)
20
+
21
+ put "Marking Story #{story_id} as finished..."
22
+ if story.update_attributes(:current_state => :finished)
23
+
24
+ target_branch = "develop"
25
+ put "Merging #{current_branch} into #{target_branch}"
26
+ sys "git checkout #{target_branch}"
27
+ sys "git merge --no-ff #{current_branch}"
28
+
29
+ put "Removing #{current_branch} branch"
30
+ sys "git branch -d #{current_branch}"
31
+
32
+ return 0
33
+ else
34
+ put "Unable to mark Story #{story_id} as finished"
35
+
36
+ return 1
37
+ end
38
+ end
39
+
40
+ end
41
+ end
data/lib/commands/pick.rb CHANGED
@@ -1,100 +1,61 @@
1
- require 'optparse'
1
+ require 'commands/base'
2
2
 
3
- class Pick
3
+ module Commands
4
+ class Pick < Base
4
5
 
5
- attr_accessor :options
6
-
7
- def initialize(*args)
8
- @options = {}
9
- parse_gitconfig
10
- parse_argv(*args)
11
- end
6
+ def type
7
+ raise Error "must define in subclass"
8
+ end
9
+
10
+ def plural_type
11
+ raise Error "must define in subclass"
12
+ end
12
13
 
13
- def run!
14
- unless options[:api_token] && options[:project_id]
15
- puts "Pivotal Tracker API Token and Project ID are required"
16
- return 1
14
+ def branch_suffix
15
+ raise Error "must define in subclass"
17
16
  end
18
17
 
19
- puts "Retrieving latest stories from Pivotal Tracker..."
20
- api = Pivotal::Api.new(:api_token => options[:api_token])
18
+ def run!
19
+ super
20
+
21
+ put "Retrieving latest #{plural_type} from Pivotal Tracker..."
22
+ api = Pivotal::Api.new(:api_token => options[:api_token])
21
23
 
22
- project = api.projects.find(:id => options[:project_id])
23
- story = project.stories.find(:conditions => { :story_type => :feature, :current_state => :unstarted }, :limit => 1).first
24
+ project = api.projects.find(:id => options[:project_id])
25
+ story = project.stories.find(:conditions => { :story_type => type, :current_state => :unstarted }, :limit => 1).first
24
26
 
25
- unless story
26
- puts "No stories available!"
27
- return 0
28
- end
27
+ unless story
28
+ put "No stories available!"
29
+ return 0
30
+ end
29
31
 
30
- puts "Story: #{story.name}"
31
- puts "URL: #{story.url}"
32
+ put "Story: #{story.name}"
33
+ put "URL: #{story.url}"
32
34
 
33
- suffix = "feature"
34
- unless options[:quiet]
35
- print "Enter branch name (will be prepended by #{story.id}) [feature]: "
36
- suffix = gets.chomp
35
+ put "Updating story status in Pivotal Tracker..."
36
+ if story.start!(:owned_by => options[:full_name])
37
+
38
+ suffix = branch_suffix
39
+ unless options[:quiet]
40
+ put "Enter branch name (will be prepended by #{story.id}) [#{suffix}]: ", false
41
+ suffix = input.gets.chomp
37
42
 
38
- suffix = "feature" if suffix == ""
39
- end
43
+ suffix = "feature" if suffix == ""
44
+ end
40
45
 
41
- puts "Creating branch..."
42
- branch = "#{story.id}-#{suffix}"
43
- create_branch branch
44
-
45
- puts "Updating story status in Pivotal Tracker..."
46
- story.start!(:owned_by => options[:full_name])
47
-
48
- return 0
49
- end
50
-
51
- private
52
-
53
- def sys(cmd)
54
- puts cmd if options[:verbose]
55
- system cmd
56
- end
57
-
58
- def get(cmd)
59
- puts cmd if options[:verbose]
60
- `#{cmd}`
61
- end
62
-
63
- def parse_gitconfig
64
- token = get("git config --get pivotal.api-token").strip
65
- id = get("git config --get pivotal.project-id").strip
66
- name = get("git config --get pivotal.full-name").strip
46
+ branch = "#{story.id}-#{suffix}"
47
+ if get("git branch").match(branch).nil?
48
+ put "Creating #{branch} branch..."
49
+ sys "git checkout -b #{branch}"
50
+ end
67
51
 
68
- options[:api_token] = token if token
69
- options[:project_id] = id if id
70
- options[:full_name] = name if name
71
- end
72
-
73
- def parse_argv(*args)
74
- OptionParser.new do |opts|
75
- opts.banner = "Usage: git pick [options]"
76
- opts.on("-k", "--api-key=", "Pivotal Tracker API key") { |k| options[:api_token] = k }
77
- opts.on("-p", "--project-id=", "Pivotal Trakcer project id") { |p| options[:project_id] = p }
78
- opts.on("-n", "--full-name=", "Pivotal Trakcer full name") { |n| options[:full_name] = n }
79
- opts.on("-q", "--quiet", "Quiet, no-interaction mode") { |q| options[:quiet] = q }
80
- opts.on("-v", "--[no-]verbose", "Run verbosely") { |v| options[:verbose] = v }
81
- opts.on_tail("-h", "--help", "This usage guide") { puts opts; exit 0 }
82
- end.parse!(args)
83
- end
84
-
85
- def agrees?(selection)
86
- selection =~ /y/i || selection == ""
87
- end
88
-
89
- def create_branch(branch)
90
- unless branch_exists?(branch)
91
- puts "Creating #{branch} branch..."
92
- sys "git checkout -b #{branch}"
52
+ return 0
53
+ else
54
+ put "Unable to mark story as started"
55
+
56
+ return 1
57
+ end
93
58
  end
94
- end
95
59
 
96
- def branch_exists?(branch)
97
- !get("git branch").match(branch).nil?
98
60
  end
99
-
100
61
  end
@@ -0,0 +1,25 @@
1
+ module Pivotal
2
+ module Attributes
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+
7
+ class << base
8
+ attr_accessor :attributes
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def has_attributes(*attributes)
14
+ @attributes = attributes.map(&:to_s)
15
+
16
+ attributes.each do |attribute|
17
+ define_method attribute do
18
+ parsed_resource.xpath("*/" + attribute.to_s).text
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
data/lib/pivotal/base.rb CHANGED
@@ -6,9 +6,9 @@ require 'builder'
6
6
  module Pivotal
7
7
  class Base
8
8
  include Pivotal::Associations
9
+ include Pivotal::Attributes
9
10
 
10
11
  attr_accessor :resource, :xml
11
- undef id
12
12
 
13
13
  def initialize(params = {})
14
14
  params.each do |key, value|
@@ -24,13 +24,19 @@ module Pivotal
24
24
  @parsed_resource ||= Nokogiri::XML(xml)
25
25
  end
26
26
 
27
- def method_missing(method, *args)
28
- parsed_resource.css(method.to_s).text
29
- end
30
-
31
27
  def update_attributes(options = {})
32
- @xml = resource.put generate_xml(options)
33
- return self
28
+ begin
29
+ resource.put generate_xml(options) do |response|
30
+ if response.code == 200
31
+ @xml = response.body
32
+ return true
33
+ else
34
+ return false
35
+ end
36
+ end
37
+ rescue RestClient::Exception
38
+ return false
39
+ end
34
40
  end
35
41
 
36
42
  class << self
@@ -58,9 +64,7 @@ module Pivotal
58
64
 
59
65
  def allowed_keys(options = {})
60
66
  options.reject do |key, _|
61
- !%w[id story_type url estimate current_state
62
- description name requested_by owned_by
63
- created_at accepted_at labels].include? key.to_s
67
+ !self.class.attributes.include? key.to_s
64
68
  end
65
69
  end
66
70
 
@@ -50,7 +50,7 @@ module Pivotal
50
50
 
51
51
  def build_collection_from_xml(xml = "")
52
52
  Nokogiri::XML(xml).xpath(component_class.xpath).map do |item|
53
- item_id = item.xpath("//id").text
53
+ item_id = item.xpath("id").text
54
54
  component_class.new :resource => resource[item_id], :xml => item.to_xml
55
55
  end
56
56
  end
@@ -2,6 +2,13 @@ module Pivotal
2
2
  class Project < Base
3
3
 
4
4
  has_collection :stories, :of => Pivotal::Story
5
+ has_attributes :id, :name, :iteration_length, :week_start_day,
6
+ :point_scale, :account, :velocity_scheme,
7
+ :current_velocity, :initial_velocity,
8
+ :number_of_iterations_to_show, :labels,
9
+ :allow_attachments, :public, :use_https,
10
+ :bugs_and_chores_are_estimatable, :commit_mode,
11
+ :last_activity_at, :memberships, :integrations
5
12
 
6
13
  end
7
14
  end
data/lib/pivotal/story.rb CHANGED
@@ -1,9 +1,35 @@
1
1
  module Pivotal
2
2
  class Story < Base
3
3
 
4
+ has_attributes :id, :story_type, :url, :estimate, :current_state,
5
+ :description, :name, :requested_by, :owned_by,
6
+ :created_at, :accepted_at, :labels
7
+
4
8
  def start!(options = {})
9
+ return false if feature? && unestimated?
10
+
5
11
  update_attributes(options.merge(:current_state => :started))
6
12
  end
13
+
14
+ def feature?
15
+ story_type == "feature"
16
+ end
17
+
18
+ def bug?
19
+ story_type == "bug"
20
+ end
21
+
22
+ def chore?
23
+ story_type == "chore"
24
+ end
25
+
26
+ def release?
27
+ story_type == "release"
28
+ end
29
+
30
+ def unestimated?
31
+ estimate == "unestimated"
32
+ end
7
33
 
8
34
  end
9
35
  end
data/lib/pivotal.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__),'pivotal', 'collection')
2
2
  require File.join(File.dirname(__FILE__),'pivotal', 'associations')
3
+ require File.join(File.dirname(__FILE__),'pivotal', 'attributes')
3
4
 
4
5
  require File.join(File.dirname(__FILE__),'pivotal','base')
5
6
  require File.join(File.dirname(__FILE__),'pivotal','story')
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+ require 'commands/base'
3
+
4
+ describe Commands::Base do
5
+
6
+ before(:each) do
7
+ @input = mock('input')
8
+ @output = mock('output')
9
+
10
+ # stub out git config requests
11
+ Commands::Base.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
12
+ end
13
+
14
+ it "should set the api key with the -k option" do
15
+ @pick = Commands::Base.new(@input, @output,"-k", "1234")
16
+ @pick.options[:api_token].should == "1234"
17
+ end
18
+
19
+ it "should set the api key with the --api-token= option" do
20
+ @pick = Commands::Base.new(@input, @output,"--api-key=1234")
21
+ @pick.options[:api_token].should == "1234"
22
+ end
23
+
24
+ it "should set the project id with the -p option" do
25
+ @pick = Commands::Base.new(@input, @output,"-p", "1")
26
+ @pick.options[:project_id].should == "1"
27
+ end
28
+
29
+ it "should set the project id with the --project-id= option" do
30
+ @pick = Commands::Base.new(@input, @output,"--project-id=1")
31
+ @pick.options[:project_id].should == "1"
32
+ end
33
+
34
+ it "should set the full name with the -n option" do
35
+ @pick = Commands::Base.new(@input, @output,"-n", "Jeff Tucker")
36
+ @pick.options[:full_name].should == "Jeff Tucker"
37
+ end
38
+
39
+ it "should set the full name with the --full-name= option" do
40
+ @pick = Commands::Base.new(@input, @output,"--full-name=Jeff Tucker")
41
+ @pick.options[:full_name].should == "Jeff Tucker"
42
+ end
43
+
44
+ it "should set the quiet flag with the -q option" do
45
+ @pick = Commands::Base.new(@input, @output,"-q")
46
+ @pick.options[:quiet].should be_true
47
+ end
48
+
49
+ it "should set the quiet flag with the --quiet option" do
50
+ @pick = Commands::Base.new(@input, @output,"--quiet")
51
+ @pick.options[:quiet].should be_true
52
+ end
53
+
54
+ it "should set the verbose flag with the -v option" do
55
+ @pick = Commands::Base.new(@input, @output,"-v")
56
+ @pick.options[:verbose].should be_true
57
+ end
58
+
59
+ it "should set the verbose flag with the --verbose option" do
60
+ @pick = Commands::Base.new(@input, @output,"--verbose")
61
+ @pick.options[:verbose].should be_true
62
+ end
63
+
64
+ it "should unset the verbose flag with the --no-verbose option" do
65
+ @pick = Commands::Base.new(@input, @output,"--no-verbose")
66
+ @pick.options[:verbose].should be_false
67
+ end
68
+
69
+ it "should print a message if the API token is missing" do
70
+ @output.expects(:print).with("Pivotal Tracker API Token and Project ID are required\n")
71
+
72
+ @pick = Commands::Base.new(@input, @output, "-p", "1")
73
+ @pick.run!
74
+ end
75
+
76
+ it "should print a message if the project ID is missing" do
77
+ @output.expects(:print).with("Pivotal Tracker API Token and Project ID are required\n")
78
+
79
+ @pick = Commands::Base.new(@input, @output, "-k", "1")
80
+ @pick.run!
81
+ end
82
+
83
+ end
@@ -0,0 +1,25 @@
1
+ require 'commands/bug'
2
+ require 'spec_helper'
3
+
4
+ describe Commands::Bug do
5
+
6
+ before(:each) do
7
+ # stub out git config requests
8
+ Commands::Bug.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
9
+
10
+ @bug = Commands::Bug.new
11
+ end
12
+
13
+ it "should specify its story type" do
14
+ @bug.type.should == "bug"
15
+ end
16
+
17
+ it "should specify a plural for its story types" do
18
+ @bug.plural_type.should == "bugs"
19
+ end
20
+
21
+ it "should specify its branch suffix" do
22
+ @bug.branch_suffix.should == "bugfix"
23
+ end
24
+
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'commands/feature'
2
+ require 'spec_helper'
3
+
4
+ describe Commands::Feature do
5
+
6
+ before(:each) do
7
+ # stub out git config requests
8
+ Commands::Feature.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
9
+
10
+ @feature = Commands::Feature.new
11
+ end
12
+
13
+ it "should specify its story type" do
14
+ @feature.type.should == "feature"
15
+ end
16
+
17
+ it "should specify a plural for its story types" do
18
+ @feature.plural_type.should == "features"
19
+ end
20
+
21
+ it "should specify its branch suffix" do
22
+ @feature.branch_suffix.should == "feature"
23
+ end
24
+
25
+ end
data/spec/factories.rb ADDED
@@ -0,0 +1,13 @@
1
+ require File.join(File.dirname(__FILE__), 'factory')
2
+
3
+ Factory.define :story, {
4
+ :id => 1,
5
+ :current_state => :unstarted,
6
+ :story_type => :feature,
7
+ :estimate => 1,
8
+ }
9
+
10
+ Factory.define :project, {
11
+ :id => 1,
12
+ :name => "Project"
13
+ }
data/spec/factory.rb ADDED
@@ -0,0 +1,26 @@
1
+ class Factory
2
+ class << self
3
+ def factories
4
+ @@factories ||= {}
5
+ end
6
+
7
+ def define(type, attributes)
8
+ factories[type] = attributes
9
+ end
10
+ end
11
+ end
12
+
13
+
14
+ def Factory(type, attrs = {})
15
+ defaults = Factory.factories[type]
16
+ attrs = defaults.merge(attrs)
17
+
18
+ markup = Builder::XmlMarkup.new
19
+ markup.__send__(type) do
20
+ attrs.each do |attribute, value|
21
+ markup.__send__(attribute, value.to_s)
22
+ end
23
+ end
24
+
25
+ markup.target!
26
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pivotal::Attributes do
4
+
5
+ it "should load the #has_attributes method onto a class" do
6
+ @base = Class.new
7
+ @base.should_not respond_to(:has_attributes)
8
+
9
+ @base.send :include, Pivotal::Attributes
10
+ @base.should respond_to(:has_attributes)
11
+ end
12
+
13
+ it "should add methods specified in #has_attributes to the object" do
14
+ @base = Class.new
15
+ @base.send :include, Pivotal::Attributes
16
+
17
+ @base.new.should_not respond_to(:one)
18
+ @base.has_attributes :one
19
+ @base.new.should respond_to(:one)
20
+ end
21
+
22
+ it "should add the attributes specified in #has_attributes to the class #attributes list" do
23
+ @base = Class.new
24
+ @base.send :include, Pivotal::Attributes
25
+
26
+ @base.attributes.should be_nil
27
+ @base.has_attributes :one
28
+ @base.attributes.should == ["one"]
29
+ end
30
+
31
+ end
@@ -20,11 +20,6 @@ describe Pivotal::Base do
20
20
  @base.parsed_resource.should be_a(Nokogiri::XML::Document)
21
21
  end
22
22
 
23
- it "should forward undefined methods to the XML parse tree" do
24
- @base.should_not respond_to(:id)
25
- @base.id.should == "1"
26
- end
27
-
28
23
  it "should present the class's item xpath" do
29
24
  @base.class.xpath.should == "//api"
30
25
  end
@@ -37,7 +32,12 @@ describe Pivotal::Base do
37
32
 
38
33
  before(:each) do
39
34
  @xml = "<api><current_state>started</current_state></api>"
40
- @base.resource.expects(:put).with(@xml).returns(@xml)
35
+ @response = mock("Response")
36
+ @response.stubs(:code).returns(200)
37
+ @response.stubs(:body).returns(@xml)
38
+
39
+ @base.resource.expects(:put).with(@xml).yields(@response)
40
+ @base.class.has_attributes :current_state
41
41
  end
42
42
 
43
43
  it "should be able to update the remote resource with a hash of string values" do
@@ -55,7 +55,16 @@ describe Pivotal::Base do
55
55
  it "should update the stored xml with the new remote model" do
56
56
  lambda {
57
57
  @base.update_attributes(:current_state => "started")
58
- }.should change(@base, :xml).to(@xml)
58
+ }.should change(@base, :xml).to(@response.body)
59
+ end
60
+
61
+ it "should return true if the response code is 200" do
62
+ @base.update_attributes(:current_state => :started).should == true
63
+ end
64
+
65
+ it "should return false if the response code is not 200" do
66
+ @response.stubs(:code).returns(422)
67
+ @base.update_attributes(:current_state => :started).should == false
59
68
  end
60
69
 
61
70
  end
@@ -4,6 +4,7 @@ describe Pivotal::Collection do
4
4
 
5
5
  before(:each) do
6
6
  @api = pivotal_api
7
+ RestClient::Resource.any_instance.stubs(:get).returns("<project><id>1</id></project>")
7
8
  end
8
9
 
9
10
  it "should find a single item given an id" do
@@ -3,8 +3,7 @@ require 'spec_helper'
3
3
  describe Pivotal::Project do
4
4
 
5
5
  before(:each) do
6
- @api = pivotal_api
7
- @project = Pivotal::Project.new :resource => @api.resource["projects"][1]
6
+ @project = Pivotal::Project.new :resource => pivotal_api.resource["projects"][1]
8
7
  end
9
8
 
10
9
  it "should be connected to the project resource" do
@@ -16,4 +15,20 @@ describe Pivotal::Project do
16
15
  @project.stories.component_class.should == Pivotal::Story
17
16
  end
18
17
 
18
+ [:id, :name, :iteration_length, :week_start_day,
19
+ :point_scale, :account, :velocity_scheme,
20
+ :current_velocity, :initial_velocity,
21
+ :number_of_iterations_to_show, :labels,
22
+ :allow_attachments, :public, :use_https,
23
+ :bugs_and_chores_are_estimatable, :commit_mode,
24
+ :last_activity_at, :memberships, :integrations].map(&:to_s).each do |method|
25
+ it "should have an accessor method for #{method}" do
26
+ @project.methods.should include(method)
27
+ end
28
+
29
+ it "should include #{method} in the list of valid attributes" do
30
+ @project.class.attributes.should include(method)
31
+ end
32
+ end
33
+
19
34
  end
@@ -2,28 +2,93 @@ require 'spec_helper'
2
2
 
3
3
  describe Pivotal::Story do
4
4
 
5
+ def story_type(type = "feature")
6
+ Factory(:story, :story_type => type)
7
+ end
8
+
5
9
  before(:each) do
6
- @api = pivotal_api
7
- @project = Pivotal::Project.new :resource => @api.resource["projects"][1]
8
- @story = Pivotal::Story.new :resource => @project.resource["stories"][1]
10
+ @story = Pivotal::Story.new :resource => pivotal_api.resource["projects"][1]["stories"][1]
9
11
  end
10
12
 
11
13
  it "should be connected to the story resource" do
12
14
  @story.resource.url.should == "https://www.pivotaltracker.com/services/v3/projects/1/stories/1"
13
15
  end
14
16
 
15
- it "should be able to mark the story as started" do
16
- @xml = "<story><current_state>started</current_state></story>"
17
- @story.resource.expects(:put).with(@xml)
17
+ [:id, :story_type, :url, :estimate, :current_state,
18
+ :description, :name, :requested_by, :owned_by,
19
+ :created_at, :accepted_at, :labels].map(&:to_s).each do |method|
20
+ it "should have an accessor method for #{method}" do
21
+ @story.methods.should include(method)
22
+ end
23
+
24
+ it "should include #{method} in the list of valid attributes" do
25
+ @story.class.attributes.should include(method)
26
+ end
27
+
28
+ it "should return the proper value when #{method} is called" do
29
+ @story.xml = Factory(:story, method.to_sym => "Test Result")
30
+ @story.send(method).should == "Test Result"
31
+ end
32
+ end
33
+
34
+ it "should specify whether the story is a feature" do
35
+ @story.xml = story_type "feature"
36
+
37
+ @story.should be_a_feature
38
+ @story.should_not be_a_bug
39
+ @story.should_not be_a_chore
40
+ @story.should_not be_a_release
41
+ end
42
+
43
+ it "should specify whether the story is a bug" do
44
+ @story.xml = story_type "bug"
45
+
46
+ @story.should_not be_a_feature
47
+ @story.should be_a_bug
48
+ @story.should_not be_a_chore
49
+ @story.should_not be_a_release
50
+ end
51
+
52
+ it "should specify whether the story is a chore" do
53
+ @story.xml = story_type "chore"
18
54
 
55
+ @story.should_not be_a_feature
56
+ @story.should_not be_a_bug
57
+ @story.should be_a_chore
58
+ @story.should_not be_a_release
59
+ end
60
+
61
+ it "should specify whether the story is a release" do
62
+ @story.xml = story_type "release"
63
+
64
+ @story.should_not be_a_feature
65
+ @story.should_not be_a_bug
66
+ @story.should_not be_a_chore
67
+ @story.should be_a_release
68
+ end
69
+
70
+ it "should be able to mark the story as started" do
71
+ @xpath = "//current_state = 'started'"
72
+ @story.resource.expects(:put).with { |xml| Nokogiri::XML(xml).xpath(@xpath) }
19
73
  @story.start!
20
74
  end
21
75
 
22
76
  it "should be able to update other attributes when marking the story as started" do
23
- @xml = "<story><current_state>started</current_state><owned_by>Jeff Tucker</owned_by></story>"
24
- @story.resource.expects(:put).with(@xml)
25
-
77
+ # Check the XML contains the right options.
78
+ # Can't just check the XML string, because the elements may be in a
79
+ # different order (because it's built from a hash).
80
+ @xpath = "//current_state = 'started' and //owned_by = 'Jeff Tucker'"
81
+ @story.resource.expects(:put).with {|xml| Nokogiri.XML(xml).xpath(@xpath) }
26
82
  @story.start!(:owned_by => "Jeff Tucker")
27
83
  end
28
84
 
29
- end
85
+ it "should return false if attempting to start an unestimated feature story" do
86
+ # bad_response = mock("Response")
87
+ # bad_response.stubs(:code).returns(422)
88
+ # RestClient::Resource.any_instance.stubs(:put).yields(bad_response)
89
+
90
+ @story.xml = Factory(:story, :story_type => :feature, :estimate => :unestimated)
91
+ @story.start!.should be_false
92
+ end
93
+
94
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,20 +2,14 @@ require 'rubygems'
2
2
  require 'mocha'
3
3
  require 'builder'
4
4
  require 'pivotal'
5
- require 'commands/pick'
5
+ require File.join(File.dirname(__FILE__), 'factories')
6
6
 
7
7
  Spec::Runner.configure do |config|
8
8
  config.mock_with :mocha
9
9
  end
10
10
 
11
- def demo_xml
12
- Builder::XmlMarkup.new.project do |project|
13
- project.id 1
14
- end
15
- end
16
-
17
11
  def stub_connection_to_pivotal
18
- RestClient::Resource.any_instance.stubs(:get).returns(demo_xml)
12
+ RestClient::Resource.any_instance.stubs(:get).returns("")
19
13
  end
20
14
 
21
15
  def pivotal_api
metadata CHANGED
@@ -1,16 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-pivotal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Tucker
8
+ - Sam Stokes
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
12
 
12
- date: 2010-01-30 00:00:00 -05:00
13
- default_executable: git-pick
13
+ date: 2010-02-14 00:00:00 -05:00
14
+ default_executable:
14
15
  dependencies:
15
16
  - !ruby/object:Gem::Dependency
16
17
  name: nokogiri
@@ -32,6 +33,16 @@ dependencies:
32
33
  - !ruby/object:Gem::Version
33
34
  version: "0"
34
35
  version:
36
+ - !ruby/object:Gem::Dependency
37
+ name: builder
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
35
46
  - !ruby/object:Gem::Dependency
36
47
  name: rspec
37
48
  type: :development
@@ -42,6 +53,16 @@ dependencies:
42
53
  - !ruby/object:Gem::Version
43
54
  version: "0"
44
55
  version:
56
+ - !ruby/object:Gem::Dependency
57
+ name: rcov
58
+ type: :development
59
+ version_requirement:
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
45
66
  - !ruby/object:Gem::Dependency
46
67
  name: mocha
47
68
  type: :development
@@ -55,6 +76,9 @@ dependencies:
55
76
  description: A collection of git utilities to ease integration with Pivotal Tracker
56
77
  email: jeff@trydionel.com
57
78
  executables:
79
+ - git-bug
80
+ - git-feature
81
+ - git-finish
58
82
  - git-pick
59
83
  extensions: []
60
84
 
@@ -66,20 +90,33 @@ files:
66
90
  - LICENSE
67
91
  - Rakefile
68
92
  - VERSION
93
+ - bin/git-bug
94
+ - bin/git-feature
95
+ - bin/git-finish
69
96
  - bin/git-pick
70
97
  - git-pivotal.gemspec
98
+ - lib/commands/base.rb
99
+ - lib/commands/bug.rb
100
+ - lib/commands/feature.rb
101
+ - lib/commands/finish.rb
71
102
  - lib/commands/pick.rb
72
103
  - lib/pivotal.rb
73
104
  - lib/pivotal/api.rb
74
105
  - lib/pivotal/associations.rb
106
+ - lib/pivotal/attributes.rb
75
107
  - lib/pivotal/base.rb
76
108
  - lib/pivotal/collection.rb
77
109
  - lib/pivotal/project.rb
78
110
  - lib/pivotal/story.rb
79
111
  - readme.markdown
80
- - spec/commands/pick_spec.rb
112
+ - spec/commands/base_spec.rb
113
+ - spec/commands/bug_spec.rb
114
+ - spec/commands/feature_spec.rb
115
+ - spec/factories.rb
116
+ - spec/factory.rb
81
117
  - spec/pivotal/api_spec.rb
82
118
  - spec/pivotal/associations_spec.rb
119
+ - spec/pivotal/attributes_spec.rb
83
120
  - spec/pivotal/base_spec.rb
84
121
  - spec/pivotal/collection_spec.rb
85
122
  - spec/pivotal/project_spec.rb
@@ -114,9 +151,14 @@ signing_key:
114
151
  specification_version: 3
115
152
  summary: A collection of git utilities to ease integration with Pivotal Tracker
116
153
  test_files:
117
- - spec/commands/pick_spec.rb
154
+ - spec/commands/base_spec.rb
155
+ - spec/commands/bug_spec.rb
156
+ - spec/commands/feature_spec.rb
157
+ - spec/factories.rb
158
+ - spec/factory.rb
118
159
  - spec/pivotal/api_spec.rb
119
160
  - spec/pivotal/associations_spec.rb
161
+ - spec/pivotal/attributes_spec.rb
120
162
  - spec/pivotal/base_spec.rb
121
163
  - spec/pivotal/collection_spec.rb
122
164
  - spec/pivotal/project_spec.rb
@@ -1,66 +0,0 @@
1
- require 'spec_helper'
2
- require 'commands/pick'
3
-
4
- describe Pick do
5
-
6
- before(:each) do
7
- # stub out git config requests
8
- Pick.any_instance.expects(:get).times(3).with { |v| v =~ /git config/}.returns("")
9
- end
10
-
11
- it "should set the api key with the -k option" do
12
- @pick = Pick.new("-k", "1234")
13
- @pick.options[:api_token].should == "1234"
14
- end
15
-
16
- it "should set the api key with the --api-token= option" do
17
- @pick = Pick.new("--api-key=1234")
18
- @pick.options[:api_token].should == "1234"
19
- end
20
-
21
- it "should set the project id with the -p option" do
22
- @pick = Pick.new("-p", "1")
23
- @pick.options[:project_id].should == "1"
24
- end
25
-
26
- it "should set the project id with the --project-id= option" do
27
- @pick = Pick.new("--project-id=1")
28
- @pick.options[:project_id].should == "1"
29
- end
30
-
31
- it "should set the full name with the -n option" do
32
- @pick = Pick.new("-n", "Jeff Tucker")
33
- @pick.options[:full_name].should == "Jeff Tucker"
34
- end
35
-
36
- it "should set the full name with the --full-name= option" do
37
- @pick = Pick.new("--full-name=Jeff Tucker")
38
- @pick.options[:full_name].should == "Jeff Tucker"
39
- end
40
-
41
- it "should set the quiet flag with the -q option" do
42
- @pick = Pick.new("-q")
43
- @pick.options[:quiet].should be_true
44
- end
45
-
46
- it "should set the quiet flag with the --quiet option" do
47
- @pick = Pick.new("--quiet")
48
- @pick.options[:quiet].should be_true
49
- end
50
-
51
- it "should set the verbose flag with the -v option" do
52
- @pick = Pick.new("-v")
53
- @pick.options[:verbose].should be_true
54
- end
55
-
56
- it "should set the verbose flag with the --verbose option" do
57
- @pick = Pick.new("--verbose")
58
- @pick.options[:verbose].should be_true
59
- end
60
-
61
- it "should unset the verbose flag with the --no-verbose option" do
62
- @pick = Pick.new("--no-verbose")
63
- @pick.options[:verbose].should be_false
64
- end
65
-
66
- end