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.
- checksums.yaml +4 -4
- data/Gemfile.lock +17 -13
- data/README.md +27 -33
- data/Rakefile +1 -37
- data/bin/git-helper +10 -28
- data/lib/git_helper.rb +3 -5
- data/lib/git_helper/change_remote.rb +53 -40
- data/lib/git_helper/checkout_default.rb +1 -1
- data/lib/git_helper/clean_branches.rb +1 -4
- data/lib/git_helper/code_request.rb +95 -0
- data/lib/git_helper/empty_commit.rb +1 -1
- data/lib/git_helper/forget_local_commits.rb +7 -0
- data/lib/git_helper/gitlab_client.rb +0 -1
- data/lib/git_helper/highline_cli.rb +72 -6
- data/lib/git_helper/local_code.rb +124 -0
- data/lib/git_helper/merge_request.rb +57 -126
- data/lib/git_helper/new_branch.rb +2 -11
- data/lib/git_helper/octokit_client.rb +0 -1
- data/lib/git_helper/pull_request.rb +45 -110
- data/lib/git_helper/version.rb +1 -1
- data/spec/git_helper/change_remote_spec.rb +173 -0
- data/spec/git_helper/checkout_default_spec.rb +19 -0
- data/spec/git_helper/clean_branches_spec.rb +19 -0
- data/spec/git_helper/code_request_spec.rb +259 -0
- data/spec/git_helper/empty_commit_spec.rb +19 -0
- data/spec/git_helper/forget_local_commits_spec.rb +19 -0
- data/spec/git_helper/git_config_reader_spec.rb +60 -0
- data/spec/git_helper/gitlab_client_spec.rb +26 -0
- data/spec/git_helper/highline_cli_spec.rb +215 -0
- data/spec/git_helper/local_code_spec.rb +231 -0
- data/spec/git_helper/merge_request_spec.rb +234 -0
- data/spec/git_helper/new_branch_spec.rb +44 -0
- data/spec/git_helper/octokit_client_spec.rb +26 -0
- data/spec/git_helper/pull_request_spec.rb +246 -0
- data/spec/spec_helper.rb +0 -7
- metadata +41 -24
@@ -2,24 +2,90 @@ require 'highline'
|
|
2
2
|
|
3
3
|
module GitHelper
|
4
4
|
class HighlineCli
|
5
|
-
def
|
6
|
-
|
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
|
-
|
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
|
22
|
-
@
|
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
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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 ||=
|
78
|
+
@new_mr_body ||= template_name_to_apply ? local_code.read_template(template_name_to_apply) : ''
|
93
79
|
end
|
94
80
|
|
95
|
-
private def
|
96
|
-
@
|
97
|
-
|
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
|
-
|
118
|
-
|
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
|
-
|
121
|
-
|
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
|
-
@
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
160
|
-
|
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
|
165
|
-
|
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
|
176
|
-
|
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
|
181
|
-
|
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
|
186
|
-
|
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
|