git_helper 1.2.0 → 2.0.2

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +17 -13
  3. data/README.md +27 -33
  4. data/Rakefile +1 -37
  5. data/bin/git-helper +10 -28
  6. data/lib/git_helper.rb +3 -5
  7. data/lib/git_helper/change_remote.rb +53 -40
  8. data/lib/git_helper/checkout_default.rb +1 -1
  9. data/lib/git_helper/clean_branches.rb +1 -4
  10. data/lib/git_helper/code_request.rb +95 -0
  11. data/lib/git_helper/empty_commit.rb +1 -1
  12. data/lib/git_helper/forget_local_commits.rb +7 -0
  13. data/lib/git_helper/gitlab_client.rb +0 -1
  14. data/lib/git_helper/highline_cli.rb +72 -6
  15. data/lib/git_helper/local_code.rb +124 -0
  16. data/lib/git_helper/merge_request.rb +57 -126
  17. data/lib/git_helper/new_branch.rb +2 -11
  18. data/lib/git_helper/octokit_client.rb +0 -1
  19. data/lib/git_helper/pull_request.rb +45 -110
  20. data/lib/git_helper/version.rb +1 -1
  21. data/spec/git_helper/change_remote_spec.rb +173 -0
  22. data/spec/git_helper/checkout_default_spec.rb +19 -0
  23. data/spec/git_helper/clean_branches_spec.rb +19 -0
  24. data/spec/git_helper/code_request_spec.rb +259 -0
  25. data/spec/git_helper/empty_commit_spec.rb +19 -0
  26. data/spec/git_helper/forget_local_commits_spec.rb +19 -0
  27. data/spec/git_helper/git_config_reader_spec.rb +60 -0
  28. data/spec/git_helper/gitlab_client_spec.rb +26 -0
  29. data/spec/git_helper/highline_cli_spec.rb +215 -0
  30. data/spec/git_helper/local_code_spec.rb +231 -0
  31. data/spec/git_helper/merge_request_spec.rb +234 -0
  32. data/spec/git_helper/new_branch_spec.rb +44 -0
  33. data/spec/git_helper/octokit_client_spec.rb +26 -0
  34. data/spec/git_helper/pull_request_spec.rb +246 -0
  35. data/spec/spec_helper.rb +0 -7
  36. metadata +41 -24
@@ -1,7 +1,7 @@
1
1
  module GitHelper
2
2
  class EmptyCommit
3
3
  def execute
4
- system("git commit --allow-empty -m \"Empty commit\"")
4
+ GitHelper::LocalCode.new.empty_commit
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,7 @@
1
+ module GitHelper
2
+ class ForgetLocalCommits
3
+ def execute
4
+ GitHelper::LocalCode.new.forget_local_commits
5
+ end
6
+ end
7
+ end
@@ -1,5 +1,4 @@
1
1
  require 'gitlab'
2
- require_relative './git_config_reader.rb'
3
2
 
4
3
  module GitHelper
5
4
  class GitLabClient
@@ -2,24 +2,90 @@ require 'highline'
2
2
 
3
3
  module GitHelper
4
4
  class HighlineCli
