git_helper 1.1.0 → 2.0.0
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 +39 -32
- data/bin/git-helper +12 -23
- data/lib/git_helper/change_remote.rb +56 -40
- data/lib/git_helper/checkout_default.rb +3 -1
- data/lib/git_helper/clean_branches.rb +3 -4
- data/lib/git_helper/code_request.rb +100 -0
- data/lib/git_helper/empty_commit.rb +3 -1
- data/lib/git_helper/forget_local_commits.rb +9 -0
- 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 +63 -84
- data/lib/git_helper/new_branch.rb +3 -9
- data/lib/git_helper/pull_request.rb +48 -78
- data/lib/git_helper/version.rb +1 -1
- data/spec/git_helper/change_remote_spec.rb +172 -0
- data/spec/git_helper/checkout_default_spec.rb +18 -0
- data/spec/git_helper/clean_branches_spec.rb +18 -0
- data/spec/git_helper/code_request_spec.rb +258 -0
- data/spec/git_helper/empty_commit_spec.rb +18 -0
- data/spec/git_helper/forget_local_commits_spec.rb +18 -0
- data/spec/git_helper/git_config_reader_spec.rb +59 -0
- data/spec/git_helper/gitlab_client_spec.rb +25 -0
- data/spec/git_helper/highline_cli_spec.rb +214 -0
- data/spec/git_helper/local_code_spec.rb +230 -0
- data/spec/git_helper/merge_request_spec.rb +233 -0
- data/spec/git_helper/new_branch_spec.rb +43 -0
- data/spec/git_helper/octokit_client_spec.rb +25 -0
- data/spec/git_helper/pull_request_spec.rb +245 -0
- data/spec/spec_helper.rb +0 -6
- metadata +37 -20
@@ -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,21 +1,33 @@
|
|
1
1
|
require_relative './gitlab_client.rb'
|
2
2
|
require_relative './highline_cli.rb'
|
3
|
+
require_relative './local_code.rb'
|
3
4
|
|
4
5
|
module GitHelper
|
5
6
|
class GitLabMergeRequest
|
6
|
-
|
7
|
+
attr_accessor :local_project, :local_branch, :local_code, :cli, :base_branch, :new_mr_title
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@local_project = options[:local_project]
|
11
|
+
@local_branch = options[:local_branch]
|
12
|
+
@local_code = options[:local_code]
|
13
|
+
@cli = options[:cli]
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(options)
|
17
|
+
@base_branch = options[:base_branch]
|
18
|
+
@new_mr_title = options[:new_title]
|
19
|
+
|
7
20
|
begin
|
8
|
-
# Ask these questions right away
|
9
|
-
base_branch
|
10
|
-
new_mr_title
|
11
21
|
options = {
|
12
22
|
source_branch: local_branch,
|
13
23
|
target_branch: base_branch,
|
24
|
+
squash: squash_merge_request,
|
25
|
+
remove_source_branch: remove_source_branch,
|
14
26
|
description: new_mr_body
|
15
27
|
}
|
16
28
|
|
17
29
|
puts "Creating merge request: #{new_mr_title}"
|
18
|
-
mr = gitlab_client.create_merge_request(
|
30
|
+
mr = gitlab_client.create_merge_request(local_project, new_mr_title, options)
|
19
31
|
|
20
32
|
if mr.diff_refs.base_sha == mr.diff_refs.head_sha
|
21
33
|
puts "Merge request was created, but no commits have been pushed to GitLab: #{mr.web_url}"
|
@@ -33,16 +45,27 @@ module GitHelper
|
|
33
45
|
|
34
46
|
def merge
|
35
47
|
begin
|
36
|
-
# Ask these questions right away
|
37
48
|
mr_id
|
38
|
-
options = {
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
options = {
|
50
|
+
should_remove_source_branch: existing_mr.should_remove_source_branch || existing_mr.force_remove_source_branch,
|
51
|
+
squash: existing_mr.squash,
|
52
|
+
squash_commit_message: existing_mr.title
|
53
|
+
}
|
42
54
|
|
43
55
|
puts "Merging merge request: #{mr_id}"
|
44
|
-
merge = gitlab_client.accept_merge_request(
|
45
|
-
|
56
|
+
merge = gitlab_client.accept_merge_request(local_project, mr_id, options)
|
57
|
+
|
58
|
+
if merge.merge_commit_sha.nil?
|
59
|
+
options[:squash] = true
|
60
|
+
merge = gitlab_client.accept_merge_request(local_project, mr_id, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
if merge.merge_commit_sha.nil?
|
64
|
+
puts 'Could not merge merge request:'
|
65
|
+
puts " #{merge.merge_error}"
|
66
|
+
else
|
67
|
+
puts "Merge request successfully merged: #{merge.merge_commit_sha}"
|
68
|
+
end
|
46
69
|
rescue Gitlab::Error::MethodNotAllowed => e
|
47
70
|
puts 'Could not merge merge request:'
|
48
71
|
puts ' The merge request is not mergeable'
|
@@ -55,100 +78,56 @@ module GitHelper
|
|
55
78
|
end
|
56
79
|
end
|
57
80
|
|
58
|
-
private def local_repo
|
59
|
-
# Get the repository by looking in the remote URLs for the full repository 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_repo, 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
81
|
private def new_mr_body
|
92
|
-
@new_mr_body ||=
|
82
|
+
@new_mr_body ||= template_name_to_apply ? local_code.read_template(template_name_to_apply) : ''
|
93
83
|
end
|
94
84
|
|
95
|
-
private def
|
96
|
-
@
|
97
|
-
|
85
|
+
private def template_name_to_apply
|
86
|
+
return @template_name_to_apply if @template_name_to_apply
|
87
|
+
@template_name_to_apply = nil
|
98
88
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
89
|
+
unless mr_template_options.empty?
|
90
|
+
if mr_template_options.count == 1
|
91
|
+
apply_single_template = cli.apply_template?(mr_template_options.first, 'merge')
|
92
|
+
@template_name_to_apply = mr_template_options.first if apply_single_template
|
93
|
+
else
|
94
|
+
response = cli.template_to_apply(mr_template_options, 'merge')
|
95
|
+
@template_name_to_apply = response unless response == 'None'
|
96
|
+
end
|
97
|
+
end
|
106
98
|
|
107
|
-
|
108
|
-
return @template_to_apply if @template_to_apply
|
109
|
-
complete_options = mr_template_options << 'None'
|
110
|
-
index = cli.ask_options("Which merge request template should be applied?", complete_options)
|
111
|
-
@template_to_apply = complete_options[index]
|
99
|
+
@template_name_to_apply
|
112
100
|
end
|
113
101
|
|
114
102
|
private def mr_template_options
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
103
|
+
@mr_template_options ||= local_code.template_options({
|
104
|
+
nested_directory_name: 'merge_request_templates',
|
105
|
+
non_nested_file_name: 'merge_request_template'
|
106
|
+
})
|
119
107
|
end
|
120
108
|
|
121
|
-
private def
|
122
|
-
|
123
|
-
!!(answer =~ /^y/i)
|
109
|
+
private def mr_id
|
110
|
+
@mr_id ||= cli.code_request_id('Merge')
|
124
111
|
end
|
125
112
|
|
126
|
-
private def
|
127
|
-
|
128
|
-
!!(answer =~ /^y/i)
|
113
|
+
private def squash_merge_request
|
114
|
+
@squash_merge_request ||= cli.squash_merge_request?
|
129
115
|
end
|
130
116
|
|
131
|
-
private def
|
132
|
-
|
133
|
-
!!(answer =~ /^y/i)
|
117
|
+
private def remove_source_branch
|
118
|
+
@remove_source_branch ||= existing_project.remove_source_branch_after_merge || cli.remove_source_branch?
|
134
119
|
end
|
135
120
|
|
136
|
-
private def
|
137
|
-
|
138
|
-
!!(answer =~ /^y/i)
|
121
|
+
private def existing_project
|
122
|
+
@existing_project ||= gitlab_client.project(local_project)
|
139
123
|
end
|
140
124
|
|
141
|
-
private def
|
142
|
-
|
143
|
-
!!(answer =~ /^y/i)
|
125
|
+
private def existing_mr
|
126
|
+
@existing_mr ||= gitlab_client.merge_request(local_project, mr_id)
|
144
127
|
end
|
145
128
|
|
146
129
|
private def gitlab_client
|
147
130
|
@gitlab_client ||= GitHelper::GitLabClient.new.client
|
148
131
|
end
|
149
|
-
|
150
|
-
private def cli
|
151
|
-
@cli ||= GitHelper::HighlineCli.new
|
152
|
-
end
|
153
132
|
end
|
154
133
|
end
|
@@ -1,18 +1,12 @@
|
|
1
1
|
require_relative './highline_cli.rb'
|
2
|
+
require_relative './local_code.rb'
|
2
3
|
|
3
4
|
module GitHelper
|
4
5
|
class NewBranch
|
5
6
|
def execute(new_branch_name = nil)
|
6
|
-
branch_name = new_branch_name ||
|
7
|
+
branch_name = new_branch_name || GitHelper::HighlineCli.new.new_branch_name
|
7
8
|
puts "Attempting to create a new branch: #{branch_name}"
|
8
|
-
|
9
|
-
system("git branch --no-track #{branch_name}")
|
10
|
-
system("git checkout #{branch_name}")
|
11
|
-
system("git push --set-upstream origin #{branch_name}")
|
12
|
-
end
|
13
|
-
|
14
|
-
private def cli
|
15
|
-
@cli ||= GitHelper::HighlineCli.new
|
9
|
+
GitHelper::LocalCode.new.new_branch(branch_name)
|
16
10
|
end
|
17
11
|
end
|
18
12
|
end
|