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,157 @@
|
|
|
1
|
+
require 'highline'
|
|
2
|
+
|
|
3
|
+
class HighLine
|
|
4
|
+
[ :ask, :choose ].each do |name|
|
|
5
|
+
eval <<-END_OF_METHOD_ALIASING
|
|
6
|
+
def #{ name }_with_newline(*args, &block)
|
|
7
|
+
#{ name }_without_newline(*args, &block)
|
|
8
|
+
ensure
|
|
9
|
+
$stdout.puts
|
|
10
|
+
end
|
|
11
|
+
alias_method_chain(:#{ name }, :newline)
|
|
12
|
+
END_OF_METHOD_ALIASING
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class HighLine::Menu
|
|
17
|
+
def menu_option(menu_text, result)
|
|
18
|
+
choice(menu_text) { result }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module GitWorkflow
|
|
23
|
+
module Commands
|
|
24
|
+
class Setup < Base
|
|
25
|
+
def execute
|
|
26
|
+
[ :name, :token, :project_id, :workflow, :branches ].each do |step|
|
|
27
|
+
send(:"enquire_about_#{ step }")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
ALIGNMENT_PADDING = ' ' * 10
|
|
34
|
+
|
|
35
|
+
def usage_info(options)
|
|
36
|
+
options.banner = 'Usage: git workflow-setup'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def highline
|
|
40
|
+
@highline ||= HighLine.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
delegate :get_config_value_for, :to => 'GitWorkflow::Configuration.instance'
|
|
44
|
+
delegate :set_config_value, :to => 'GitWorkflow::Configuration.instance'
|
|
45
|
+
|
|
46
|
+
def set_value_through(method, setting, *args, &block)
|
|
47
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
48
|
+
optional = options.delete(:optional)
|
|
49
|
+
|
|
50
|
+
# Only update the value if it is set or is optional and blank.
|
|
51
|
+
default = get_config_value_for(setting)
|
|
52
|
+
value = highline.send(method, *args) do |question|
|
|
53
|
+
question.default = default
|
|
54
|
+
block.call(question)
|
|
55
|
+
end
|
|
56
|
+
set_config_value(setting, value) unless optional and value.blank?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def ask(setting, options = {}, &block)
|
|
60
|
+
set_value_through(:ask, setting, 'Dummy', options, &block)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def choose(setting, options = {}, &block)
|
|
64
|
+
set_value_through(:choose, setting, options, &block)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def enquire_about_name
|
|
68
|
+
username = get_config_value_for('user.name')
|
|
69
|
+
ask('pt.username', :optional => true) do |question|
|
|
70
|
+
text = %Q{
|
|
71
|
+
When marking Pivotal Tracker (http://pivotaltracker.com/) stories as started or finished,
|
|
72
|
+
this git workflow needs to know your name. You have to enter it as it appears on
|
|
73
|
+
Pivotal Tracker and it is your name, not your email address. For instance, I appear
|
|
74
|
+
as 'Matthew Denner'.
|
|
75
|
+
}
|
|
76
|
+
text << %Q{
|
|
77
|
+
If the name you have on Pivotal Tracker is the same as the current git 'user.name' then
|
|
78
|
+
you can leave this blank. Your git 'user.name' is currently: #{ username }
|
|
79
|
+
} unless username.blank?
|
|
80
|
+
text << %Q{
|
|
81
|
+
Please enter your Pivotal Tracker name:
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
question.question = text.align(ALIGNMENT_PADDING)
|
|
85
|
+
question.default = ''
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def enquire_about_token
|
|
90
|
+
ask('pt.token') do |question|
|
|
91
|
+
question.validate = /^[A-Fa-f0-9]{32}$/
|
|
92
|
+
question.question = %Q{
|
|
93
|
+
When the workflow starts or finishes a story it needs to be able to interact with Pivotal
|
|
94
|
+
Tracker (http://pivotaltracker.com/) and to do so you must have an account. Any updates
|
|
95
|
+
this git workflow makes to Pivotal Tracker need to be done through the API and so it needs
|
|
96
|
+
to know your individual API token. This can be found under 'API Token' on your profile
|
|
97
|
+
page (https://www.pivotaltracker.com/profile).
|
|
98
|
+
|
|
99
|
+
Please enter your Pivotal Tracker API token:
|
|
100
|
+
}.align(ALIGNMENT_PADDING)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def enquire_about_project_id
|
|
105
|
+
ask('pt.projectid') do |question|
|
|
106
|
+
question.answer_type = Integer
|
|
107
|
+
question.question = %Q{
|
|
108
|
+
To interact with Pivotal Tracker (http://pivotaltracker.com/) this git workflow needs to
|
|
109
|
+
know the ID for your project. This can be found in the URL you get when looking at your
|
|
110
|
+
project and is the numeric valid after 'http://www.pivotaltracker.com/projects'.
|
|
111
|
+
|
|
112
|
+
Please enter your Pivotal Tracker project ID:
|
|
113
|
+
}.align(ALIGNMENT_PADDING)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def enquire_about_workflow
|
|
118
|
+
ask('workflow.callbacks') do |question|
|
|
119
|
+
question.default = 'default'
|
|
120
|
+
question.question = %Q{
|
|
121
|
+
The standard behaviour of this git workflow is to create a new branch and mark a
|
|
122
|
+
Pivotal Tracker (http://pivotaltracker.com/) story as started when you run 'git start',
|
|
123
|
+
and to mark a Pivotal Tracker story as finished when you run 'git finish'. You can alter
|
|
124
|
+
this behaviour by choosing a different workflow.
|
|
125
|
+
|
|
126
|
+
Please enter the name of your workflow:
|
|
127
|
+
}.align(ALIGNMENT_PADDING)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def enquire_about_branches
|
|
132
|
+
choose_branch_convention(:local)
|
|
133
|
+
choose_branch_convention(:remote)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def choose_branch_convention(location)
|
|
137
|
+
choose("workflow.#{ location }branchconvention") do |convention|
|
|
138
|
+
convention.header = %Q{
|
|
139
|
+
Git branches are created locally and can be pushed remotely. The naming convention for these
|
|
140
|
+
can be different and can be setup to your personal preference. It's easier to explain their
|
|
141
|
+
format with an example:
|
|
142
|
+
|
|
143
|
+
Imagine you have story 12345 which has a title of 'Do something useful'. If you choose to have
|
|
144
|
+
a branch convention of "PT story number, then PT story title" the branch created will be
|
|
145
|
+
'12345_do_something_useful'. If you select "PT story title, then PT story number" you will
|
|
146
|
+
get the branch 'do_something_useful_12345'.
|
|
147
|
+
}.align(ALIGNMENT_PADDING)
|
|
148
|
+
|
|
149
|
+
convention.menu_option('PT story number, then PT story title', '${number}_${name}')
|
|
150
|
+
convention.menu_option('PT story title, then PT story number', '${name}_${number}')
|
|
151
|
+
|
|
152
|
+
convention.prompt = "Choose your #{ location } branch naming convention:"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'git_workflow/commands/base'
|
|
2
|
+
|
|
3
|
+
module GitWorkflow
|
|
4
|
+
module Commands
|
|
5
|
+
class Start < Base
|
|
6
|
+
def initialize(command_line_arguments)
|
|
7
|
+
super(command_line_arguments) do |remaining_arguments|
|
|
8
|
+
@story_id, @parent_branch = remaining_arguments
|
|
9
|
+
raise InvalidCommandLine, 'The command line is invalid' if @story_id.nil?
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def execute
|
|
14
|
+
story(@story_id) do |story|
|
|
15
|
+
start(story, @parent_branch)
|
|
16
|
+
start_story_on_pivotal_tracker!(story)
|
|
17
|
+
|
|
18
|
+
$stdout.puts "Story #{ story.story_id }: #{ story.name }"
|
|
19
|
+
$stdout.puts story.description
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def usage_info(options)
|
|
26
|
+
options.banner = 'Usage: git start <PT story number> [<parent branch>]'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'git_workflow/git'
|
|
3
|
+
|
|
4
|
+
module GitWorkflow
|
|
5
|
+
class Configuration
|
|
6
|
+
include Singleton
|
|
7
|
+
include GitWorkflow::Git
|
|
8
|
+
|
|
9
|
+
class Convention
|
|
10
|
+
include GitWorkflow::Git
|
|
11
|
+
|
|
12
|
+
REGEXP_STORY_ID = /\$\{\s*number\s*\}/
|
|
13
|
+
REGEXP_EVALUATION = /\$\{(number|name)\}/
|
|
14
|
+
|
|
15
|
+
def initialize(convention)
|
|
16
|
+
raise StandardError, "Convention '#{ convention }' has no story ID" unless convention =~ REGEXP_STORY_ID
|
|
17
|
+
@to_convention = convention.gsub(REGEXP_EVALUATION, '#{\1}')
|
|
18
|
+
@from_convention = Regexp.new(convention.sub(REGEXP_STORY_ID, '(\d+)').gsub(REGEXP_EVALUATION, '.+'))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to(story)
|
|
22
|
+
use_existing_branch_for(story) || generate_new_branch_name_for(story)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def from(branch)
|
|
26
|
+
match = branch.match(@from_convention)
|
|
27
|
+
raise StandardError, "Branch '#{ branch }' does not appear to conform to local convention" if match.nil?
|
|
28
|
+
match[ 1 ].to_i
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def use_existing_branch_for(story)
|
|
34
|
+
repository.branches.detect do |branch|
|
|
35
|
+
begin
|
|
36
|
+
from(branch) == story.story_id
|
|
37
|
+
rescue StandardError => exception
|
|
38
|
+
# Ignore, as this is definitely not the branch!
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def generate_new_branch_name_for(story)
|
|
45
|
+
StoryWrapper.new(story).__instance_eval__(%Q{"#{ @to_convention }"}).downcase.gsub(/[^a-z0-9]+/, '_').sub(/_+$/, '')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class StoryWrapper
|
|
49
|
+
alias_method :__instance_eval__, :instance_eval
|
|
50
|
+
instance_methods.each do |method|
|
|
51
|
+
undef_method(method) unless method.to_s =~ /^__(send|id|instance_eval)__$/
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def initialize(story)
|
|
55
|
+
@story = story
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def number
|
|
59
|
+
@story.story_id
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def name
|
|
63
|
+
@story.name
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def username
|
|
70
|
+
@username ||= get_config_value_for('pt.username') || get_config_value_for!('user.name')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def local_branch_convention
|
|
74
|
+
@local_branch_convention ||= Convention.new(get_config_value_for!('workflow.localbranchconvention'))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def remote_branch_convention
|
|
78
|
+
@remote_branch_convention ||= Convention.new(get_config_value_for!('workflow.remotebranchconvention'))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def project_id
|
|
82
|
+
@project_id ||= get_config_value_for!('pt.projectid')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def api_token
|
|
86
|
+
@api_token ||= get_config_value_for!('pt.token')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def ignore_git_global?
|
|
90
|
+
ENV['IGNORE_GIT_GLOBAL']
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
class Class
|
|
2
|
+
def delegates_to_class(method)
|
|
3
|
+
class_eval do
|
|
4
|
+
linenumber = __LINE__ + 1
|
|
5
|
+
eval(%Q{
|
|
6
|
+
def #{ method }(*args, &block)
|
|
7
|
+
self.class.#{ method }(*args, &block)
|
|
8
|
+
end
|
|
9
|
+
}, binding, __FILE__, linenumber)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class NilClass
|
|
15
|
+
def blank?
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class String
|
|
21
|
+
def blank?
|
|
22
|
+
self =~ /^\s*$/
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def underscore
|
|
26
|
+
self.split('::').map { |v| v.gsub(/(.)([A-Z])/, '\1_\2') }.join('/').downcase
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def camelize
|
|
30
|
+
self.split('/').map { |v| v.gsub(/(?:^|_)(.)/) { $1.upcase } }.join('::')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def constantize
|
|
34
|
+
self.camelize.split('::').inject(Object) do |current,constant|
|
|
35
|
+
current.const_get(constant) or current.const_missing(constant)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def align(padding = '')
|
|
40
|
+
self.gsub(/^\s+([^\s])/, "#{ padding }\\1")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
require 'popen4'
|
|
45
|
+
|
|
46
|
+
module Execution
|
|
47
|
+
class CommandFailure < StandardError
|
|
48
|
+
attr_reader :result
|
|
49
|
+
|
|
50
|
+
def initialize(command, result)
|
|
51
|
+
super("Command '#{ command }' failed")
|
|
52
|
+
@result = result
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def raise_if_required!
|
|
56
|
+
raise self unless @result.exitstatus == 0
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def execute_command(command)
|
|
61
|
+
command_output = nil
|
|
62
|
+
execute_command_with_output_handling(command) { |stdout, _, _| command_output = stdout.read }
|
|
63
|
+
command_output
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def execute_command_with_output_handling(command, &block)
|
|
67
|
+
debug("Executing '#{ command }' ...") do
|
|
68
|
+
exit_status = POpen4.popen4(command) do |stdout, stderr, stdin, pid|
|
|
69
|
+
yield(stdout, stderr, stdin)
|
|
70
|
+
end
|
|
71
|
+
CommandFailure.new(command, exit_status).raise_if_required!
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Module
|
|
77
|
+
def chain_methods(method, chain, &block)
|
|
78
|
+
match = method.to_s.match(/^([^!\?]+)([!\?])?$/) or raise StandardError, "Cannot match '#{ method }' as a method"
|
|
79
|
+
method_core, extension = match[ 1 ], match[ 2 ]
|
|
80
|
+
with_chain_method = :"#{ method_core }_with_#{ chain }#{ extension }"
|
|
81
|
+
without_chain_method = :"#{ method_core }_without_#{ chain }#{ extension }"
|
|
82
|
+
yield(with_chain_method, without_chain_method)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def alias_method_chain(method, chain)
|
|
86
|
+
chain_methods(method, chain) do |with_chain_method, without_chain_method|
|
|
87
|
+
alias_method(without_chain_method, method)
|
|
88
|
+
alias_method(method, with_chain_method)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def unalias_method_chain(method, chain)
|
|
93
|
+
chain_methods(method, chain) do |with_chain_method, without_chain_method|
|
|
94
|
+
alias_method(method, without_chain_method)
|
|
95
|
+
undef_method(without_chain_method)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def delegate(name, options)
|
|
100
|
+
class_eval <<-END_OF_DELEGATION
|
|
101
|
+
def #{ name }(*args, &block)
|
|
102
|
+
#{ options[ :to ] }.#{ name }(*args, &block)
|
|
103
|
+
end
|
|
104
|
+
END_OF_DELEGATION
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'git_workflow/core_ext'
|
|
3
|
+
|
|
4
|
+
module GitWorkflow
|
|
5
|
+
module Git
|
|
6
|
+
class Repository
|
|
7
|
+
include Singleton
|
|
8
|
+
include Execution
|
|
9
|
+
include GitWorkflow::Logging
|
|
10
|
+
|
|
11
|
+
RepositoryError = Class.new(StandardError)
|
|
12
|
+
BranchError = Class.new(RepositoryError)
|
|
13
|
+
CheckoutError = Class.new(RepositoryError)
|
|
14
|
+
ConfigError = Class.new(RepositoryError)
|
|
15
|
+
|
|
16
|
+
def current_branch
|
|
17
|
+
return @current_branch unless @current_branch.nil?
|
|
18
|
+
|
|
19
|
+
match = execute_command('git branch').match(/\*\s+([^\s]+)/)
|
|
20
|
+
raise BranchError, 'Could not determine current branch' if match.nil?
|
|
21
|
+
@current_branch = match[ 1 ]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def checkout(branch)
|
|
25
|
+
maintain_current_branch(branch) do
|
|
26
|
+
begin
|
|
27
|
+
info("Checking out branch '#{ branch }'") do
|
|
28
|
+
execute_command("git checkout #{ branch }")
|
|
29
|
+
end
|
|
30
|
+
rescue Execution::CommandFailure => exception
|
|
31
|
+
raise CheckoutError, "Unable to checkout branch '#{ branch }'"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def create(branch, source = nil)
|
|
37
|
+
maintain_current_branch(branch) do
|
|
38
|
+
begin
|
|
39
|
+
info("Creating branch '#{ branch }'") do
|
|
40
|
+
command = "git checkout -b #{ branch }"
|
|
41
|
+
command << " #{ source }" unless source.nil?
|
|
42
|
+
execute_command(command)
|
|
43
|
+
end
|
|
44
|
+
rescue Execution::CommandFailure => exception
|
|
45
|
+
raise CheckoutError, "Unable to create branch '#{ branch }'"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def merge(branch)
|
|
51
|
+
execute_command("git merge #{ branch }")
|
|
52
|
+
rescue Execution::CommandFailure => exception
|
|
53
|
+
raise BranchError, "Could not merge #{ branch } into current branch"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def branches
|
|
57
|
+
execute_command('git branch').split("\n").map { |b| b.sub(/^(\*)?\s+/, '') }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def does_branch_exist?(branch)
|
|
61
|
+
execute_command('git branch') =~ /\s+#{ branch }(\s+|$)/
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def config_get(key)
|
|
65
|
+
command = [ 'git config' ]
|
|
66
|
+
command << '--file .git/config' if GitWorkflow::Configuration.instance.ignore_git_global?
|
|
67
|
+
command << key
|
|
68
|
+
value = execute_command(command.join(' ')).strip
|
|
69
|
+
value.empty? ? nil : value
|
|
70
|
+
rescue Execution::CommandFailure => exception
|
|
71
|
+
raise ConfigError, "Could not retrieve '#{ key }' configuration setting"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def config_set(key, value)
|
|
75
|
+
raise ConfigError, 'No key has been specified for configuration setting' if key.blank?
|
|
76
|
+
execute_command(%Q{git config '#{ key }' '#{ value }'})
|
|
77
|
+
rescue Execution::CommandFailure => exception
|
|
78
|
+
raise ConfigError, "Could not set '#{ key }' configuration setting (value: #{ value })"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def push(branch)
|
|
82
|
+
execute_command("git push origin #{ branch }")
|
|
83
|
+
rescue Execution::CommandFailure => exception
|
|
84
|
+
raise BranchError, "Unable to push branch '#{ branch }'"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def maintain_current_branch(branch, &block)
|
|
90
|
+
yield
|
|
91
|
+
@current_branch = branch
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def repository
|
|
96
|
+
Repository.instance
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def in_git_branch(target_branch, &block)
|
|
100
|
+
current_branch = repository.current_branch
|
|
101
|
+
if current_branch == target_branch
|
|
102
|
+
yield
|
|
103
|
+
else
|
|
104
|
+
info("Temporarily switching to branch '#{ target_branch }'")
|
|
105
|
+
|
|
106
|
+
repository.checkout(target_branch)
|
|
107
|
+
yield
|
|
108
|
+
repository.checkout(current_branch)
|
|
109
|
+
|
|
110
|
+
info("Switched back to '#{ current_branch }'")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def merge_branch(source_branch, target_branch)
|
|
115
|
+
info("Merging branch '#{ source_branch }' into '#{ target_branch }'") do
|
|
116
|
+
repository.checkout(target_branch)
|
|
117
|
+
repository.merge(source_branch)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def checkout_or_create_branch(branch, source = nil)
|
|
122
|
+
if repository.does_branch_exist?(branch)
|
|
123
|
+
repository.checkout(branch)
|
|
124
|
+
else
|
|
125
|
+
repository.create(branch, source)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def get_config_value_for(key, default = nil)
|
|
130
|
+
repository.config_get(key)
|
|
131
|
+
rescue Repository::ConfigError => exception
|
|
132
|
+
default
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def get_config_value_for!(key)
|
|
136
|
+
get_config_value_for(key) or raise StandardError, "Required configuration setting '#{ key }' is unset"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def set_config_value(key, value)
|
|
140
|
+
repository.config_set(key, value)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|