5
- def ask(prompt)
6
- cli.ask(prompt) do |conf|
5
+ def new_branch_name
6
+ ask('New branch name?')
7
+ end
8
+
9
+ def process_directory_remotes?(directory)
10
+ answer = ask("Found git directory: #{directory}. Do you wish to proceed in updating #{directory}'s remote URLs? (y/n)")
11
+ answer.empty? ? true : !!(answer =~ /^y/i)
12
+ end
13
+
14
+ def conflicting_remote_clarification
15
+ ask('Found git remotes for both GitHub and GitLab. Would you like to proceed with GitLab or GitHub? (github/gitlab)').downcase
16
+ end
17
+
18
+ def title
19
+ ask('Title?')
20
+ end
21
+
22
+ def base_branch
23
+ ask('Base branch?')
24
+ end
25
+
26
+ def code_request_id(request_type)
27
+ ask("#{request_type} Request ID?")
28
+ end
29
+
30
+ def accept_autogenerated_title?(autogenerated_title)
31
+ return false unless autogenerated_title
32
+ answer = ask("Accept the autogenerated code request title '#{autogenerated_title}'? (y/n)")
33
+ answer.empty? ? true : !!(answer =~ /^y/i)
34
+ end
35
+
36
+ def base_branch_default?(default_branch)
37
+ answer = ask("Is '#{default_branch}' the correct base branch for your new code request? (y/n)")
38
+ answer.empty? ? true : !!(answer =~ /^y/i)
39
+ end
40
+
41
+ def squash_merge_request?
42
+ answer = ask('Squash merge request? (y/n)')
43
+ answer.empty? ? true : !!(answer =~ /^y/i)
44
+ end
45
+
46
+ def remove_source_branch?
47
+ answer = ask('Remove source branch after merging? (y/n)')
48
+ answer.empty? ? true : !!(answer =~ /^y/i)
49
+ end
50
+
51
+ def merge_method(merge_options)
52
+ index = ask_options("Merge method?", merge_options)
53
+ merge_options[index]
54
+ end
55
+
56
+ def apply_template?(template_file_name, request_type)
57
+ answer = ask("Apply the #{request_type} request template from #{template_file_name}? (y/n)")
58
+ answer.empty? ? true : !!(answer =~ /^y/i)
59
+ end
60
+
61
+ def template_to_apply(template_options, request_type)
62
+ complete_options = template_options << 'None'
63
+ index = ask_options("Which #{request_type} request template should be applied?", complete_options)
64
+ complete_options[index]
65
+ end
66
+
67
+ #######################
68
+ ### GENERAL METHODS ###
69
+ #######################
70
+
71
+ private def ask(prompt)
72
+ highline_client.ask(prompt) do |conf|
7
73
  conf.readline = true
8
74
  end.to_s
9
75
  end
10
76
 
11
- def ask_options(prompt, choices)
77
+ private def ask_options(prompt, choices)
12
78
  choices_as_string_options = ''
13
79
  choices.each { |choice| choices_as_string_options << "#{choices.index(choice) + 1}. #{choice}\n" }
14
80
  compiled_prompt = "#{prompt}\n#{choices_as_string_options.strip}"
15
81
 
16
- cli.ask(compiled_prompt) do |conf|
82
+ highline_client.ask(compiled_prompt) do |conf|
17
83
  conf.readline = true
18
84
  end.to_i - 1
19
85
  end
20
86
 
21
- private def cli
22
- @cli ||= HighLine.new
87
+ private def highline_client
88
+ @highline_client ||= HighLine.new
23
89
  end
24
90
  end
25
91
  end
