git_workflow 0.0.5
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/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
|
+
|