git_workflow 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +77 -0
- data/Rakefile +33 -0
- data/bin/git-finish +4 -0
- data/bin/git-start +4 -0
- data/bin/git-workflow-setup +4 -0
- data/features/core_functionality/4049578_need_the_ability_to_start_a_new_branch.feature +15 -0
- data/features/core_functionality/4049611_need_the_ability_to_finish_a_specified_branch.feature +17 -0
- data/features/core_functionality/4056359_start_with_local_branch_already_present.feature +16 -0
- data/features/core_functionality/4056363_finish_the_current_branch.feature +17 -0
- data/features/core_functionality/4056389_changes_to_pt_story_name_cause_branch_issues.feature +25 -0
- data/features/core_functionality/4135658_control_output_verbosity_using_v_switch.feature +32 -0
- data/features/core_functionality/4468313_get_config_value_for_is_producing_an_error_log.feature +43 -0
- data/features/extensions/4058326_display_story_description_on_start.feature +14 -0
- data/features/git_branching/4135501_allow_branch_start_point_to_be_specified_from_command_line.feature +27 -0
- data/features/hooks/4069174_investigate_the_effort_required_to_add_pre_amp_post_hooks.feature +24 -0
- data/features/hooks/4199841_need_hooks_for_my_workflow.feature +51 -0
- data/features/hooks/4199845_need_hooks_for_sanger_workflow.feature +66 -0
- data/features/hooks/4205913_output_from_rake_tests_should_be_seen.feature +19 -0
- data/features/hooks/4424828_git_start_is_missing_pt_integration_for_my_hooks.feature +15 -0
- data/features/pivotal_tracker_api/4133291_chores_go_straight_to_accepted_not_to_finished.feature +15 -0
- data/features/release/4132264_fix_the_libxml_warnings_from_nokogiri.feature +20 -0
- data/features/step_definitions/aruba_extensions.rb +4 -0
- data/features/step_definitions/configuration_steps.rb +38 -0
- data/features/step_definitions/git_steps.rb +102 -0
- data/features/step_definitions/misc_steps.rb +3 -0
- data/features/step_definitions/pivotal_tracker_steps.rb +43 -0
- data/features/step_definitions/project_code_steps.rb +35 -0
- data/features/support/aruba.rb +1 -0
- data/features/support/hooks/configuration.rb +4 -0
- data/features/support/hooks/environment.rb +49 -0
- data/features/support/hooks/pivotal_tracker.rb +170 -0
- data/lib/git_workflow.rb +5 -0
- data/lib/git_workflow/callbacks.rb +16 -0
- data/lib/git_workflow/callbacks/pivotal_tracker_support.rb +64 -0
- data/lib/git_workflow/callbacks/remote_git_branch_support.rb +9 -0
- data/lib/git_workflow/callbacks/styles/debug.rb +58 -0
- data/lib/git_workflow/callbacks/styles/default.rb +43 -0
- data/lib/git_workflow/callbacks/styles/mine.rb +52 -0
- data/lib/git_workflow/callbacks/styles/sanger.rb +48 -0
- data/lib/git_workflow/callbacks/test_code_support.rb +29 -0
- data/lib/git_workflow/command_line.rb +46 -0
- data/lib/git_workflow/commands.rb +4 -0
- data/lib/git_workflow/commands/base.rb +31 -0
- data/lib/git_workflow/commands/finish.rb +36 -0
- data/lib/git_workflow/commands/setup.rb +157 -0
- data/lib/git_workflow/commands/start.rb +30 -0
- data/lib/git_workflow/configuration.rb +93 -0
- data/lib/git_workflow/core_ext.rb +106 -0
- data/lib/git_workflow/git.rb +143 -0
- data/lib/git_workflow/logger.yaml +27 -0
- data/lib/git_workflow/logging.rb +77 -0
- data/lib/git_workflow/story.rb +96 -0
- data/spec/core_functionality/4056539_support_http_proxy_spec.rb +68 -0
- data/spec/core_functionality/4058365_bad_request_on_story_update_spec.rb +13 -0
- data/spec/core_functionality/4058394_make_commands_more_verbose_spec.rb +41 -0
- data/spec/core_functionality/4058719_error_if_none_of_the_required_configuration_values_are_set_spec.rb +21 -0
- data/spec/core_functionality/4058861_git_config_returns_empty_strings_which_should_be_nil_spec.rb +59 -0
- data/spec/core_functionality/4172431_add_git_workflow_setup_command_to_make_setup_easier_spec.rb +148 -0
- data/spec/core_functionality/4199841_need_callbacks_for_my_workflow_spec.rb +97 -0
- data/spec/extensions/4199841_need_callbacks_for_my_workflow_spec.rb +167 -0
- data/spec/extensions/4205913_output_from_rake_tests_should_be_seen_spec.rb +34 -0
- data/spec/git_branching/4058723_branches_end_in_if_the_last_character_is_invalid_spec.rb +40 -0
- data/spec/git_branching/4059824_code_is_not_using_workflow_localbranchconvention_to_decode_branch_names_on_finish_spec.rb +115 -0
- data/spec/git_branching/4216087_support_pushing_branches_spec.rb +44 -0
- data/spec/hooks/4199845_need_hooks_for_sanger_workflow_spec.rb +27 -0
- data/spec/pivotal_tracker_api/4056381_pt_api_token_not_being_passed_in_headers_spec.rb +40 -0
- data/spec/pivotal_tracker_api/4056638_library_code_using_localhost_spec.rb +16 -0
- data/spec/pivotal_tracker_api/4056661_content_type_header_should_be_text_xml_on_updates_spec.rb +16 -0
- data/spec/pivotal_tracker_api/4058718_if_pt_username_is_not_set_use_user_name_spec.rb +26 -0
- data/spec/pivotal_tracker_api/4146016_decode_the_xml_encoded_text_of_description_and_name_spec.rb +63 -0
- data/spec/shared_examples/configuration.rb +10 -0
- data/spec/shared_examples/story.rb +17 -0
- data/spec/spec_helper.rb +18 -0
- metadata +220 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# rake features FEATURE=features/hooks/4205913_output_from_rake_tests_should_be_seen.feature
|
2
|
+
@hooks @needs_service
|
3
|
+
Feature: Output from rake tests should be seen
|
4
|
+
Scenario: The output should be appearing
|
5
|
+
Given my Pivotal Tracker configuration is setup as normal
|
6
|
+
And I have "mine" callbacks enabled
|
7
|
+
|
8
|
+
Given the story 4205913 exists
|
9
|
+
And the name of story 4205913 is "Output from rake tests should be seen"
|
10
|
+
|
11
|
+
Given the local branch "4205913_output_from_rake_tests_should_be_seen" exists
|
12
|
+
And the local branch "4205913_output_from_rake_tests_should_be_seen" is active
|
13
|
+
And the rake task "spec" will fail
|
14
|
+
And the rake task "features" will fail
|
15
|
+
|
16
|
+
When I execute "git finish"
|
17
|
+
|
18
|
+
Then the stdout should contain "Running spec"
|
19
|
+
And the stderr should contain "Failing spec"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# rake features FEATURE=4424828_git_start_is_missing_pt_integration_for_my_hooks
|
2
|
+
@hooks @needs_service @my_workflow
|
3
|
+
Feature: 'git start' should integrate with PT for my workflow
|
4
|
+
Scenario: Starting a new branch with my workflow
|
5
|
+
Given my Pivotal Tracker configuration is setup as normal
|
6
|
+
And I have "mine" callbacks enabled
|
7
|
+
|
8
|
+
Given the story 4424828 exists
|
9
|
+
And the name of story 4424828 is "'git start' is missing PT integration for my hooks"
|
10
|
+
|
11
|
+
When I successfully execute "git start 4424828"
|
12
|
+
|
13
|
+
Then the branch "4424828_git_start_is_missing_pt_integration_for_my_hooks" should be active
|
14
|
+
And the owner of story 4424828 should be "Matthew Denner"
|
15
|
+
And story 4424828 should be started
|
data/features/pivotal_tracker_api/4133291_chores_go_straight_to_accepted_not_to_finished.feature
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# rake features FEATURE=features/pivotal_tracker_api/4133291_chores_go_straight_to_accepted_not_to_finished.feature
|
2
|
+
@pivotal_tracker_api @needs_service
|
3
|
+
Feature: Finishing a chore means accepting it
|
4
|
+
Scenario: Finishing a chore
|
5
|
+
Given my Pivotal Tracker configuration is setup as normal
|
6
|
+
|
7
|
+
Given the story 4133291 exists
|
8
|
+
And story 4133291 is a chore
|
9
|
+
And the name of story 4133291 is "Chores go straight to 'accepted' not to 'finished'"
|
10
|
+
|
11
|
+
Given the local branch "4133291_chores_go_straight_to_accepted_not_to_finished" exists
|
12
|
+
|
13
|
+
When I successfully execute "git finish 4133291"
|
14
|
+
|
15
|
+
Then story 4133291 should be accepted
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# rake features FEATURE=features/release/4132264_fix_the_libxml_warnings_from_nokogiri.feature
|
2
|
+
@release @needs_service
|
3
|
+
Feature: Fix the LibXML warnings from Nokogiri
|
4
|
+
Background:
|
5
|
+
Given my Pivotal Tracker configuration is setup as normal
|
6
|
+
|
7
|
+
Given the story 4132264 exists
|
8
|
+
And the name of story 4132264 is "Fix the LibXML warnings from Nokogiri"
|
9
|
+
|
10
|
+
Scenario: Starting a branch
|
11
|
+
When I successfully execute "git start 4132264"
|
12
|
+
|
13
|
+
Then the output should not contain "I_KNOW_I_AM_USING_AN_OLD_AND_BUGGY_VERSION_OF_LIBXML2"
|
14
|
+
|
15
|
+
Scenario: Finishing a branch
|
16
|
+
Given the local branch "4132264_fix_the_libxml_warnings_from_nokogiri" exists
|
17
|
+
|
18
|
+
When I successfully execute "git finish 4132264"
|
19
|
+
|
20
|
+
Then the output should not contain "I_KNOW_I_AM_USING_AN_OLD_AND_BUGGY_VERSION_OF_LIBXML2"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Given /^my Pivotal Tracker configuration is setup as normal$/ do
|
2
|
+
Given %Q{my Pivotal Tracker username is "Matthew Denner"}
|
3
|
+
Given %Q{my Pivotal Tracker project ID is 93630}
|
4
|
+
Given %Q{my Pivotal Tracker token is 1234567890}
|
5
|
+
Given %Q{my local branch naming convention is "${number}_${name}"}
|
6
|
+
Given %Q{my remote branch naming convention is "${number}_${name}"}
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^my git username is "([^\"]+)"$/ do |username|
|
10
|
+
git_configure('user.name', username)
|
11
|
+
end
|
12
|
+
|
13
|
+
Given /^my Pivotal Tracker username is "([^\"]+)"$/ do |username|
|
14
|
+
git_configure('pt.username', username)
|
15
|
+
end
|
16
|
+
|
17
|
+
Given /^my Pivotal Tracker project ID is (\d+)$/ do |id|
|
18
|
+
git_configure('pt.projectid', id.to_i)
|
19
|
+
mock_service.project_id = id.to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
Given /^my Pivotal Tracker token is ([a-zA-Z0-9]+)$/ do |token|
|
23
|
+
git_configure('pt.token', token)
|
24
|
+
end
|
25
|
+
|
26
|
+
Given /^my (local|remote) branch naming convention is "((?:(?:(?:\$\{[a-z][a-z_]+\})|[a-zA-Z0-9_]+)+))"$/ do |branch,convention|
|
27
|
+
git_configure("workflow.#{ branch }branchconvention", convention)
|
28
|
+
end
|
29
|
+
|
30
|
+
Given /^I have "([^\"]+)" callbacks enabled$/ do |callbacks|
|
31
|
+
git_configure("workflow.callbacks", callbacks)
|
32
|
+
end
|
33
|
+
|
34
|
+
def git_configure(key, value)
|
35
|
+
in_current_dir do
|
36
|
+
%x{git config '#{ key }' '#{ value }'}
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
def execute_silently(command)
|
2
|
+
%x{#{ command } > /dev/null 2>&1}
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^the local branch "([^\"]+)" exists$/ do |branch|
|
6
|
+
in_current_dir do
|
7
|
+
execute_silently(%Q{git checkout -b #{ branch } master})
|
8
|
+
execute_silently(%Q{git checkout master})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Given /^the local branch "([^\"]+)" is active$/ do |branch|
|
13
|
+
in_current_dir do
|
14
|
+
execute_silently(%Q{git checkout #{ branch }})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Then /^the branch "([^\"]*)" should be active$/ do |name|
|
19
|
+
in_current_dir do
|
20
|
+
%x{git branch}.split("\n").map(&:strip).should include("* #{ name }")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Then /^the branch "([^\"]*)" should be merged into master$/ do |name|
|
25
|
+
in_current_dir do
|
26
|
+
execute_silently(%Q{git checkout master})
|
27
|
+
%x{git branch --no-merge}.split("\n").map(&:strip).should_not include(name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Then /^the branch "([^\"]+)" should not be merged into master$/ do |name|
|
32
|
+
in_current_dir do
|
33
|
+
execute_silently(%Q{git checkout master})
|
34
|
+
%x{git branch --no-merge}.split("\n").map(&:strip).should include(name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
When /^I( successfully)? execute "git (start|finish)([^\"]*)"$/ do |success,command,arguments|
|
39
|
+
root_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
40
|
+
real_command = File.join(root_path, 'bin', "git-#{ command }")
|
41
|
+
lib_path = File.join(root_path, 'lib')
|
42
|
+
When %Q{I#{ success } run "IGNORE_GIT_GLOBAL=true ruby -I#{ lib_path } -rrubygems #{ real_command } #{ arguments }"}
|
43
|
+
end
|
44
|
+
|
45
|
+
Given /^I commit "([^\"]+)"$/ do |filename|
|
46
|
+
in_current_dir do
|
47
|
+
execute_silently(%Q{git add '#{ filename }'})
|
48
|
+
execute_silently(%Q{git commit -m 'Committing "#{ filename }"' '#{ filename }'})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
Then /^the parent of branch "([^\"]+)" should be "([^\"]+)"$/ do |child,parent|
|
53
|
+
in_current_dir do
|
54
|
+
# This is probably the most hacky piece of shit I've written so far!
|
55
|
+
execute_silently("git checkout #{ child }")
|
56
|
+
output = %x{git show-branch --topo-order --current}
|
57
|
+
lines = output.split("\n")
|
58
|
+
|
59
|
+
# Determine the branches that are matching and their offset in the output lines.
|
60
|
+
matchers = []
|
61
|
+
until lines.empty?
|
62
|
+
line = lines.shift
|
63
|
+
break if line =~ /^---.*$/
|
64
|
+
|
65
|
+
if line =~ /^(\s*)\*\s+\[#{ child }\]/
|
66
|
+
matchers[ $1.length ] = '\*'
|
67
|
+
elsif line =~ /^(\s*)!\s\[#{ parent }\]/
|
68
|
+
matchers[ $1.length ] = '\+'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
raise StandardError, "Cannot find branches in list:\n#{ output }" unless [ '\*', '\+' ].all? { |m| matchers.include?(m) }
|
72
|
+
|
73
|
+
# Find a line that matches the appropriate commit. Basically when the two branches
|
74
|
+
# share a commit. This doesn't work if the branches are intermerging.
|
75
|
+
regexp = Regexp.new("^#{ matchers.map { |m| m || ' ' }.join }.+")
|
76
|
+
|
77
|
+
matched_line = nil
|
78
|
+
until lines.empty?
|
79
|
+
line = lines.shift
|
80
|
+
next unless line =~ regexp
|
81
|
+
matched_line = line
|
82
|
+
break
|
83
|
+
end
|
84
|
+
raise StandardError, "#{ child } appears not to be related to #{ parent }" if matched_line.nil?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
Then /^the local and remote "([^\"]+)" branches should agree$/ do |branch|
|
89
|
+
Then %Q{the local branch "#{ branch }" and remote branch "#{ branch }" should agree}
|
90
|
+
end
|
91
|
+
|
92
|
+
Then /^the local branch "([^\"]+)" and remote branch "([^\"]+)" should agree$/ do |local,remote|
|
93
|
+
in_current_dir do
|
94
|
+
ref_to_sha = %x{git show-ref}.split("\n").inject({}) do |ref_to_sha, line|
|
95
|
+
match = line.match(%r{^([a-f0-9]+)\s+refs/(.+)$}) or raise StandardError, "Cannot parse ref line '#{ line }'"
|
96
|
+
ref_to_sha[ match[ 2 ] ] = match[ 1 ]
|
97
|
+
ref_to_sha
|
98
|
+
end
|
99
|
+
|
100
|
+
ref_to_sha[ "heads/#{ local }" ].should == ref_to_sha[ "remotes/origin/#{ remote }" ]
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
Transform /^story (\d+)$/ do |id|
|
2
|
+
id.to_i
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^the story (\d+) exists$/ do |id|
|
6
|
+
create_story(id)
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^story (\d+) is a (feature|bug|chore)$/ do |id, type|
|
10
|
+
for_story(id) do |story|
|
11
|
+
story.story_type = type
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /^the name of story (\d+) is "([^\"]+)"$/ do |id,name|
|
16
|
+
for_story(id) do |story|
|
17
|
+
story.name = name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Given /^the description of story (\d+) is "([^\"]+)"$/ do |id,description|
|
22
|
+
for_story(id) do |story|
|
23
|
+
story.description = description
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Then /^story (\d+) should be (started|finished|delivered|rejected|accepted)$/ do |id,state|
|
28
|
+
for_story(id) do |story|
|
29
|
+
story.current_state.should == state
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Then /^story (\d+) should have a comment of "([^\"]+)"$/ do |id,comment|
|
34
|
+
for_story(id) do |story|
|
35
|
+
story.comments.should include(comment)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Then /^the owner of story (\d+) should be "([^\"]+)"$/ do |id,owner|
|
40
|
+
for_story(id) do |story|
|
41
|
+
story.owned_by.should == owner
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Given /^the rake task "([^\"]+)" will fail$/ do |task|
|
2
|
+
in_current_dir do
|
3
|
+
File.open('Rakefile', 'a+') do |file|
|
4
|
+
file << <<-END_OF_RAKE_TASK
|
5
|
+
task(:#{ task }) do
|
6
|
+
$stdout.puts "Running #{ task }"
|
7
|
+
$stderr.puts "Failing #{ task }"
|
8
|
+
|
9
|
+
raise 'Task "#{ task }" is setup to fail'
|
10
|
+
end
|
11
|
+
END_OF_RAKE_TASK
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add and commit the file
|
15
|
+
%x{git add Rakefile}
|
16
|
+
%x{git commit -m 'Upated #{ task } to fail' Rakefile}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Given /^the rake task "([^\"]+)" will succeed$/ do |task|
|
21
|
+
in_current_dir do
|
22
|
+
File.open('Rakefile', 'a+') do |file|
|
23
|
+
file << <<-END_OF_RAKE_TASK
|
24
|
+
task(:#{ task }) do
|
25
|
+
$stdout.puts "Running #{ task }"
|
26
|
+
$stderr.puts "Succeeding #{ task }"
|
27
|
+
end
|
28
|
+
END_OF_RAKE_TASK
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add and commit the file
|
32
|
+
%x{git add Rakefile}
|
33
|
+
%x{git commit -m 'Upated #{ task } to succeed' Rakefile}
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'aruba'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Ensures that the environment is setup appropriately for all scenarios
|
2
|
+
Before do
|
3
|
+
# Modify the PATH so that the bin directory is available
|
4
|
+
@path_before = ENV['PATH']
|
5
|
+
ENV['PATH'] = "#{ File.expand_path(File.join(File.dirname(__FILE__), %w{.. .. .. bin})) }:#{ ENV['PATH'] }"
|
6
|
+
|
7
|
+
# We always need a local repository
|
8
|
+
Given %Q{a directory named "local_repository"}
|
9
|
+
end
|
10
|
+
|
11
|
+
Before('@needs_remote_repository') do
|
12
|
+
# Sets up the remote repository
|
13
|
+
Given %Q{a directory named "remote_repository"}
|
14
|
+
Given %Q{I cd to "remote_repository"}
|
15
|
+
Given %Q{I successfully run "git init --bare ."}
|
16
|
+
|
17
|
+
# Creates the local one by cloning the remote repository
|
18
|
+
Given %Q{I cd to the root}
|
19
|
+
Given %Q{I successfully run "git clone remote_repository local_repository"}
|
20
|
+
|
21
|
+
# This then sets up the master branch, both locally and remotely
|
22
|
+
Given %Q{I cd to "local_repository"}
|
23
|
+
Given %Q{an empty file named "basic-file"}
|
24
|
+
Given %Q{I successfully run "git add basic-file"}
|
25
|
+
Given %Q{I successfully run "git commit -a -m 'Initial project'"}
|
26
|
+
Given %Q{I successfully run "git push origin master"}
|
27
|
+
end
|
28
|
+
|
29
|
+
Before('~@needs_remote_repository') do
|
30
|
+
Given %Q{I cd to "local_repository"}
|
31
|
+
Given %Q{I successfully run "git init ."}
|
32
|
+
Given %Q{an empty file named "basic-file"}
|
33
|
+
Given %Q{I successfully run "git add basic-file"}
|
34
|
+
Given %Q{I successfully run "git commit -a -m 'Initial project'"}
|
35
|
+
end
|
36
|
+
|
37
|
+
Before do
|
38
|
+
Given %Q{I cd to the root}
|
39
|
+
Given %Q{I cd to "local_repository"}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Tidy up the environmental setup
|
43
|
+
After do
|
44
|
+
ENV['PATH'] = @path_before
|
45
|
+
end
|
46
|
+
|
47
|
+
def project_root
|
48
|
+
File.join(File.dirname(__FILE__), %w{.. .. ..})
|
49
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'net/http'
|
3
|
+
require 'sinatra'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'builder'
|
6
|
+
|
7
|
+
# Nokogiri doesn't like buggy LibXML2
|
8
|
+
I_KNOW_I_AM_USING_AN_OLD_AND_BUGGY_VERSION_OF_LIBXML2 = true
|
9
|
+
require 'nokogiri'
|
10
|
+
|
11
|
+
# This is a fix-up for sinatra so that we can silence the output. It allows us to
|
12
|
+
# pass a Hash of options for webrick in the options passed to the #run! method.
|
13
|
+
class Sinatra::Base
|
14
|
+
class << self
|
15
|
+
def run!(options={})
|
16
|
+
set options
|
17
|
+
handler = detect_rack_handler
|
18
|
+
handler_name = handler.name.gsub(/.*::/, '')
|
19
|
+
handler.run(self, { :Host => bind, :Port => port }.merge(options.fetch(:webrick, {}))) do |server|
|
20
|
+
trap(:INT) do
|
21
|
+
## Use thins' hard #stop! if available, otherwise just #stop
|
22
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
23
|
+
end
|
24
|
+
set :running, true
|
25
|
+
end
|
26
|
+
rescue Errno::EADDRINUSE => e
|
27
|
+
puts "== Someone is already performing on port #{port}!"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class PivotalTrackerService
|
33
|
+
class MockService < Sinatra::Base
|
34
|
+
get '/services/v3/projects/:project_id/stories/:story_id' do |_, story_id|
|
35
|
+
handle_story(story_id) do |story|
|
36
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
37
|
+
xml.instruct!
|
38
|
+
xml.story {
|
39
|
+
xml.id(story.story_id, :type => 'integer')
|
40
|
+
[ :story_type, :current_state, :name, :owned_by, :description ].each do |element|
|
41
|
+
xml.tag!(element, story.send(element))
|
42
|
+
end
|
43
|
+
}
|
44
|
+
xml.to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
put '/services/v3/projects/:project_id/stories/:story_id' do |_, story_id|
|
49
|
+
handle_story(story_id) do |story|
|
50
|
+
Nokogiri::XML(request.body.read).xpath('/story/*').each do |element|
|
51
|
+
story.send(:"#{ element.name }=", element.content)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
post '/services/v3/projects/:project_id/stories/:story_id/notes' do |_, story_id|
|
57
|
+
handle_story(story_id) do |story|
|
58
|
+
comment = Nokogiri::XML(request.body.read).xpath('/note/text').first or raise StandardError, 'No comment body found!'
|
59
|
+
story.comments.push(comment.content)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_story(id, &block)
|
64
|
+
self.class.handle_story(id, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
class << self
|
68
|
+
def stories
|
69
|
+
@stories ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_story(id, &block)
|
73
|
+
story = stories[ id ] or raise StandardError, "Story #{ id } does not appear to exist"
|
74
|
+
yield(story)
|
75
|
+
end
|
76
|
+
|
77
|
+
def reset_stories!
|
78
|
+
@stories = {}
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_story(id)
|
82
|
+
raise StandardError, "Story #{ id } already exists" unless stories[ id ].nil?
|
83
|
+
stories[ id ] = story = OpenStruct.new(
|
84
|
+
:story_id => id,
|
85
|
+
:story_type => 'feature',
|
86
|
+
:current_state => 'not yet started',
|
87
|
+
:owned_by => '',
|
88
|
+
:name => '',
|
89
|
+
:comments => []
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def for_story(id, &block)
|
94
|
+
story = stories[ id ] or raise StandardError, "Story #{ id } is undefined"
|
95
|
+
yield(story)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
attr_writer :project_id
|
101
|
+
|
102
|
+
def initialize(host, port)
|
103
|
+
@host, @port = host, port.to_i
|
104
|
+
end
|
105
|
+
|
106
|
+
def run!
|
107
|
+
kill_previous_instance
|
108
|
+
start_sinatra
|
109
|
+
wait_for_sinatra_to_startup!
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def kill_previous_instance
|
115
|
+
Thread.list.first.kill if Thread.list.size > 2
|
116
|
+
end
|
117
|
+
|
118
|
+
def start_sinatra
|
119
|
+
Thread.new do
|
120
|
+
# The effort you have to go through to silence Sinatra/WEBrick!
|
121
|
+
logger = Logger.new(STDERR)
|
122
|
+
logger.level = Logger::FATAL
|
123
|
+
|
124
|
+
MockService.run!(:host => @host, :port => @port, :webrick => { :Logger => logger, :AccessLog => [] })
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# We have to pause execution until Sinatra has come up. This makes a number of attempts to
|
129
|
+
# retrieve the root document. If it runs out of attempts it raises an exception
|
130
|
+
def wait_for_sinatra_to_startup!
|
131
|
+
(1..10).each do |_|
|
132
|
+
begin
|
133
|
+
Net::HTTP.get(URI.parse('http://localhost:7000/'))
|
134
|
+
return
|
135
|
+
rescue Errno::ECONNREFUSED => exception
|
136
|
+
sleep(1)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
raise StandardError, "Our dummy webservice did not start up in time!"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
mock_service = PivotalTrackerService.new('localhost', 7000)
|
145
|
+
mock_service.run!
|
146
|
+
self.class.instance_eval do
|
147
|
+
define_method(:mock_service) { mock_service }
|
148
|
+
end
|
149
|
+
eval <<-END_OF_WORLD_HOOKS
|
150
|
+
def create_story(id)
|
151
|
+
PivotalTrackerService::MockService.create_story(id)
|
152
|
+
end
|
153
|
+
|
154
|
+
def for_story(id, &block)
|
155
|
+
PivotalTrackerService::MockService.for_story(id, &block)
|
156
|
+
end
|
157
|
+
END_OF_WORLD_HOOKS
|
158
|
+
|
159
|
+
# This ensures that we have a behaviour similar to that of Pivotal Tracker.
|
160
|
+
Before('@needs_service') do
|
161
|
+
@_http_proxy_before = ENV['http_proxy']
|
162
|
+
ENV['http_proxy'] = 'http://localhost:7000/'
|
163
|
+
end
|
164
|
+
|
165
|
+
After('@needs_service') do
|
166
|
+
ENV['http_proxy'] = @_http_proxy_before
|
167
|
+
|
168
|
+
PivotalTrackerService::MockService.reset_stories!
|
169
|
+
end
|
170
|
+
|