git_helper 1.1.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +17 -13
  3. data/README.md +28 -32
  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 +59 -97
  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 +47 -81
  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 +40 -23
@@ -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,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 ||= mr_template_options.empty? ? '' : read_template
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 autogenerated_title
100
- @autogenerated_title ||= local_branch.split('_')[0..-1].join(' ').capitalize
101
- end
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
- private def default_branch
104
- return @default_branch if @default_branch
105
- page_number = 1
106
- counter = 1
107
- branches = []
108
-
109
- while counter > 0
110
- break if default_branch = branches.select { |branch| branch.default }.first
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
- @default_branch = default_branch.name
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
- return @mr_template_options if @mr_template_options
129
- nested_templates = Dir.glob(File.join("**/merge_request_templates", "*.md"), File::FNM_DOTMATCH | File::FNM_CASEFOLD)
130
- non_nested_templates = Dir.glob(File.join("**", "merge_request_template.md"), File::FNM_DOTMATCH | File::FNM_CASEFOLD)
131
- @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
+ })
132
103
  end
133
104
 
134
- private def base_branch_default?
135
- answer = cli.ask("Is '#{default_branch}' the correct base branch for your new merge request? (y/n)")
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 accept_autogenerated_title?
140
- answer = cli.ask("Accept the autogenerated merge request title '#{autogenerated_title}'? (y/n)")
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 squash_merge_request?
145
- answer = cli.ask('Squash merge request? (y/n)')
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 remove_source_branch?
150
- answer = cli.ask('Remove source branch after merging? (y/n)')
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 apply_template?(template_file_name)
155
- answer = cli.ask("Apply the merge request template from #{template_file_name}? (y/n)")
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