redmine_stagecoach 0.5.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create ruby-1.9.2-p290@stagecoach
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'ghi'
4
+ gem 'trollop'
5
+ gem 'capistrano'
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "shoulda", ">= 0"
11
+ gem "bundler", "~> 1.0.0"
12
+ gem "jeweler", "~> 1.6.4"
13
+ gem "rcov", ">= 0"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ capistrano (2.9.0)
5
+ highline
6
+ net-scp (>= 1.0.0)
7
+ net-sftp (>= 2.0.0)
8
+ net-ssh (>= 2.0.14)
9
+ net-ssh-gateway (>= 1.1.0)
10
+ ghi (0.3.0)
11
+ git (1.2.5)
12
+ highline (1.6.9)
13
+ jeweler (1.6.4)
14
+ bundler (~> 1.0)
15
+ git (>= 1.2.5)
16
+ rake
17
+ net-scp (1.0.4)
18
+ net-ssh (>= 1.99.1)
19
+ net-sftp (2.0.5)
20
+ net-ssh (>= 2.0.9)
21
+ net-ssh (2.3.0)
22
+ net-ssh-gateway (1.1.0)
23
+ net-ssh (>= 1.99.1)
24
+ rake (0.9.2.2)
25
+ rcov (0.9.11)
26
+ shoulda (2.11.3)
27
+ trollop (1.16.2)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ bundler (~> 1.0.0)
34
+ capistrano
35
+ ghi
36
+ jeweler (~> 1.6.4)
37
+ rcov
38
+ shoulda
39
+ trollop
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Oli Barnett
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,69 @@
1
+ = Stagecoach
2
+ stagecoach works in two stages, init and deploy. The init stage creates a branch based on a redmine/planio issue, and the deploy stage pushes, merges and (yes!) deploys it.
3
+
4
+ *You should always run stagecoach from the root directory of your repo*
5
+ Otherwise it may (will) break.
6
+
7
+ The first time you run stagecoach it will ask you for some information, namely your redmine/planio repo URL and your API key for this repo.
8
+ It will also install a custom commit-msg git hook (if you ask it to) - for more information, see below.
9
+
10
+ All stagecoach config is saved in /path/to/your/repo/.stagecoach which is created at initial setup and added to your global .gitignore. This is a yaml file with fairly obvious syntax
11
+ so if you need to remove a branch or edit the issue number that a branch points to, it is possible (although not necessarily recommended) to edit it.
12
+
13
+ Init Stage
14
+ ==========
15
+ stagecoach -p[lanio] 4115 (OR) -g[ithub] 525 -b[ranch] my_new_branch
16
+
17
+ You can also just run stagecoach without any flags and it will allow you to enter this stuff manually.
18
+
19
+ To get started, all stagecoach needs from you is the issue number you are working on (redmine/planio or github) and a new branch name. You /can/ use an existing branch if it
20
+ is up to date with your master branch. If it is not, stagecoach will squawk and die and you will have to bring the branch up to date, or use a new one.
21
+
22
+ If you are working from a redmine/planio issue, stagecoach sets the issue to 'In Progress'. Currently it does not assign the issue to you, but you have the option to view the issue
23
+ in your browser and do this manually. It also sets up a git issue for you to reference in your commits (see commit-msg githook).
24
+
25
+ If you are working from a github issue, we can all get on with our lives.
26
+
27
+ Coding Stage
28
+ ============
29
+ Future versions of stagecoach may do the coding for you, but at the moment you have to do this part manually.
30
+ Just code and commit, code and commit until your feature or fix is ready.
31
+
32
+ Commit-msg githook
33
+ ------------------
34
+ If you opt to install the commit-msg githook during initial setup (stagecoach -s) then your commit messages will be automatically referenced to the github issue of the branch you are in
35
+ (this only applies to branches created or registered in Stagecoach).
36
+
37
+ - to reference a different issue from a commit, simply refer to it as normal with #xxx in the commit message. The git-hook will leave your message alone.
38
+ - to make no reference at all, you can use the #noref tag in the commit message. The git-hook
39
+ - to close an issue with a commit, use the #closes tag.
40
+
41
+ For more information, see
42
+ - the githook itself at /path/to/your/repo/.git/hooks/commit-msg
43
+ - http://book.git-scm.com/5_git_hooks.html
44
+
45
+ Deploy Stage
46
+ ============
47
+ stagecoach -d[eploy]
48
+
49
+ This automates the entire deploy workflow for you as follows:
50
+
51
+ git push origin new_branch_name
52
+ git checkout staging
53
+ git pull
54
+ git merge task_name
55
+ git push origin staging
56
+ cap staging deploy
57
+ set redmine/planio ticket to 'feedback' status (if applicable)
58
+
59
+ Sample usage:
60
+ stagecoach -p 4115 -b new_branch_name
61
+ [code, commit until feature or fix is complete]
62
+ stagecoach -d
63
+
64
+ Flags
65
+ --branch, -b: Enter your new branch name here, eg. stagecoach -b new_branch (optional)
66
+ --planio, -p: Enter your planio issue number here, eg. stagecoach -p 1234 (optional)
67
+ --github, -g: Enter your github issue number here, eg. stagecoach -g 1234 (optional)
68
+ --deploy, -d: Use this option to skip straight to push & deploy if you have already pulled from master and created your new branch
69
+ --setup, -s: Use this the first time you run stagecoach to save your redmine repository/api key and install the commit-msg githook if desired
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "redmine_stagecoach"
18
+ gem.homepage = "http://github.com/omnikron/stagecoach"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Stagecoach is in ur Redmine, automating ur Git workflow.}
21
+ gem.description = %Q{Git/capistrano workflow automation script with Redmine & Github issue integration}
22
+ gem.email = "o.barnett@digitaleseiten.de"
23
+ gem.authors = ["Oli Barnett"]
24
+ gem.executables = ['stagecoach']
25
+ # dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ test.rcov_opts << '--exclude "gems/*"'
42
+ end
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "stagecoach #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
data/bin/stagecoach ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ require File.dirname(__FILE__) + '/../lib/stagecoach.rb'
4
+
5
+ CONFIG_FILE = `pwd`.chomp + '/.stagecoach'
6
+
7
+ module Stagecoach
8
+ staging = 'staging'
9
+ master = 'master'
10
+
11
+ # Command line options courtesy of the Trollop gem.
12
+ opts = CommandLine.trollop
13
+
14
+ # Initial setup with -s flag.
15
+ if opts[:setup]
16
+ Config.setup
17
+ end
18
+
19
+ # Initial setup without -s flag.
20
+ unless File.exist?(CONFIG_FILE)
21
+ Config.setup
22
+ end
23
+
24
+ # Load config file to a hash.
25
+ config = Config.yaml_to_hash
26
+
27
+ # Checks validity of argument variables.
28
+ # Ignore these checks with -t flag.
29
+ unless opts[:testing_given]
30
+ Config.setup if config["redmine_site"] == "none"
31
+ Config.setup if config["redmine_api"] == "none"
32
+
33
+ # Checks that command-line args are present and correct.
34
+ Trollop::die :planio, "issue number can only contain digits" if opts[:planio] && opts[:planio][/\D/]
35
+ Trollop::die :github, "issue number can only contain digits" if opts[:github] && opts[:github][/\D/]
36
+ Trollop::die :branch, "name must be longer than 1 character" if opts[:branch] && opts[:branch].length <= 1
37
+
38
+ # You can't give a planio and a github issue number (for the moment)
39
+ if opts[:github] && opts[:planio]
40
+ puts "You can't enter a github issue at the same time as a planio issue at the moment. Make up your mind!\nExiting..."
41
+ exit
42
+ end
43
+ end
44
+
45
+ # Set up redmine client config.
46
+ RedmineApi::Client.instance_eval do
47
+ self.site = config["redmine_site"]
48
+ self.user = config["redmine_api_key"]
49
+ end
50
+
51
+ # Checks for uncommitted/unstashed changes and aborts if present.
52
+ if Git.changes != ''
53
+ puts "You have uncommitted changes:".red
54
+ puts Git.changes
55
+ puts "Please commit or stash these changes before running Stagecoach. -h for help."
56
+ puts "Exiting..."
57
+ exit
58
+ end
59
+
60
+ # Initial stage - set up branch and git issue.
61
+
62
+ unless opts[:deploy]
63
+
64
+ # If no issue argument has been given.
65
+ if opts[:github].nil? && opts[:planio].nil?
66
+ print "Are you working on a [P]lanio or a [G]ithub issue: "
67
+ case STDIN.gets.chomp
68
+ when 'P'
69
+ print "Please enter your Planio issue number: "
70
+ opts[:planio] = gets.chomp
71
+ when 'G'
72
+ print "Please enter your Github issue number: "
73
+ opts[:github] = gets.chomp
74
+ end
75
+ end
76
+
77
+ # Check that the planio issue is not already assigned.
78
+ if opts[:planio]
79
+ planio_issue_number = opts[:planio]
80
+ planio_issue = Redmine.issue(planio_issue_number)
81
+ if planio_issue.status.id != '1'
82
+ puts "Warning!".red
83
+ puts "This issue is in status '#{planio_issue.status.name}'"
84
+ begin
85
+ puts "It is assigned to #{planio_issue.assigned_to.name}"
86
+ rescue
87
+ puts "But it is not assigned to anybody yet."
88
+ end
89
+ print "Continue? [Y]es or [Q]uit: "
90
+ case STDIN.gets.chomp
91
+ when 'Y'
92
+ when 'Q'
93
+ exit
94
+ end
95
+ end
96
+
97
+ # Set the planio issue status to 'In Bearbeitung'
98
+ planio_issue.status_id = 2
99
+ planio_issue.save
100
+ end
101
+
102
+ # TODO: Check that the github issue is not already assigned.
103
+
104
+ CommandLine.line_break
105
+ puts "Stagecoach: initial stage"
106
+
107
+ # Change to master, pull changes, and create a new branch.
108
+ CommandLine.line_break
109
+ puts "Switching to master branch"
110
+ #
111
+ # TODO if there is a file that has been git added but not git committed, it
112
+ # will pop up at this point looking confusing (eg. "A test_file").
113
+ # Handle this better?
114
+ #
115
+ Git.checkout(master)
116
+ puts "Pulling changes:"
117
+ Git.pull
118
+ if opts[:branch]
119
+ new_branch = opts[:branch]
120
+ else
121
+ print "Please enter a new git branch name for your changes (branch will be created): "
122
+ new_branch = STDIN.gets.chomp
123
+ end
124
+
125
+ # Check that the new branch isn't master, because that would be silly
126
+ case new_branch
127
+ when 'master', 'Master'
128
+ puts "You can't use stagecoach to deploy your master branch.\nExiting..."
129
+ exit
130
+ end
131
+
132
+ # Make sure new local branch does not already exist.
133
+ if Git.branch_exist?(new_branch)
134
+ puts "There is already a local branch called #{new_branch}."
135
+ if Git.diff(master, new_branch) == ""
136
+ print "#{new_branch} is up to date with master. [U]se or [Q]uit: "
137
+ else
138
+ puts "#{new_branch} is not up to date with master. Please use a different branch or update this one.".red
139
+ puts "Exiting..."
140
+ CommandLine.line_break
141
+ puts "The following files in branch '#{new_branch}' differ from their master branch versions:"
142
+ puts Git.diff(master, new_branch)
143
+ CommandLine.line_break
144
+ exit
145
+ end
146
+ case STDIN.gets.chomp
147
+ when 'U'
148
+ Git.change_to_branch(new_branch)
149
+ when 'Q'
150
+ exit
151
+ end
152
+ else
153
+ Git.new_branch(new_branch)
154
+ end
155
+
156
+ # Ugly code, pretty output...
157
+ CommandLine.line_break
158
+
159
+ # Issue handling.
160
+ if opts[:github]
161
+ config[Git.current_branch] = {:github_issue => opts[:github]}
162
+ #TODO check that github issue is not assigned to somebody already
163
+ elsif opts[:planio]
164
+ config[Git.current_branch] = {:planio_issue => planio_issue_number}
165
+ end
166
+
167
+ issue = config[Git.current_branch]
168
+
169
+ # Set up the related issue for this branch.
170
+ if planio_issue = issue[:planio_issue]
171
+ begin
172
+ puts "Searching for issue number #{planio_issue}..."
173
+ redmine_issue = Redmine.issue(planio_issue)
174
+ rescue ActiveResource::ResourceNotFound => e
175
+ puts e.message
176
+ exit
177
+ end
178
+ puts "Issue found: #{redmine_issue.subject}\n"
179
+
180
+ # Create a Github issue referencing the planio issue.
181
+ puts "Creating Git issue with subject: " + redmine_issue.subject
182
+ body = "Planio issue: #{Redmine.issue_url(redmine_issue)} \n\n #{redmine_issue.description}"
183
+
184
+ # Create a Git issue.
185
+ github_issue = Git.new_issue(redmine_issue.subject, body)
186
+ github_issue_id = github_issue[/\d+/]
187
+
188
+ # Save it so we can reference it in commits using the magic of git hooks!
189
+ config[Git.current_branch] = {:github_issue => github_issue_id, :planio_issue => planio_issue}
190
+
191
+ print "Would you like to edit the issue on Github? [Y]es or anything else to continue: "
192
+
193
+ if STDIN.gets.chomp == 'Y'
194
+ Git.view_issue(github_issue_id)
195
+ else
196
+ end
197
+ end
198
+
199
+ # Github issues are easier.
200
+ if issue[:github]
201
+ #TODO what happens if no github issue is found?
202
+ puts "Searching for github issue number #{issue[:number]}..."
203
+ github_issue = Git.issue(issue[:number])
204
+ puts "Issue found: #{github_issue} \n"
205
+ end
206
+
207
+ # Saves the branch-specific details for later.
208
+ Config.save(config)
209
+ puts "Happy coding! Run stagecoach -d when you're ready to deploy."
210
+ end
211
+
212
+ # ------------------------------------------------------------------
213
+ # Deploy stage.
214
+ if opts[:deploy]
215
+
216
+ # Get the current git branch
217
+ branch = Git.current_branch
218
+
219
+ # There's no point in deploying without any commits
220
+ unless Git.branch_has_commits?(branch)
221
+ puts "You don't have any uncommitted changes on branch #{branch}. Please make some commits before running stagecoach!\nExiting..."
222
+ exit
223
+ end
224
+
225
+ # You never know! Display git status in case there are any nasty surprises.
226
+ unless Git.status =~ /nothing to commit/
227
+ CommandLine.line_break
228
+ puts "You have a dirty git branch:\n".red
229
+ puts Git.status
230
+ CommandLine.line_break
231
+ print "[D]eploy anyway".red
232
+ print " or "
233
+ print "[anything else] to cancel: ".green
234
+ case STDIN.gets.chomp
235
+ when "D"
236
+ CommandLine.line_break
237
+ puts "DEPLOYING:"
238
+ CommandLine.line_break
239
+ else
240
+ puts "Exiting..."
241
+ exit
242
+ end
243
+ end
244
+
245
+
246
+ # Stop anybody deploying master to staging...
247
+ case branch
248
+ when 'master', 'Master'
249
+ puts "You can't use stagecoach to deploy your master branch.\nExiting..."
250
+ exit
251
+ end
252
+
253
+ # Finally, push, merge and deploy!
254
+ Git.push(branch)
255
+ Git.merge(staging, branch)
256
+ Git.push(staging)
257
+ Capistrano.deploy(staging)
258
+ Git.change_to_branch(master)
259
+
260
+ # Planio issue to feedback status
261
+ if planio_issue_number = config[branch][:planio_issue]
262
+ CommandLine.line_break
263
+ puts "Attempting to change Planio ticket status to 'Feedback' for you:"
264
+ issue = Redmine.issue(planio_issue_number)
265
+ issue.status_id = 4
266
+ issue.save
267
+ Redmine.view_issue(issue)
268
+ end
269
+ end
270
+ end
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Git commit-msg hook. If your branch name is in the form "t123", automatically
4
+ # adds "Refs #123." to commit messages unless they mention "#123" already.
5
+ # Include "#close" or "#finish" to add "Closes #123."
6
+ #
7
+ # For Pivotal Tracker, branch names like "s123" adds "[#123]".
8
+ # Include "#close" or "#finish" to add "[Finishes #123]".
9
+ #
10
+ # If you include "#noref" in the commit message, nothing will be added to
11
+ # the commit message, and the "#noref" itself will be stripped.
12
+ #
13
+ # By Henrik Nyh <http://henrik.nyh.se> 2009-09-10 under the MIT License.
14
+ #
15
+ #
16
+ # Install:
17
+ #
18
+ # cd your_project
19
+ # stick it in .git/hooks/commit-msg && chmod u+x .git/hooks/commit-msg
20
+ #
21
+ # Or store it centrally and symlink in your projects:
22
+ # TODO Replace ~./.githooks etc with the file location in the gem
23
+ # ~/.githooks/commit-msg && chmod u+x ~/.githooks/commit-msg
24
+ # cd your_project
25
+ # ~/.githooks/commit-msg .git/hooks
26
+
27
+ require 'yaml'
28
+
29
+ # Custom method to check for installed redmine_stagecoach gem
30
+ def gem_available?(name)
31
+ Gem::Specification.find_by_name(name)
32
+ rescue Gem::LoadError
33
+ false
34
+ rescue
35
+ Gem.available?(name)
36
+ end
37
+
38
+ if gem_available?('redmine_stagecoach') == false
39
+ exit
40
+ else
41
+ config = YAML::load(File.open(File.dirname(__FILE__) + '/../../.stagecoach', 'r'))
42
+ end
43
+
44
+ # Find out what branch we are on
45
+ def branches
46
+ `git branch`.split("\n")
47
+ end
48
+
49
+ def current_branch
50
+ branches.each do |b|
51
+ if b =~ /\*/
52
+ return b[1..-1].strip
53
+ end
54
+ end
55
+ end
56
+
57
+
58
+ # And now the git hook stuff
59
+
60
+ FLAGS = [
61
+ NOREF = "noref",
62
+ UP_NOREF = "Noref",
63
+ CAPS_NOREF = "NOREF",
64
+ CLOSE = "close",
65
+ UP_CLOSE= "Close",
66
+ CAPS_CLOSE= "CLOSE"
67
+ ]
68
+
69
+ NO_REFERENCE_FLAGS = [ NOREF, UP_NOREF, CAPS_NOREF ]
70
+ CLOSING_FLAGS = [ CLOSE, UP_CLOSE, CAPS_CLOSE ]
71
+
72
+ begin
73
+ ticket_number = config[current_branch][:github_issue]
74
+ rescue
75
+ exit
76
+ end
77
+ finish = "Closes #%s" % ticket_number
78
+ reference = "#%s" % ticket_number
79
+
80
+ message_file = ARGV[0]
81
+ message = File.read(message_file).strip
82
+ exit if message.include?("##{ticket_number}")
83
+ exit if message =~ /#\d+/
84
+
85
+ # Determine if any of the flags are included. Make a note of which and then remove it.
86
+ message.sub!(/(?:^|\s)#(#{Regexp.union(*FLAGS)})\b/, '')
87
+ flag = $1
88
+
89
+ message =
90
+ case flag
91
+ when *NO_REFERENCE_FLAGS
92
+ message
93
+ when *CLOSING_FLAGS
94
+ [ message, finish ].join(" ")
95
+ else
96
+ [ message, reference ].join(" ")
97
+ end
98
+
99
+ File.open(message_file, 'w') {|f| f.write message }
data/lib/stagecoach.rb ADDED
@@ -0,0 +1,7 @@
1
+ lib = File.dirname(__FILE__)
2
+
3
+ require lib + '/stagecoach/config'
4
+ require lib + '/stagecoach/git'
5
+ require lib + '/stagecoach/redmine'
6
+ require lib + '/stagecoach/command_line.rb'
7
+ require lib + '/stagecoach/capistrano.rb'
@@ -0,0 +1,11 @@
1
+ module Stagecoach
2
+ class Capistrano
3
+ class << self
4
+ def deploy(branch)
5
+ CommandLine.line_break
6
+ puts "Deploying staging"
7
+ puts `bundle exec cap #{branch} deploy`
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,84 @@
1
+ module Stagecoach
2
+ class CommandLine
3
+ def self.line_break
4
+ puts "\n"
5
+ end
6
+
7
+ def self.trollop
8
+ require 'trollop'
9
+ # Command line options using Trollop.
10
+ Trollop::options do
11
+ banner <<-EOS
12
+ stagecoach works in two stages, init and deploy. The init stage creates a branch based on a redmine/planio issue, and the deploy stage pushes, merges and (yes!) deploys it.
13
+
14
+ #{"You should always run stagecoach from the root directory of your repo".red}
15
+ Otherwise it may (will) break.
16
+
17
+ The first time you run stagecoach it will ask you for some information, namely your redmine/planio repo URL and your API key for this repo.
18
+ It will also install a custom commit-msg git hook (if you ask it to) - for more information, see below.
19
+
20
+ All stagecoach config is saved in /path/to/your/repo/.stagecoach which is created at initial setup and added to your global .gitignore. This is a yaml file with fairly obvious syntax
21
+ so if you need to remove a branch or edit the issue number that a branch points to, it is possible (although not necessarily recommended) to edit it.
22
+
23
+ Init Stage
24
+ ----------
25
+ stagecoach -p[lanio] 4115 (OR) -g[ithub] 525 -b[ranch] my_new_branch
26
+
27
+ You can also just run stagecoach without any flags and it will allow you to enter this stuff manually.
28
+
29
+ To get started, all stagecoach needs from you is the issue number you are working on (redmine/planio or github) and a new branch name. You /can/ use an existing branch if it
30
+ is up to date with your master branch. If it is not, stagecoach will squawk and die and you will have to bring the branch up to date, or use a new one.
31
+
32
+ If you are working from a redmine/planio issue, stagecoach sets the issue to 'In Progress'. Currently it does not assign the issue to you, but you have the option to view the issue
33
+ in your browser and do this manually. It also sets up a git issue for you to reference in your commits (see commit-msg githook).
34
+
35
+ If you are working from a github issue, we can all get on with our lives.
36
+
37
+ Coding Stage
38
+ ------------
39
+ Future versions of stagecoach may do the coding for you, but at the moment you have to do this part manually.
40
+ Just code and commit, code and commit until your feature or fix is ready.
41
+
42
+ #{"commit-msg githook".green}
43
+ If you opt to install the commit-msg githook during initial setup (stagecoach -s) then your commit messages will be automatically referenced to the github issue of the branch you are in
44
+ (this only applies to branches created or registered in Stagecoach).
45
+
46
+ - to reference a different issue from a commit, simply refer to it as normal with #xxx in the commit message. The git-hook will leave your message alone.
47
+ - to make no reference at all, you can use the #noref tag in the commit message. The git-hook
48
+ - to close an issue with a commit, use the #closes tag.
49
+
50
+ For more information, see
51
+ - the githook itself at /path/to/your/repo/.git/hooks/commit-msg
52
+ - http://book.git-scm.com/5_git_hooks.html
53
+
54
+ Deploy Stage
55
+ ------------
56
+ stagecoach -d[eploy]
57
+
58
+ This automates the entire deploy workflow for you as follows:
59
+
60
+ git push origin new_branch_name
61
+ git checkout staging
62
+ git pull
63
+ git merge task_name
64
+ git push origin staging
65
+ cap staging deploy
66
+ set redmine/planio ticket to 'feedback' status (if applicable)
67
+
68
+ #{"Sample usage:".green}
69
+ stagecoach -p 4115 -b new_branch_name
70
+ [code, commit until feature or fix is complete]
71
+ stagecoach -d
72
+
73
+ #{"Flags".red}
74
+ EOS
75
+ opt :branch, "Enter your new branch name here, eg. stagecoach -b new_branch (optional)", :type => :string
76
+ opt :planio, "Enter your planio issue number here, eg. stagecoach -p 1234 (optional)", :type => :string
77
+ opt :github, "Enter your github issue number here, eg. stagecoach -g 1234 (optional)", :type => :string
78
+ opt :deploy, "Use this option to skip straight to push & deploy if you have already pulled from master and created your new branch"
79
+ opt :setup, "Use this the first time you run stagecoach to save your redmine repository and api key"
80
+ opt :testing, "Dev testing tool"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,125 @@
1
+ require 'yaml'
2
+ require 'FileUtils'
3
+
4
+ module Stagecoach
5
+ class Config
6
+ class << self
7
+ def new
8
+ File.open(CONFIG_FILE, 'w') { |f| f.write("---\nredmine_site: none\nredmine_api_key: none")}
9
+ end
10
+
11
+ def open
12
+ File.open(CONFIG_FILE, 'r+')
13
+ end
14
+
15
+ def yaml_to_hash
16
+ YAML::load(Config.open)
17
+ end
18
+
19
+ def save(hash, config_file = Config.open)
20
+ config_file.write(hash.to_yaml)
21
+ end
22
+
23
+ def githook_install(source_dir, install_dir, file)
24
+ FileUtils.cp(source_dir + file, install_dir + file)
25
+ puts 'OK!'
26
+ puts 'Making githook executable (may require admin password)'
27
+ FileUtils.chmod(0711, ( install_dir + file ))
28
+ puts 'OK!'
29
+ end
30
+
31
+ def setup
32
+ # Say hello
33
+ CommandLine.line_break
34
+ puts "Stagecoach Initial Setup"
35
+ CommandLine.line_break
36
+
37
+ # Now scare everybody away again
38
+ puts "You are running stagecoach from #{FileUtils.pwd.green}. Is this the root directory of your repository?"
39
+ puts "Stagecoach may not work properly anywhere else! So proceed with caution"
40
+ CommandLine.line_break
41
+ print "[C]ontinue or [Q]uit: "
42
+
43
+ # Create a config file if necessary
44
+ case STDIN.gets.chomp
45
+ when 'C'
46
+ Config.new unless File.exist?(CONFIG_FILE)
47
+ when 'Q'
48
+ puts "Exiting..."
49
+ exit
50
+ end
51
+
52
+ # Tell git to ignore the stagecoach config file
53
+ Git.global_ignore('.stagecoach')
54
+
55
+ # Install the commit-msg githook if it is not already there:
56
+ source_dir = (File.dirname(__FILE__) + '/../githooks/')
57
+ install_dir = FileUtils.pwd + '/.git/hooks/'
58
+ git_hook = 'commit-msg'
59
+
60
+ CommandLine.line_break
61
+ puts "Would you like to install the stagecoach #{"commit-msg githook".green}?"
62
+ puts "This automatically references stagecoach-created github issues from each commit you make"
63
+ puts "Note that this will only affect branches created in stagecoach. For more information run stagecoach -h"
64
+ CommandLine.line_break
65
+ print "[I]nstall or [S]kip this step: "
66
+ loop do
67
+ case STDIN.gets.chomp
68
+ when 'I'
69
+ if File.exist?(install_dir + git_hook)
70
+ case FileUtils.compare_file(source_dir + git_hook, install_dir + git_hook)
71
+ when true
72
+ puts 'The stagecoach githook is already installed in this repo. Skipping this step...'
73
+ break
74
+ when false
75
+ puts "You have a commit-msg githook already. Are you sure you want to install? This will #{'overwrite'.red} your current commit-msg githook."
76
+ print "Type [overwrite] to continue or anything else to skip installation: "
77
+ case STDIN.gets.chomp
78
+ when 'overwrite'
79
+ Config.githook_install(source_dir, install_dir, git_hook)
80
+ break
81
+ else
82
+ break
83
+ end
84
+ end
85
+ else
86
+ puts "Installing..."
87
+ Config.githook_install(source_dir, install_dir, git_hook)
88
+ break
89
+ end
90
+ when 'S'
91
+ puts 'Skipping Installation.'
92
+ break
93
+ end
94
+ end
95
+
96
+ # TODO Some verification of the input at this stage, for example test the
97
+ # connection and have the user re-enter the details if necessary
98
+ # http://api.rubyonrails.org/classes/ActiveResource/Connection.html#method-i-head
99
+ loop do
100
+ CommandLine.line_break
101
+ print "Enter your redmine/planio repository, eg. https://digitaleseiten.plan.io: "
102
+ redmine_repo = STDIN.gets.chomp
103
+ print "Enter your API key for that repo: "
104
+ redmine_api_key = STDIN.gets.chomp
105
+
106
+ Config.save({"redmine_site" => redmine_repo, "redmine_api_key" => redmine_api_key})
107
+
108
+ CommandLine.line_break
109
+ puts "Settings saved OK:"
110
+ puts "Repository: " + redmine_repo
111
+ puts "API Key: " + redmine_api_key
112
+ CommandLine.line_break
113
+ puts "Exiting..."
114
+ exit
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ class String
122
+ def red; colorize(self, "\e[1m\e[31m"); end
123
+ def green; colorize(self, "\e[32m"); end
124
+ def colorize(text, color_code) "#{color_code}#{text}\e[0m" end
125
+ end
@@ -0,0 +1,123 @@
1
+ module Stagecoach
2
+ class Git
3
+ class << self
4
+ def branches
5
+ `git branch`.split("\n")
6
+ end
7
+
8
+ def global_ignore(filename)
9
+ gitignore = File.open(Dir.home + '/.gitignore', 'a+')
10
+
11
+ # Check if filename is ignored already and ignores it if not
12
+ unless File.read(gitignore) =~ /\.stagecoach/
13
+ gitignore.puts(filename)
14
+ end
15
+ end
16
+
17
+ def changes
18
+ `git diff-files --name-status -r --ignore-submodules`
19
+ end
20
+
21
+ def status
22
+ `git status`
23
+ end
24
+
25
+ def current_branch
26
+ branches.each do |b|
27
+ if b =~ /\*/
28
+ return b[1..-1].strip
29
+ end
30
+ end
31
+ end
32
+
33
+ def correct_branch?
34
+ CommandLine.line_break
35
+ print "You are currently in local branch: #{Git.current_branch.red} \nAre these details correct? ([Y]es or [Q]uit): "
36
+ case STDIN.gets.chomp
37
+ when "Y"
38
+ when "Q"
39
+ exit
40
+ else
41
+ puts "Please enter Y to continue or Q to quit."
42
+ end
43
+ end
44
+
45
+ def new_branch(branch)
46
+ CommandLine.line_break
47
+ `git checkout -b #{branch}`
48
+ end
49
+
50
+ def change_to_branch(branch)
51
+ CommandLine.line_break
52
+ puts "Changing to branch '#{branch}'"
53
+ if branch_exist?(branch)
54
+ `git checkout #{branch}`
55
+ else
56
+ print "Branch '#{branch}' does not exist. [C]reate or [Q]uit: "
57
+ case STDIN.gets.chomp
58
+ when 'C'
59
+ new_branch(branch)
60
+ when 'Q'
61
+ exit
62
+ end
63
+ end
64
+ end
65
+
66
+ def diff(branch1, branch2)
67
+ diff = `git diff --name-status #{branch1}..#{branch2}`
68
+ return diff
69
+ end
70
+
71
+
72
+ def merge(to_branch, from_branch)
73
+ CommandLine.line_break
74
+ puts "Merging into #{to_branch} (after pulling updates)"
75
+ Git.change_to_branch(to_branch)
76
+ puts `git pull origin #{to_branch}`
77
+ puts `git merge #{from_branch}`
78
+ raise 'merge failed' if $?.exitstatus != 0
79
+ end
80
+
81
+ def push(branch)
82
+ CommandLine.line_break
83
+ puts "Pushing your changes to branch '#{branch}'"
84
+ puts `git push origin #{branch}`
85
+ end
86
+
87
+
88
+ def checkout(branch)
89
+ puts `git checkout #{branch}`
90
+ end
91
+
92
+ def pull
93
+ puts `git pull`
94
+ end
95
+
96
+ def branch_exist?(branch)
97
+ branches.find { |e| /#{branch}/ =~ e }
98
+ end
99
+
100
+ def new_issue(title, description)
101
+ `ghi -o "#{title}" -m "#{description}"`
102
+ end
103
+
104
+ def branch_has_commits?(branch)
105
+ log = `git log --branches --not --remotes --simplify-by-decoration --decorate --oneline`
106
+ if log.include? branch
107
+ return true
108
+ else
109
+ return false
110
+ end
111
+ end
112
+
113
+ def view_issue(github_issue)
114
+ issue_url = `ghi -u#{github_issue}`
115
+ `open #{issue_url}`
116
+ end
117
+
118
+ def issue(id)
119
+ `ghi -l #{id}`
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'active_resource'
3
+
4
+ module RedmineApi
5
+ class Client < ActiveResource::Base; end
6
+ class Issue < RedmineApi::Client; end
7
+ end
8
+
9
+ module Stagecoach
10
+ class Redmine
11
+ def self.issue(issue_number)
12
+ return RedmineApi::Issue.find(issue_number)
13
+ end
14
+
15
+ def self.issue_url(issue)
16
+ RedmineApi::Client.site + "/issues/" + issue.id
17
+ end
18
+
19
+ # Open the issue in a browser.
20
+ def self.view_issue(issue)
21
+ issue_url = Redmine.issue_url(issue)
22
+ print "Open planio issue in browser? [Y]es or anything else to exit: "
23
+ `open #{issue_url.to_s}` if gets.chomp == "Y"
24
+ puts "Staging completed! Exiting..."
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "redmine_stagecoach"
8
+ s.version = "0.5.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Oli Barnett"]
12
+ s.date = "2012-01-24"
13
+ s.description = "Git/capistrano workflow automation script with Redmine & Github issue integration"
14
+ s.email = "o.barnett@digitaleseiten.de"
15
+ s.executables = ["stagecoach"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".rvmrc",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.rdoc",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/stagecoach",
30
+ "lib/.DS_Store",
31
+ "lib/githooks/commit-msg",
32
+ "lib/stagecoach.rb",
33
+ "lib/stagecoach/capistrano.rb",
34
+ "lib/stagecoach/command_line.rb",
35
+ "lib/stagecoach/config.rb",
36
+ "lib/stagecoach/git.rb",
37
+ "lib/stagecoach/redmine.rb",
38
+ "redmine_stagecoach.gemspec",
39
+ "test/helper.rb",
40
+ "test/test_stagecoach.rb"
41
+ ]
42
+ s.homepage = "http://github.com/omnikron/stagecoach"
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = "1.8.15"
46
+ s.summary = "Stagecoach is in ur Redmine, automating ur Git workflow."
47
+
48
+ if s.respond_to? :specification_version then
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_runtime_dependency(%q<ghi>, [">= 0"])
53
+ s.add_runtime_dependency(%q<trollop>, [">= 0"])
54
+ s.add_runtime_dependency(%q<capistrano>, [">= 0"])
55
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
56
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
57
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
58
+ s.add_development_dependency(%q<rcov>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<ghi>, [">= 0"])
61
+ s.add_dependency(%q<trollop>, [">= 0"])
62
+ s.add_dependency(%q<capistrano>, [">= 0"])
63
+ s.add_dependency(%q<shoulda>, [">= 0"])
64
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
65
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
66
+ s.add_dependency(%q<rcov>, [">= 0"])
67
+ end
68
+ else
69
+ s.add_dependency(%q<ghi>, [">= 0"])
70
+ s.add_dependency(%q<trollop>, [">= 0"])
71
+ s.add_dependency(%q<capistrano>, [">= 0"])
72
+ s.add_dependency(%q<shoulda>, [">= 0"])
73
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
74
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
75
+ s.add_dependency(%q<rcov>, [">= 0"])
76
+ end
77
+ end
78
+
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'stagecoach'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestStagecoach < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redmine_stagecoach
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Oli Barnett
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-24 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ghi
16
+ requirement: &70166234091840 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70166234091840
25
+ - !ruby/object:Gem::Dependency
26
+ name: trollop
27
+ requirement: &70166234091360 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70166234091360
36
+ - !ruby/object:Gem::Dependency
37
+ name: capistrano
38
+ requirement: &70166234090880 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70166234090880
47
+ - !ruby/object:Gem::Dependency
48
+ name: shoulda
49
+ requirement: &70166234090400 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70166234090400
58
+ - !ruby/object:Gem::Dependency
59
+ name: bundler
60
+ requirement: &70166234089920 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.0.0
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70166234089920
69
+ - !ruby/object:Gem::Dependency
70
+ name: jeweler
71
+ requirement: &70166234089440 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.6.4
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70166234089440
80
+ - !ruby/object:Gem::Dependency
81
+ name: rcov
82
+ requirement: &70166234088960 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70166234088960
91
+ description: Git/capistrano workflow automation script with Redmine & Github issue
92
+ integration
93
+ email: o.barnett@digitaleseiten.de
94
+ executables:
95
+ - stagecoach
96
+ extensions: []
97
+ extra_rdoc_files:
98
+ - LICENSE.txt
99
+ - README.rdoc
100
+ files:
101
+ - .document
102
+ - .rvmrc
103
+ - Gemfile
104
+ - Gemfile.lock
105
+ - LICENSE.txt
106
+ - README.rdoc
107
+ - Rakefile
108
+ - VERSION
109
+ - bin/stagecoach
110
+ - lib/.DS_Store
111
+ - lib/githooks/commit-msg
112
+ - lib/stagecoach.rb
113
+ - lib/stagecoach/capistrano.rb
114
+ - lib/stagecoach/command_line.rb
115
+ - lib/stagecoach/config.rb
116
+ - lib/stagecoach/git.rb
117
+ - lib/stagecoach/redmine.rb
118
+ - redmine_stagecoach.gemspec
119
+ - test/helper.rb
120
+ - test/test_stagecoach.rb
121
+ homepage: http://github.com/omnikron/stagecoach
122
+ licenses:
123
+ - MIT
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ segments:
135
+ - 0
136
+ hash: 2500404004806684746
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 1.8.15
146
+ signing_key:
147
+ specification_version: 3
148
+ summary: Stagecoach is in ur Redmine, automating ur Git workflow.
149
+ test_files: []