@@ -0,0 +1,124 @@
1
+ module GitHelper
2
+ class LocalCode
3
+ def checkout_default
4
+ system("git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed \"s@^refs/remotes/origin/@@\")")
5
+ end
6
+
7
+ def forget_local_commits
8
+ system("git pull")
9
+ system("git reset --hard origin/HEAD")
10
+ end
11
+
12
+ def empty_commit
13
+ system("git commit --allow-empty -m \"Empty commit\"")
14
+ end
15
+
16
+ def clean_branches
17
+ system("git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed \"s@^refs/remotes/origin/@@\")")
18
+ system("git pull")
19
+ system("git fetch -p")
20
+ system("git branch -vv | grep \"origin/.*: gone]\" | awk '{print \$1}' | grep -v \"*\" | xargs git branch -D")
21
+ end
22
+
23
+ def new_branch(branch_name)
24
+ system("git pull")
25
+ system("git branch --no-track #{branch_name}")
26
+ system("git checkout #{branch_name}")
27
+ system("git push --set-upstream origin #{branch_name}")
28
+ end
29
+
30
+ def change_remote(remote_name, remote_url)
31
+ `git remote set-url #{remote_name} #{remote_url}`
32
+ end
33
+
34
+ def remotes
35
+ `git remote -v`.split("\n")
36
+ end
37
+
38
+ def remote_name(remote)
39
+ remote.scan(/([a-zA-z]+)/).first.first
40
+ end
41
+
42
+ def ssh_remote?(remote)
43
+ remote.scan(/(git@)/).any?
44
+ end
45
+
46
+ def https_remote?(remote)
47
+ remote.scan(/(https:\/\/)/).any?
48
+ end
49
+
50
+ def remote_project(remote)
51
+ if https_remote?(remote)
52
+ remote.scan(/https:\/\/[\S]+\/([\S]*).git/).first.first
53
+ elsif ssh_remote?(remote)
54
+ remote.scan(/\/([\S]*).git/).first.first
55
+ end
56
+ end
57
+
58
+ def remote_source(remote)
59
+ if https_remote?(remote)
60
+ remote.scan(/https:\/\/([a-zA-z.]+)\//).first.first
61
+ elsif ssh_remote?(remote)
62
+ remote.scan(/git@([a-zA-z.]+):/).first.first
63
+ end
64
+ end
65
+
66
+ def github_repo?
67
+ remotes.select { |remote| remote.include?('github') }.any?
68
+ end
69
+
70
+ def gitlab_project?
71
+ remotes.select { |remote| remote.include?('gitlab') }.any?
72
+ end
73
+
74
+ def project_name
75
+ # Get the repo/project name by looking in the remote URLs for the full name
76
+ `git remote -v`.scan(/\S[\s]*[\S]+.com[\S]{1}([\S]*).git/).first.first
77
+ end
78
+
79
+ def branch
80
+ # Get the current branch by looking in the list of branches for the *
81
+ `git branch`.scan(/\*\s([\S]*)/).first.first
82
+ end
83
+
84
+ def default_branch
85
+ `git symbolic-ref refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@" | tr -d "\n"`
86
+ end
87
+
88
+ def template_options(template_identifiers)
89
+ nested_templates = Dir.glob(
90
+ File.join("**/#{template_identifiers[:nested_directory_name]}", "*.md"),
91
+ File::FNM_DOTMATCH | File::FNM_CASEFOLD
92
+ )
93
+ non_nested_templates = Dir.glob(
94
+ File.join("**", "#{template_identifiers[:non_nested_file_name]}.md"),
95
+ File::FNM_DOTMATCH | File::FNM_CASEFOLD
96
+ )
97
+ nested_templates.concat(non_nested_templates).uniq
98
+ end
99
+
100
+ def read_template(file_name)
101
+ File.open(file_name).read
102
+ end
103
+
104
+ def generate_title(local_branch)
105
+ branch_arr = local_branch.split(local_branch.include?('_') ? '_' : '-')
106
+
107
+ return if branch_arr.empty?
108
+
109
+ if branch_arr.length == 1
110
+ branch_arr.first.capitalize
111
+ elsif branch_arr[0].scan(/([\w]+)/).any? && branch_arr[1].scan(/([\d]+)/).any? # branch includes jira_123 at beginning
112
+ issue = "#{branch_arr[0].upcase}-#{branch_arr[1]}"
113
+ description = branch_arr[2..-1].join(' ')
114
+ "#{issue} #{description.capitalize}"
115
+ elsif branch_arr[0].scan(/([\w]+-[\d]+)/).any? # branch includes string jira-123 at beginning
116
+ issue = branch_arr[0].upcase
117
+ description = branch_arr[1..-1].join(' ')
118
+ "#{issue} #{description.capitalize}"
119
+ else # plain words
120
+ branch_arr[0..-1].join(' ').capitalize
121
+ end
122
+ end
123
+ end
124
+ end
@@ -1,16 +1,24 @@
1
- require_relative './gitlab_client.rb'
2
- require_relative './highline_cli.rb'
3
-
4
1
  module GitHelper
5
2
  class GitLabMergeRequest
6
- def create
3
+ attr_accessor :local_project, :local_branch, :local_code, :cli, :base_branch, :new_mr_title
4
+
5
+ def initialize(options)
6
+ @local_project = options[:local_project]
7
+ @local_branch = options[:local_branch]
8
+ @local_code = options[:local_code]
9
+ @cli = options[:cli]
10
+ end
11
+
12
+ def create(options)
13
+ @base_branch = options[:base_branch]
14
+ @new_mr_title = options[:new_title]
15
+
7
16
  begin
8
- # Ask these questions right away
9
- base_branch
10
- new_mr_title
11
17
  options = {
12
18
  source_branch: local_branch,
13
19
  target_branch: base_branch,
20
+ squash: squash_merge_request,
21
+ remove_source_branch: remove_source_branch,
14
22
  description: new_mr_body
15
23
  }
16
24
 
@@ -33,16 +41,27 @@ module GitHelper
33
41
 
34
42
  def merge
35
43
  begin
36
- # Ask these questions right away
37
44
  mr_id
38
- options = {}
39
- options[:should_remove_source_branch] = remove_source_branch?
40
- options[:squash] = squash_merge_request?
41
- options[:squash_commit_message] = existing_mr_title
45
+ options = {
46
+ should_remove_source_branch: existing_mr.should_remove_source_branch || existing_mr.force_remove_source_branch,
47
+ squash: existing_mr.squash,
48
+ squash_commit_message: existing_mr.title
49
+ }
42
50
 
43
51
  puts "Merging merge request: #{mr_id}"
44
52
  merge = gitlab_client.accept_merge_request(local_project, mr_id, options)
45
- puts "Merge request successfully merged: #{merge.merge_commit_sha}"
53
+
54
+ if merge.merge_commit_sha.nil?
55
+ options[:squash] = true
56
+ merge = gitlab_client.accept_merge_request(local_project, mr_id, options)
57
+ end
58
+
59
+ if merge.merge_commit_sha.nil?
60
+ puts 'Could not merge merge request:'
61
+ puts " #{merge.merge_error}"
62
+ else
63
+ puts "Merge request successfully merged: #{merge.merge_commit_sha}"
64
+ end
46
65
  rescue Gitlab::Error::MethodNotAllowed => e
47
66
  puts 'Could not merge merge request:'
48
67
  puts ' The merge request is not mergeable'
@@ -55,144 +74,56 @@ module GitHelper
55
74
  end
56
75
  end
57
76
 
58
- private def local_project
59
- # Get the project by looking in the remote URLs for the full project name
60
- remotes = `git remote -v`
61
- return remotes.scan(/\S[\s]*[\S]+.com[\S]{1}([\S]*).git/).first.first
62
- end
63
-
64
- private def local_branch
65
- # Get the current branch by looking in the list of branches for the *
66
- branches = `git branch`
67
- return branches.scan(/\*\s([\S]*)/).first.first
68
- end
69
-
70
- private def read_template
71
- if mr_template_options.count == 1
72
- apply_template?(mr_template_options.first) ? File.open(mr_template_options.first).read : ''
73
- else
74
- template_file_name_to_apply = template_to_apply
75
- template_file_name_to_apply == "None" ? '' : File.open(template_file_name_to_apply).read
76
- end
77
- end
78
-
79
- private def mr_id
80
- @mr_id ||= cli.ask('Merge Request ID?')
81
- end
82
-
83
- private def existing_mr_title
84
- @existing_mr_title ||= gitlab_client.merge_request(local_project, mr_id).title
85
- end
86
-
87
- private def new_mr_title
88
- @new_mr_title ||= accept_autogenerated_title? ? autogenerated_title : cli.ask('Title?')
89
- end
90
-
91
77
  private def new_mr_body
92
- @new_mr_body ||= mr_template_options.empty? ? '' : read_template
78
+ @new_mr_body ||= template_name_to_apply ? local_code.read_template(template_name_to_apply) : ''
93
79
  end
94
80
 
95
- private def base_branch
96
- @base_branch ||= base_branch_default? ? default_branch : cli.ask('Base branch?')
97
- end
98
-
99
- private def autogenerated_title
100
- @autogenerated_title ||= generate_title
101
- end
102
-
103
- private def generate_title
104
- branch_arr = local_branch.split(local_branch.include?('_') ? '_' : '-')
105
-
106
- return if branch_arr.empty?
107
-
108
- if branch_arr.length == 1
109
- return branch_arr.first.capitalize
110
- end
111
-
112
- if branch_arr[0].scan(/([\w]+)/).empty? || branch_arr[1].scan(/([\d]+)/).empty?
113
- branch_arr[0..-1].join(' ').capitalize
114
- else
115
- issue = branch_arr[0].upcase
81
+ private def template_name_to_apply
82
+ return @template_name_to_apply if @template_name_to_apply
83
+ @template_name_to_apply = nil
116
84
 
117
- if issue.include?('-')
118
- description = branch_arr[1..-1].join(' ')
85
+ unless mr_template_options.empty?
86
+ if mr_template_options.count == 1
87
+ apply_single_template = cli.apply_template?(mr_template_options.first, 'merge')
88
+ @template_name_to_apply = mr_template_options.first if apply_single_template
119
89
  else
120
- issue = "#{issue}-#{branch_arr[1]}"
121
- description = branch_arr[2..-1].join(' ')
90
+ response = cli.template_to_apply(mr_template_options, 'merge')
91
+ @template_name_to_apply = response unless response == 'None'
122
92
  end
123
-
124
- "#{issue} #{description.capitalize}"
125
- end
126
- end
127
-
128
- private def default_branch
129
- return @default_branch if @default_branch
130
- page_number = 1
131
- counter = 1
132
- branches = []
133
-
134
- while counter > 0
135
- break if default_branch = branches.select { |branch| branch.default }.first
136
- page_branches = gitlab_client.branches(local_project, page: page_number, per_page: 100)
137
- branches = page_branches
138
- counter = page_branches.count
139
- page_number += 1
140
93
  end
141
94
 
142
- @default_branch = default_branch.name
143
- end
144
-
145
- private def template_to_apply
146
- return @template_to_apply if @template_to_apply
147
- complete_options = mr_template_options << 'None'
148
- index = cli.ask_options("Which merge request template should be applied?", complete_options)
149
- @template_to_apply = complete_options[index]
95
+ @template_name_to_apply
150
96
  end
151
97
 
152
98
  private def mr_template_options
153
- return @mr_template_options if @mr_template_options
154
- nested_templates = Dir.glob(File.join("**/merge_request_templates", "*.md"), File::FNM_DOTMATCH | File::FNM_CASEFOLD)
155
- non_nested_templates = Dir.glob(File.join("**", "merge_request_template.md"), File::FNM_DOTMATCH | File::FNM_CASEFOLD)
156
- @mr_template_options = nested_templates.concat(non_nested_templates)
99
+ @mr_template_options ||= local_code.template_options({
100
+ nested_directory_name: 'merge_request_templates',
101
+ non_nested_file_name: 'merge_request_template'
102
+ })
157
103
  end
158
104
 
159
- private def base_branch_default?
160
- answer = cli.ask("Is '#{default_branch}' the correct base branch for your new merge request? (y/n)")
161
- !!(answer =~ /^y/i)
105
+ private def mr_id
106
+ @mr_id ||= cli.code_request_id('Merge')
162
107
  end
163
108
 
164
- private def accept_autogenerated_title?
165
- title = autogenerated_title
166
-
167
- if title
168
- answer = cli.ask("Accept the autogenerated merge request title '#{title}'? (y/n)")
169
- !!(answer =~ /^y/i)
170
- else
171
- false
172
- end
109
+ private def squash_merge_request
110
+ @squash_merge_request ||= cli.squash_merge_request?
173
111
  end
174
112
 
175
- private def squash_merge_request?
176
- answer = cli.ask('Squash merge request? (y/n)')
177
- !!(answer =~ /^y/i)
113
+ private def remove_source_branch
114
+ @remove_source_branch ||= existing_project.remove_source_branch_after_merge || cli.remove_source_branch?
178
115
  end
179
116
 
180
- private def remove_source_branch?
181
- answer = cli.ask('Remove source branch after merging? (y/n)')
182
- !!(answer =~ /^y/i)
117
+ private def existing_project
118
+ @existing_project ||= gitlab_client.project(local_project)
183
119
  end
184
120
 
185
- private def apply_template?(template_file_name)
186
- answer = cli.ask("Apply the merge request template from #{template_file_name}? (y/n)")
187
- !!(answer =~ /^y/i)
121
+ private def existing_mr
122
+ @existing_mr ||= gitlab_client.merge_request(local_project, mr_id)
188
123
  end
189
124
 
190
125
  private def gitlab_client
191
126
  @gitlab_client ||= GitHelper::GitLabClient.new.client
192
127
  end
193
-
194
- private def cli
195
- @cli ||= GitHelper::HighlineCli.new
196
- end
197
128
  end
198
129
  end