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.
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