git_helper 1.1.1 → 2.0.1
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 +28 -32
- 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 +59 -97
- 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 +47 -81
- 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 +40 -23
@@ -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,113 +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 ||=
|
93
|
-
end
|
94
|
-
|
95
|
-
private def base_branch
|
96
|
-
@base_branch ||= base_branch_default? ? default_branch : cli.ask('Base branch?')
|
78
|
+
@new_mr_body ||= template_name_to_apply ? local_code.read_template(template_name_to_apply) : ''
|
97
79
|
end
|
98
80
|
|
99
|
-
private def
|
100
|
-
@
|
101
|
-
|
81
|
+
private def template_name_to_apply
|
82
|
+
return @template_name_to_apply if @template_name_to_apply
|
83
|
+
@template_name_to_apply = nil
|
102
84
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
page_branches = gitlab_client.branches(local_project, page: page_number, per_page: 100)
|
112
|
-
branches = page_branches
|
113
|
-
counter = page_branches.count
|
114
|
-
page_number += 1
|
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
|
89
|
+
else
|
90
|
+
response = cli.template_to_apply(mr_template_options, 'merge')
|
91
|
+
@template_name_to_apply = response unless response == 'None'
|
92
|
+
end
|
115
93
|
end
|
116
94
|
|
117
|
-
@
|
118
|
-
end
|
119
|
-
|
120
|
-
private def template_to_apply
|
121
|
-
return @template_to_apply if @template_to_apply
|
122
|
-
complete_options = mr_template_options << 'None'
|
123
|
-
index = cli.ask_options("Which merge request template should be applied?", complete_options)
|
124
|
-
@template_to_apply = complete_options[index]
|
95
|
+
@template_name_to_apply
|
125
96
|
end
|
126
97
|
|
127
98
|
private def mr_template_options
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
99
|
+
@mr_template_options ||= local_code.template_options({
|
100
|
+
nested_directory_name: 'merge_request_templates',
|
101
|
+
non_nested_file_name: 'merge_request_template'
|
102
|
+
})
|
132
103
|
end
|
133
104
|
|
134
|
-
private def
|
135
|
-
|
136
|
-
!!(answer =~ /^y/i)
|
105
|
+
private def mr_id
|
106
|
+
@mr_id ||= cli.code_request_id('Merge')
|
137
107
|
end
|
138
108
|
|
139
|
-
private def
|
140
|
-
|
141
|
-
!!(answer =~ /^y/i)
|
109
|
+
private def squash_merge_request
|
110
|
+
@squash_merge_request ||= cli.squash_merge_request?
|
142
111
|
end
|
143
112
|
|
144
|
-
private def
|
145
|
-
|
146
|
-
!!(answer =~ /^y/i)
|
113
|
+
private def remove_source_branch
|
114
|
+
@remove_source_branch ||= existing_project.remove_source_branch_after_merge || cli.remove_source_branch?
|
147
115
|
end
|
148
116
|
|
149
|
-
private def
|
150
|
-
|
151
|
-
!!(answer =~ /^y/i)
|
117
|
+
private def existing_project
|
118
|
+
@existing_project ||= gitlab_client.project(local_project)
|
152
119
|
end
|
153
120
|
|
154
|
-
private def
|
155
|
-
|
156
|
-
!!(answer =~ /^y/i)
|
121
|
+
private def existing_mr
|
122
|
+
@existing_mr ||= gitlab_client.merge_request(local_project, mr_id)
|
157
123
|
end
|
158
124
|
|
159
125
|
private def gitlab_client
|
160
126
|
@gitlab_client ||= GitHelper::GitLabClient.new.client
|
161
127
|
end
|
162
|
-
|
163
|
-
private def cli
|
164
|
-
@cli ||= GitHelper::HighlineCli.new
|
165
|
-
end
|
166
128
|
end
|
167
129
|
end
|