zenflow 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.zenflow +10 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +95 -0
- data/Guardfile +6 -0
- data/LICENSE.md +22 -0
- data/README.markdown +92 -0
- data/VERSION.yml +5 -0
- data/bin/zenflow +17 -0
- data/lib/zenflow.rb +35 -0
- data/lib/zenflow/cli.rb +130 -0
- data/lib/zenflow/commands/deploy.rb +33 -0
- data/lib/zenflow/commands/feature.rb +10 -0
- data/lib/zenflow/commands/hotfix.rb +15 -0
- data/lib/zenflow/commands/release.rb +16 -0
- data/lib/zenflow/commands/reviews.rb +12 -0
- data/lib/zenflow/helpers/ask.rb +66 -0
- data/lib/zenflow/helpers/branch.rb +80 -0
- data/lib/zenflow/helpers/branch_command.rb +95 -0
- data/lib/zenflow/helpers/branch_commands/abort.rb +21 -0
- data/lib/zenflow/helpers/branch_commands/branches.rb +21 -0
- data/lib/zenflow/helpers/branch_commands/compare.rb +20 -0
- data/lib/zenflow/helpers/branch_commands/deploy.rb +29 -0
- data/lib/zenflow/helpers/branch_commands/diff.rb +19 -0
- data/lib/zenflow/helpers/branch_commands/finish.rb +68 -0
- data/lib/zenflow/helpers/branch_commands/review.rb +58 -0
- data/lib/zenflow/helpers/branch_commands/start.rb +39 -0
- data/lib/zenflow/helpers/branch_commands/update.rb +22 -0
- data/lib/zenflow/helpers/changelog.rb +100 -0
- data/lib/zenflow/helpers/config.rb +36 -0
- data/lib/zenflow/helpers/github.rb +40 -0
- data/lib/zenflow/helpers/help.rb +37 -0
- data/lib/zenflow/helpers/log.rb +21 -0
- data/lib/zenflow/helpers/pull_request.rb +68 -0
- data/lib/zenflow/helpers/repo.rb +13 -0
- data/lib/zenflow/helpers/shell.rb +89 -0
- data/lib/zenflow/helpers/version.rb +87 -0
- data/lib/zenflow/version.rb +3 -0
- data/spec/fixtures/VERSION.yml +5 -0
- data/spec/fixtures/cassettes/create_bad_pull_request.yml +57 -0
- data/spec/fixtures/cassettes/create_pull_request.yml +68 -0
- data/spec/fixtures/cassettes/existing_pull_request.yml +145 -0
- data/spec/fixtures/cassettes/pull_request_by_ref.yml +145 -0
- data/spec/fixtures/cassettes/pull_request_find.yml +70 -0
- data/spec/fixtures/cassettes/pull_request_for_non-existent_ref.yml +145 -0
- data/spec/fixtures/cassettes/pull_request_list.yml +74 -0
- data/spec/fixtures/cassettes/unexisting_pull_request.yml +145 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/shared_examples_for_version_number.rb +19 -0
- data/spec/zenflow/commands/deploy_spec.rb +48 -0
- data/spec/zenflow/commands/feature_spec.rb +11 -0
- data/spec/zenflow/commands/hotfix_spec.rb +14 -0
- data/spec/zenflow/commands/release_spec.rb +15 -0
- data/spec/zenflow/commands/reviews_spec.rb +15 -0
- data/spec/zenflow/helpers/ask_spec.rb +115 -0
- data/spec/zenflow/helpers/branch_command_spec.rb +310 -0
- data/spec/zenflow/helpers/branch_spec.rb +300 -0
- data/spec/zenflow/helpers/changelog_spec.rb +188 -0
- data/spec/zenflow/helpers/cli_spec.rb +277 -0
- data/spec/zenflow/helpers/github_spec.rb +45 -0
- data/spec/zenflow/helpers/help_spec.rb +36 -0
- data/spec/zenflow/helpers/log_spec.rb +31 -0
- data/spec/zenflow/helpers/pull_request_spec.rb +108 -0
- data/spec/zenflow/helpers/shell_spec.rb +135 -0
- data/spec/zenflow/helpers/version_spec.rb +111 -0
- data/zenflow.gemspec +33 -0
- metadata +273 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Zenflow
|
2
|
+
|
3
|
+
def self.Deploy(to, opts={})
|
4
|
+
Branch.push(to)
|
5
|
+
if opts[:migrations]
|
6
|
+
Log("Deploying with migrations to #{to}")
|
7
|
+
Shell["cap #{to} deploy:migrations"]
|
8
|
+
else
|
9
|
+
Log("Deploying to #{to}")
|
10
|
+
Shell["cap #{to} deploy"]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Deploy < Thor
|
15
|
+
class_option :migrations, type: :boolean, desc: "Run migrations during deployment", aliases: :m
|
16
|
+
|
17
|
+
desc "qa", "Deploy to qa."
|
18
|
+
def qa
|
19
|
+
Zenflow::Deploy("qa", options)
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "staging", "Deploy to staging."
|
23
|
+
def staging
|
24
|
+
Zenflow::Deploy("staging", options)
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "production", "Deploy to production."
|
28
|
+
def production
|
29
|
+
Zenflow::Deploy("production", options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Zenflow
|
2
|
+
class Hotfix < BranchCommand
|
3
|
+
|
4
|
+
flow "hotfix"
|
5
|
+
|
6
|
+
branch source: Zenflow::Config[:release_branch]
|
7
|
+
branch deploy: Zenflow::Config[:staging_branch]
|
8
|
+
branch deploy: Zenflow::Config[:qa_branch]
|
9
|
+
|
10
|
+
changelog :rotate
|
11
|
+
version :patch
|
12
|
+
tag true
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Zenflow
|
2
|
+
class Release < BranchCommand
|
3
|
+
|
4
|
+
flow "release"
|
5
|
+
|
6
|
+
branch source: Zenflow::Config[:development_branch]
|
7
|
+
branch deploy: Zenflow::Config[:staging_branch]
|
8
|
+
branch deploy: Zenflow::Config[:qa_branch]
|
9
|
+
branch destination: Zenflow::Config[:release_branch]
|
10
|
+
|
11
|
+
changelog :rotate
|
12
|
+
version :minor
|
13
|
+
tag true
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Zenflow
|
2
|
+
class Reviews < Thor
|
3
|
+
|
4
|
+
desc "list", "Show all open reviews."
|
5
|
+
def list
|
6
|
+
Zenflow::Log(Terminal::Table.new(rows: Zenflow::PullRequest.list.map {|request|
|
7
|
+
[request["number"], request["head"]["ref"]]
|
8
|
+
}).to_s, indent: false, arrows: false, color: false)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Zenflow
|
2
|
+
|
3
|
+
def self.Ask(question, options={})
|
4
|
+
response = Zenflow::Query.ask_question(question, options)
|
5
|
+
Zenflow::Query.handle_response(response, options)
|
6
|
+
rescue StandardError => e
|
7
|
+
puts e.message
|
8
|
+
options[:response] = nil
|
9
|
+
retry
|
10
|
+
rescue Interrupt => e
|
11
|
+
puts
|
12
|
+
puts "-----> Exiting...".cyan
|
13
|
+
LogToFile("-----> Received interrupt. Exiting...")
|
14
|
+
exit(1)
|
15
|
+
end
|
16
|
+
|
17
|
+
class Query
|
18
|
+
def self.ask_question(question, options={})
|
19
|
+
if options[:response].to_s.strip != ""
|
20
|
+
response = options[:response]
|
21
|
+
else
|
22
|
+
response = Zenflow::Query.prompt_for_answer(question, options)
|
23
|
+
end
|
24
|
+
Zenflow::LogToFile("Response: #{response}")
|
25
|
+
response
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.prompt_for_answer(question, options={})
|
29
|
+
prompt = ">> #{question} "
|
30
|
+
prompt << "[#{options[:options].join('/')}] " if options[:options]
|
31
|
+
prompt << "[#{options[:default]}] " if options[:default] && !options[:options]
|
32
|
+
Zenflow::LogToFile("Asked: #{prompt}")
|
33
|
+
print prompt
|
34
|
+
$stdin.gets.chomp
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.handle_response(response, options={})
|
38
|
+
if !Zenflow::Query.valid_response?(response, options)
|
39
|
+
raise Zenflow::Query.build_error_message(response, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
return options[:default].downcase if response == ""
|
43
|
+
return response.downcase if response == "Y" || response == "N"
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.valid_response?(response, options={})
|
48
|
+
return false if options[:options] && !options[:options].include?(response)
|
49
|
+
return false if options[:validate] && options[:validate].is_a?(Regexp) && !response[options[:validate]]
|
50
|
+
return false if options[:required] && response == ""
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.build_error_message(response, options={})
|
55
|
+
if options[:required]
|
56
|
+
message = "You must respond to this prompt."
|
57
|
+
elsif options[:error_message]
|
58
|
+
message = options[:error_message]
|
59
|
+
else
|
60
|
+
message = %{"#{response}" is not a valid response.}
|
61
|
+
end
|
62
|
+
"-----> #{message} Try again.".red
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Zenflow
|
2
|
+
module Branch
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def list(prefix)
|
6
|
+
branches = Zenflow::Shell.run "git branch | grep #{prefix}", :silent => true
|
7
|
+
return ['!! NONE !!'] if branches.empty?
|
8
|
+
branches.split("\n").map{|branch| branch.sub(/.*#{prefix}\/?/, "") }
|
9
|
+
end
|
10
|
+
|
11
|
+
def current(prefix)
|
12
|
+
branch = Zenflow::Shell.run("git branch | grep '* #{prefix}'", :silent => true)
|
13
|
+
branch.chomp.sub(/\* #{prefix}\/?/, "") unless branch.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
def update(name)
|
17
|
+
Zenflow::Log("Updating the #{name} branch")
|
18
|
+
Zenflow::Shell["git checkout #{name} && git pull"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(name, base)
|
22
|
+
Zenflow::Log("Creating the #{name} branch based on #{base}")
|
23
|
+
Zenflow::Shell["git checkout -b #{name} #{base}"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def push(name)
|
27
|
+
Zenflow::Log("Pushing the #{name} branch to #{Zenflow::Config[:remote] || 'origin'}")
|
28
|
+
Zenflow::Shell["git push #{Zenflow::Config[:remote] || 'origin'} #{name}"]
|
29
|
+
if Zenflow::Config[:backup_remote]
|
30
|
+
Zenflow::Log("Pushing the #{name} branch to #{Zenflow::Config[:backup_remote]}")
|
31
|
+
Zenflow::Shell["git push #{Zenflow::Config[:backup_remote]} #{name}"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def push_tags
|
36
|
+
Zenflow::Log("Pushing tags to #{Zenflow::Config[:remote] || 'origin'}")
|
37
|
+
Zenflow::Shell["git push #{Zenflow::Config[:remote] || 'origin'} --tags"]
|
38
|
+
if Zenflow::Config[:backup_remote]
|
39
|
+
Zenflow::Log("Pushing tags to #{Zenflow::Config[:backup_remote]}")
|
40
|
+
Zenflow::Shell["git push #{Zenflow::Config[:backup_remote]} --tags"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def track(name)
|
45
|
+
Zenflow::Log("Tracking the #{name} branch against #{Zenflow::Config[:remote] || 'origin'}/#{name}")
|
46
|
+
Zenflow::Shell["git branch --set-upstream-to=#{Zenflow::Config[:remote] || 'origin'}/#{name} #{name}"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def checkout(name)
|
50
|
+
Zenflow::Log("Switching to the #{name} branch")
|
51
|
+
Zenflow::Shell["git checkout #{name}"]
|
52
|
+
end
|
53
|
+
|
54
|
+
def merge(name)
|
55
|
+
Zenflow::Log("Merging in the #{name} branch")
|
56
|
+
Zenflow::Shell["git merge --no-ff #{name}"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def tag(name=nil, description=nil)
|
60
|
+
Zenflow::Log("Tagging the release")
|
61
|
+
Zenflow::Shell["git tag -a '#{name || Zenflow::Ask('Name of the tag:', :required => true)}' -m '#{Zenflow::Shell.shell_escape_for_single_quoting((description || Zenflow::Ask('Tag message:', :required => true)).to_s)}'"]
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete_remote(name)
|
65
|
+
Zenflow::Log("Removing the remote branch from #{Zenflow::Config[:remote] || 'origin'}")
|
66
|
+
Zenflow::Shell["git branch -r | grep #{Zenflow::Config[:remote] || 'origin'}/#{name} && git push #{Zenflow::Config[:remote] || 'origin'} :#{name} || echo ''"]
|
67
|
+
if Zenflow::Config[:backup_remote]
|
68
|
+
Zenflow::Log("Removing the remote branch from #{Zenflow::Config[:backup_remote]}")
|
69
|
+
Zenflow::Shell["git branch -r | grep #{Zenflow::Config[:backup_remote]}/#{name} && git push #{Zenflow::Config[:backup_remote]} :#{name} || echo ''"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_local(name, options={})
|
74
|
+
Zenflow::Log("Removing the local branch")
|
75
|
+
Zenflow::Shell["git branch -#{options[:force] ? 'D' : 'd'} #{name}"]
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Zenflow
|
2
|
+
class BranchCommand < Thor
|
3
|
+
|
4
|
+
include Zenflow::BranchCommands::Abort
|
5
|
+
include Zenflow::BranchCommands::Branches
|
6
|
+
include Zenflow::BranchCommands::Compare
|
7
|
+
include Zenflow::BranchCommands::Deploy
|
8
|
+
include Zenflow::BranchCommands::Diff
|
9
|
+
include Zenflow::BranchCommands::Finish
|
10
|
+
include Zenflow::BranchCommands::Review
|
11
|
+
include Zenflow::BranchCommands::Start
|
12
|
+
include Zenflow::BranchCommands::Update
|
13
|
+
|
14
|
+
no_commands do
|
15
|
+
def flow
|
16
|
+
self.class.flow
|
17
|
+
end
|
18
|
+
|
19
|
+
def branch(key)
|
20
|
+
val = all_branches(key)
|
21
|
+
if val.size == 1
|
22
|
+
val.first
|
23
|
+
else
|
24
|
+
val
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def all_branches(key)
|
29
|
+
self.class.branch[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
def version
|
33
|
+
self.class.version
|
34
|
+
end
|
35
|
+
|
36
|
+
def changelog
|
37
|
+
self.class.changelog
|
38
|
+
end
|
39
|
+
|
40
|
+
def tag
|
41
|
+
self.class.tag
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_branches
|
45
|
+
Zenflow::Branch.delete_remote("#{flow}/#{branch_name}") if !options[:offline]
|
46
|
+
Zenflow::Branch.delete_local("#{flow}/#{branch_name}", force: true)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def branch_name
|
55
|
+
@branch_name ||= Zenflow::Branch.current(flow) ||
|
56
|
+
Zenflow::Ask("Name of the #{flow}:",
|
57
|
+
required: true,
|
58
|
+
validate: /^[-0-9a-z]+$/,
|
59
|
+
error_message: "Names can only contain dashes, 0-9, and a-z").downcase
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# DSL METHODS
|
64
|
+
|
65
|
+
def self.flow(flow=nil)
|
66
|
+
@flow = flow if flow
|
67
|
+
@flow
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.branch(branch={})
|
71
|
+
@branch ||= {}
|
72
|
+
branch.keys.each do |key|
|
73
|
+
@branch[key] ||= []
|
74
|
+
@branch[key] << branch[key]
|
75
|
+
end
|
76
|
+
@branch
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.version(version=nil)
|
80
|
+
@version = version if version
|
81
|
+
@version
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.changelog(changelog=nil)
|
85
|
+
@changelog = changelog if changelog
|
86
|
+
@changelog
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.tag(tag=nil)
|
90
|
+
@tag = tag if tag
|
91
|
+
@tag
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Zenflow
|
2
|
+
module BranchCommands
|
3
|
+
module Abort
|
4
|
+
|
5
|
+
def self.included(thor)
|
6
|
+
thor.class_eval do
|
7
|
+
|
8
|
+
desc "abort", "Aborts the branch and cleans up"
|
9
|
+
option :offline, type: :boolean, desc: "Runs in offline mode"
|
10
|
+
def abort
|
11
|
+
branch_name
|
12
|
+
Zenflow::Branch.checkout(branch(:source))
|
13
|
+
delete_branches
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Zenflow
|
2
|
+
module BranchCommands
|
3
|
+
module Branches
|
4
|
+
|
5
|
+
def self.included(thor)
|
6
|
+
thor.class_eval do
|
7
|
+
|
8
|
+
desc "branches", "List branches"
|
9
|
+
def branches
|
10
|
+
Zenflow::Log("Available #{flow} branches:")
|
11
|
+
Zenflow::Branch.list(flow).each do |branch|
|
12
|
+
Zenflow::Log("* #{branch}", indent: true, color: false)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Zenflow
|
2
|
+
module BranchCommands
|
3
|
+
module Compare
|
4
|
+
|
5
|
+
def self.included(thor)
|
6
|
+
thor.class_eval do
|
7
|
+
|
8
|
+
desc "compare", "Launch GitHub compare view against the latest code"
|
9
|
+
def compare
|
10
|
+
branch_name
|
11
|
+
Zenflow::Log("Opening GitHub compare view for #{branch(:source)}...#{flow}/#{branch_name}")
|
12
|
+
Zenflow::Shell["open https://github.com/#{Zenflow::Repo.slug}/compare/#{branch(:source)}...#{flow}/#{branch_name}"]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Zenflow
|
2
|
+
module BranchCommands
|
3
|
+
module Deploy
|
4
|
+
|
5
|
+
def self.included(thor)
|
6
|
+
thor.class_eval do
|
7
|
+
|
8
|
+
desc "deploy [OPTIONS]", "Deploy"
|
9
|
+
option :migrations, type: :boolean, desc: "Run migrations during deployment", aliases: :m
|
10
|
+
def deploy
|
11
|
+
branch_name
|
12
|
+
if !Zenflow::Config[:deployable]
|
13
|
+
Zenflow::Log("This project is not deployable right now", color: :red)
|
14
|
+
exit(1)
|
15
|
+
end
|
16
|
+
all_branches(:deploy).each do |branch|
|
17
|
+
Zenflow::Branch.update(branch)
|
18
|
+
Zenflow::Branch.merge("#{flow}/#{branch_name}")
|
19
|
+
Zenflow::Deploy(branch, options)
|
20
|
+
end
|
21
|
+
Zenflow::Branch.checkout("#{flow}/#{branch_name}")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Zenflow
|
2
|
+
module BranchCommands
|
3
|
+
module Diff
|
4
|
+
|
5
|
+
def self.included(thor)
|
6
|
+
thor.class_eval do
|
7
|
+
|
8
|
+
desc "diff", "Launch a diff against the latest code"
|
9
|
+
def diff
|
10
|
+
Zenflow::Log("Displaying diff with #{branch(:source)}")
|
11
|
+
Zenflow::Shell["git difftool #{branch(:source)}"]
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|