octo-merge 0.7.0.rc1

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.
@@ -0,0 +1,102 @@
1
+ require 'optparse'
2
+
3
+ module OctoMerge
4
+ class CLI
5
+ class Parser
6
+ def self.parse(args)
7
+ new(args).parse!
8
+ end
9
+
10
+ def initialize(args)
11
+ @args = args
12
+ end
13
+
14
+ def parse!
15
+ prepare
16
+ opts.parse!(args)
17
+ options
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :args
23
+
24
+ def prepare
25
+ prepare_banner
26
+
27
+ prepare_application
28
+
29
+ opts.separator ""
30
+ opts.separator "Common options:"
31
+
32
+ prepare_help
33
+ prepare_version
34
+ end
35
+
36
+ def prepare_banner
37
+ opts.banner = "Usage: octo-merge [options]"
38
+ opts.separator ""
39
+ end
40
+
41
+ def prepare_application
42
+ opts.on("--repo=REPO", "Repository (e.g.: 'rails/rails')") do |repo|
43
+ options[:repo] = repo
44
+ end
45
+
46
+ opts.on("--dir=DIR", "Working directory (e.g.: '~/Dev/Rails/rails')") do |dir|
47
+ options[:dir] = dir
48
+ end
49
+
50
+ opts.on("--pull_requests=PULL_REQUESTS", "Pull requests (e.g.: '23,42,66')") do |pull_requests|
51
+ options[:pull_requests] = pull_requests
52
+ end
53
+
54
+ opts.on("--login=login", "Login (Your GitHub username)") do |login|
55
+ options[:login] = login
56
+ end
57
+
58
+ opts.on("--password=password", "Password (Your GitHub API-Token)") do |password|
59
+ options[:password] = password
60
+ end
61
+
62
+ opts.on("--strategy=STRATEGY", "Merge strategy (e.g.: 'MergeWithoutRebase')") do |strategy|
63
+ options[:strategy] = strategy
64
+ end
65
+
66
+ opts.on("--query=QUERY", "Query to use in interactive mode (e.g.: 'label:ready-to-merge')") do |query|
67
+ options[:query] = query
68
+ end
69
+
70
+ opts.on('--interactive', 'Select PullRequests within an interactive session') do |interactive|
71
+ options[:interactive] = interactive
72
+ end
73
+
74
+ opts.on('--setup', 'Setup') do |setup|
75
+ options[:setup] = setup
76
+ end
77
+ end
78
+
79
+ def prepare_help
80
+ opts.on_tail('-h', '--help', 'Display this screen') do
81
+ puts opts
82
+ exit
83
+ end
84
+ end
85
+
86
+ def prepare_version
87
+ opts.on_tail('-v', '--version', 'Display the version') do
88
+ puts OctoMerge::VERSION
89
+ exit
90
+ end
91
+ end
92
+
93
+ def opts
94
+ @opts ||= OptionParser.new
95
+ end
96
+
97
+ def options
98
+ @options ||= {}
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,5 @@
1
+ module OctoMerge
2
+ class Configuration
3
+ attr_accessor :login, :password
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module OctoMerge
2
+ class Context
3
+ attr_reader :working_directory, :repo
4
+
5
+ def initialize(working_directory:, repo:, pull_request_numbers:)
6
+ @working_directory = working_directory
7
+ @repo = repo
8
+ @pull_request_numbers = pull_request_numbers
9
+ end
10
+
11
+ def pull_requests
12
+ @pull_requests ||= pull_request_numbers.map do |number|
13
+ PullRequest.new(repo: repo, number: number.to_s)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :pull_request_numbers
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module OctoMerge
2
+ class Execute
3
+ attr_reader :context, :strategy
4
+
5
+ def initialize(context:, strategy:)
6
+ @context = context
7
+ @strategy = strategy
8
+ end
9
+
10
+ def run
11
+ env.run
12
+ end
13
+
14
+ def env
15
+ @env ||= strategy.new(
16
+ working_directory: context.working_directory,
17
+ pull_requests: context.pull_requests
18
+ )
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,47 @@
1
+ require 'inquirer'
2
+
3
+ module OctoMerge
4
+ class InteractivePullRequests
5
+ def initialize(repo:, query:)
6
+ @repo = repo
7
+ @query = query
8
+ end
9
+
10
+ def self.get(options = {})
11
+ new(repo: options[:repo], query: options[:query]).to_s
12
+ end
13
+
14
+ def to_s
15
+ system("clear")
16
+
17
+ idx = Ask.checkbox(
18
+ "Select the pull requests you want to merge",
19
+ formatted_pull_requests
20
+ )
21
+
22
+ idx.zip(pull_requests).select { |e| e[0] }.map { |e| e[1].number }.join(",")
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :repo, :query
28
+
29
+ def formatted_pull_requests
30
+ pull_requests.map do |pull_request|
31
+ format_pull_request(pull_request)
32
+ end
33
+ end
34
+
35
+ def pull_requests
36
+ list.all
37
+ end
38
+
39
+ def format_pull_request(pull_request)
40
+ "#{pull_request.number}: \"#{pull_request.title}\" by @#{pull_request.user.login}"
41
+ end
42
+
43
+ def list
44
+ @list ||= OctoMerge::ListPullRequests.new(repo: repo, query: query)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,20 @@
1
+ module OctoMerge
2
+ class ListPullRequests
3
+ attr_reader :repo, :query
4
+
5
+ def initialize(repo:, query:)
6
+ @repo = repo
7
+ @query = query
8
+ end
9
+
10
+ def all
11
+ @all ||= github_client.search_issues("is:open is:pr repo:#{repo} #{query}")[:items]
12
+ end
13
+
14
+ private
15
+
16
+ def github_client
17
+ OctoMerge.github_client
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,108 @@
1
+ require "pathname"
2
+ require "yaml"
3
+
4
+ module OctoMerge
5
+ class Options
6
+ def self.option(key)
7
+ define_method(key) { self[key] }
8
+ end
9
+
10
+ option :login
11
+ option :password
12
+
13
+ option :dir
14
+ option :pull_requests
15
+ option :query
16
+ option :repo
17
+ option :setup
18
+ option :strategy
19
+
20
+ def [](key)
21
+ data[key]
22
+ end
23
+
24
+ def cli_options=(options)
25
+ reset_cache
26
+ @cli_options = options
27
+ end
28
+
29
+ def self.user_config_path
30
+ USER_CONFIG_PATH
31
+ end
32
+
33
+ def self.pathname
34
+ Pathname.new(CONFIG_FILE)
35
+ end
36
+
37
+ private
38
+
39
+ CONFIG_FILE = ".octo-merge.yml"
40
+ DEFAULT_OPTIONS = {
41
+ dir: ".",
42
+ strategy: "MergeWithoutRebase"
43
+ }
44
+ USER_CONFIG_PATH = File.expand_path("~/#{CONFIG_FILE}")
45
+
46
+ def data
47
+ @data ||= begin
48
+ options = default_options
49
+ .merge(user_options)
50
+ .merge(project_options)
51
+ .merge(cli_options)
52
+
53
+
54
+ # Sanitize input
55
+ options[:dir] = File.expand_path(options[:dir])
56
+ options[:strategy] = Object.const_get("OctoMerge::Strategy::#{options[:strategy]}")
57
+ options[:pull_requests] = get_interactive_pull_requests(options) if options[:interactive]
58
+ options[:pull_requests] = options[:pull_requests].to_s.split(",")
59
+
60
+ options
61
+ end
62
+ end
63
+
64
+ # This hotfix will configure the API credentials before doing the API call.
65
+ def get_interactive_pull_requests(options)
66
+ OctoMerge.configure do |config|
67
+ config.login = options[:login]
68
+ config.password = options[:password]
69
+ end
70
+
71
+ OctoMerge::InteractivePullRequests.get(options)
72
+ end
73
+
74
+ def reset_cache
75
+ @data = nil
76
+ end
77
+
78
+ def default_options
79
+ DEFAULT_OPTIONS
80
+ end
81
+
82
+ def user_options
83
+ if File.exist?(USER_CONFIG_PATH)
84
+ body = File.read(USER_CONFIG_PATH)
85
+ symbolize_keys YAML.load(body)
86
+ else
87
+ {}
88
+ end
89
+ end
90
+
91
+ def project_options
92
+ if File.exist?(CONFIG_FILE)
93
+ body = File.read(CONFIG_FILE)
94
+ symbolize_keys YAML.load(body)
95
+ else
96
+ {}
97
+ end
98
+ end
99
+
100
+ def cli_options
101
+ @cli_options ||= {}
102
+ end
103
+
104
+ def symbolize_keys(hash)
105
+ hash.inject({}){ |memo, (k, v)| memo[k.to_sym] = v; memo }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,54 @@
1
+ require "octokit"
2
+
3
+ module OctoMerge
4
+ class PullRequest
5
+ attr_reader :repo, :number
6
+
7
+ def initialize(repo:, number:)
8
+ @repo = repo
9
+ @number = number.to_s
10
+ end
11
+
12
+ def url
13
+ github_api_result.html_url
14
+ end
15
+
16
+ def remote
17
+ github_api_result.user.login
18
+ end
19
+
20
+ def remote_url
21
+ github_api_result.head.repo.ssh_url
22
+ end
23
+
24
+ def remote_branch
25
+ github_api_result.head.ref
26
+ end
27
+
28
+ def number_branch
29
+ "pull/#{number}"
30
+ end
31
+
32
+ def title
33
+ github_api_result.title
34
+ end
35
+
36
+ def body
37
+ github_api_result.body
38
+ end
39
+
40
+ def ==(other_pull_request)
41
+ repo == other_pull_request.repo && number == other_pull_request.number
42
+ end
43
+
44
+ private
45
+
46
+ def github_api_result
47
+ @github_api_result ||= github_client.pull_request(repo, number)
48
+ end
49
+
50
+ def github_client
51
+ OctoMerge.github_client
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,74 @@
1
+ module OctoMerge
2
+ class Setup
3
+ def initialize(options)
4
+ @options = options
5
+ end
6
+
7
+ def self.run(*args)
8
+ new(*args).tap { |setup| setup.run }
9
+ end
10
+
11
+ def run
12
+ setup_user_config_file
13
+ setup_project_config_file
14
+ end
15
+
16
+ private
17
+
18
+ HINTS = {
19
+ login: "login is your GitHub username",
20
+ password: "You can manage your GitHub API tokens at: https://github.com/settings/tokens"
21
+ }
22
+ private_constant :HINTS
23
+
24
+ attr_reader :options
25
+
26
+ def setup_user_config_file
27
+ setup(
28
+ name: "user",
29
+ path: Options.user_config_path,
30
+ attributes: [:login, :password],
31
+ default: true
32
+ )
33
+ end
34
+
35
+ def setup_project_config_file
36
+ setup(
37
+ name: "project",
38
+ path: Options.pathname.realpath.to_s,
39
+ attributes: [:repo, :strategy, :query],
40
+ default: false
41
+ )
42
+ end
43
+
44
+ def setup(name:, path:, attributes:, default:)
45
+ return unless Ask.confirm "Create #{name} config file? (#{path})", default: default
46
+
47
+ create(path: path, config: config_for(attributes))
48
+ end
49
+
50
+ def config_for(attributes)
51
+ attributes.inject({}) { |hash, key|
52
+ hash[key] = ask_for(key)
53
+ hash
54
+ }
55
+ end
56
+
57
+ def ask_for(key)
58
+ puts "[INFO] #{HINTS[key]}" if HINTS[key]
59
+
60
+ Ask.input "#{key}", default: options.send(key)
61
+ end
62
+
63
+ def create(path:, config:)
64
+ File.write(path.to_s, content_for(config))
65
+ end
66
+
67
+ def content_for(config)
68
+ config
69
+ .select { |key, value| !value.nil? && value != "" }
70
+ .map { |key, value| "#{key}: \"#{value}\"" }
71
+ .join("\n")
72
+ end
73
+ end
74
+ end