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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.zenflow +10 -0
  7. data/CHANGELOG.md +0 -0
  8. data/Gemfile +3 -0
  9. data/Gemfile.lock +95 -0
  10. data/Guardfile +6 -0
  11. data/LICENSE.md +22 -0
  12. data/README.markdown +92 -0
  13. data/VERSION.yml +5 -0
  14. data/bin/zenflow +17 -0
  15. data/lib/zenflow.rb +35 -0
  16. data/lib/zenflow/cli.rb +130 -0
  17. data/lib/zenflow/commands/deploy.rb +33 -0
  18. data/lib/zenflow/commands/feature.rb +10 -0
  19. data/lib/zenflow/commands/hotfix.rb +15 -0
  20. data/lib/zenflow/commands/release.rb +16 -0
  21. data/lib/zenflow/commands/reviews.rb +12 -0
  22. data/lib/zenflow/helpers/ask.rb +66 -0
  23. data/lib/zenflow/helpers/branch.rb +80 -0
  24. data/lib/zenflow/helpers/branch_command.rb +95 -0
  25. data/lib/zenflow/helpers/branch_commands/abort.rb +21 -0
  26. data/lib/zenflow/helpers/branch_commands/branches.rb +21 -0
  27. data/lib/zenflow/helpers/branch_commands/compare.rb +20 -0
  28. data/lib/zenflow/helpers/branch_commands/deploy.rb +29 -0
  29. data/lib/zenflow/helpers/branch_commands/diff.rb +19 -0
  30. data/lib/zenflow/helpers/branch_commands/finish.rb +68 -0
  31. data/lib/zenflow/helpers/branch_commands/review.rb +58 -0
  32. data/lib/zenflow/helpers/branch_commands/start.rb +39 -0
  33. data/lib/zenflow/helpers/branch_commands/update.rb +22 -0
  34. data/lib/zenflow/helpers/changelog.rb +100 -0
  35. data/lib/zenflow/helpers/config.rb +36 -0
  36. data/lib/zenflow/helpers/github.rb +40 -0
  37. data/lib/zenflow/helpers/help.rb +37 -0
  38. data/lib/zenflow/helpers/log.rb +21 -0
  39. data/lib/zenflow/helpers/pull_request.rb +68 -0
  40. data/lib/zenflow/helpers/repo.rb +13 -0
  41. data/lib/zenflow/helpers/shell.rb +89 -0
  42. data/lib/zenflow/helpers/version.rb +87 -0
  43. data/lib/zenflow/version.rb +3 -0
  44. data/spec/fixtures/VERSION.yml +5 -0
  45. data/spec/fixtures/cassettes/create_bad_pull_request.yml +57 -0
  46. data/spec/fixtures/cassettes/create_pull_request.yml +68 -0
  47. data/spec/fixtures/cassettes/existing_pull_request.yml +145 -0
  48. data/spec/fixtures/cassettes/pull_request_by_ref.yml +145 -0
  49. data/spec/fixtures/cassettes/pull_request_find.yml +70 -0
  50. data/spec/fixtures/cassettes/pull_request_for_non-existent_ref.yml +145 -0
  51. data/spec/fixtures/cassettes/pull_request_list.yml +74 -0
  52. data/spec/fixtures/cassettes/unexisting_pull_request.yml +145 -0
  53. data/spec/spec_helper.rb +36 -0
  54. data/spec/support/shared_examples_for_version_number.rb +19 -0
  55. data/spec/zenflow/commands/deploy_spec.rb +48 -0
  56. data/spec/zenflow/commands/feature_spec.rb +11 -0
  57. data/spec/zenflow/commands/hotfix_spec.rb +14 -0
  58. data/spec/zenflow/commands/release_spec.rb +15 -0
  59. data/spec/zenflow/commands/reviews_spec.rb +15 -0
  60. data/spec/zenflow/helpers/ask_spec.rb +115 -0
  61. data/spec/zenflow/helpers/branch_command_spec.rb +310 -0
  62. data/spec/zenflow/helpers/branch_spec.rb +300 -0
  63. data/spec/zenflow/helpers/changelog_spec.rb +188 -0
  64. data/spec/zenflow/helpers/cli_spec.rb +277 -0
  65. data/spec/zenflow/helpers/github_spec.rb +45 -0
  66. data/spec/zenflow/helpers/help_spec.rb +36 -0
  67. data/spec/zenflow/helpers/log_spec.rb +31 -0
  68. data/spec/zenflow/helpers/pull_request_spec.rb +108 -0
  69. data/spec/zenflow/helpers/shell_spec.rb +135 -0
  70. data/spec/zenflow/helpers/version_spec.rb +111 -0
  71. data/zenflow.gemspec +33 -0
  72. 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,10 @@
1
+ module Zenflow
2
+ class Feature < BranchCommand
3
+
4
+ flow "feature"
5
+
6
+ branch source: Zenflow::Config[:development_branch]
7
+ branch deploy: Zenflow::Config[:qa_branch]
8
+
9
+ end
10
+ 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