zenflow 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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