zenflow 0.8.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.
- 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
|