git_helper 1.2.0 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,18 +1,9 @@
|
|
1
|
-
require_relative './highline_cli.rb'
|
2
|
-
|
3
1
|
module GitHelper
|
4
2
|
class NewBranch
|
5
3
|
def execute(new_branch_name = nil)
|
6
|
-
branch_name = new_branch_name ||
|
4
|
+
branch_name = new_branch_name || GitHelper::HighlineCli.new.new_branch_name
|
7
5
|
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
|
6
|
+
GitHelper::LocalCode.new.new_branch(branch_name)
|
16
7
|
end
|
17
8
|
end
|
18
9
|
end
|
@@ -1,13 +1,19 @@
|
|
1
|
-
require_relative './octokit_client.rb'
|
2
|
-
require_relative './highline_cli.rb'
|
3
|
-
|
4
1
|
module GitHelper
|
5
2
|
class GitHubPullRequest
|
6
|
-
|
3
|
+
attr_accessor :local_repo, :local_branch, :local_code, :cli, :base_branch, :new_pr_title
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@local_repo = 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_pr_title = options[:new_title]
|
15
|
+
|
7
16
|
begin
|
8
|
-
# Ask these questions right away
|
9
|
-
base_branch
|
10
|
-
new_pr_title
|
11
17
|
new_pr_body
|
12
18
|
|
13
19
|
puts "Creating pull request: #{new_pr_title}"
|
@@ -30,12 +36,11 @@ module GitHelper
|
|
30
36
|
|
31
37
|
def merge
|
32
38
|
begin
|
33
|
-
# Ask these questions right away
|
34
39
|
pr_id
|
35
40
|
merge_method
|
36
41
|
|
37
42
|
puts "Merging pull request: #{pr_id}"
|
38
|
-
merge = octokit_client.merge_pull_request(local_repo, pr_id,
|
43
|
+
merge = octokit_client.merge_pull_request(local_repo, pr_id, existing_pr.title, { merge_method: merge_method })
|
39
44
|
puts "Pull request successfully merged: #{merge.sha}"
|
40
45
|
rescue Octokit::UnprocessableEntity => e
|
41
46
|
puts 'Could not merge pull request:'
|
@@ -66,131 +71,61 @@ module GitHelper
|
|
66
71
|
end
|
67
72
|
end
|
68
73
|
|
69
|
-
private def local_repo
|
70
|
-
# Get the repository by looking in the remote URLs for the full repository name
|
71
|
-
remotes = `git remote -v`
|
72
|
-
return remotes.scan(/\S[\s]*[\S]+.com[\S]{1}([\S]*).git/).first.first
|
73
|
-
end
|
74
|
-
|
75
|
-
private def local_branch
|
76
|
-
# Get the current branch by looking in the list of branches for the *
|
77
|
-
branches = `git branch`
|
78
|
-
return branches.scan(/\*\s([\S]*)/).first.first
|
79
|
-
end
|
80
|
-
|
81
|
-
private def read_template
|
82
|
-
if pr_template_options.count == 1
|
83
|
-
apply_template?(pr_template_options.first) ? File.open(pr_template_options.first).read : ''
|
84
|
-
else
|
85
|
-
template_file_name_to_apply = template_to_apply
|
86
|
-
template_file_name_to_apply == "None" ? '' : File.open(template_file_name_to_apply).read
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
private def merge_options
|
91
|
-
[ 'merge', 'squash', 'rebase' ]
|
92
|
-
end
|
93
|
-
|
94
|
-
private def pr_id
|
95
|
-
@pr_id ||= cli.ask('Pull Request ID?')
|
96
|
-
end
|
97
|
-
|
98
|
-
private def existing_pr_title
|
99
|
-
@existing_pr_title ||= octokit_client.pull_request(local_repo, pr_id).title
|
100
|
-
end
|
101
|
-
|
102
|
-
private def new_pr_title
|
103
|
-
@new_pr_title ||= accept_autogenerated_title? ? autogenerated_title : cli.ask('Title?')
|
104
|
-
end
|
105
|
-
|
106
74
|
private def new_pr_body
|
107
|
-
@new_pr_body ||=
|
108
|
-
end
|
109
|
-
|
110
|
-
private def base_branch
|
111
|
-
@base_branch ||= base_branch_default? ? default_branch : cli.ask('Base branch?')
|
75
|
+
@new_pr_body ||= template_name_to_apply ? local_code.read_template(template_name_to_apply) : ''
|
112
76
|
end
|
113
77
|
|
114
|
-
private def
|
115
|
-
@
|
116
|
-
|
117
|
-
|
118
|
-
private def generate_title
|
119
|
-
branch_arr = local_branch.split(local_branch.include?('_') ? '_' : '-')
|
120
|
-
|
121
|
-
return if branch_arr.empty?
|
78
|
+
private def template_name_to_apply
|
79
|
+
return @template_name_to_apply if @template_name_to_apply
|
80
|
+
@template_name_to_apply = nil
|
122
81
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
if branch_arr[0].scan(/([\w]+)/).empty? || branch_arr[1].scan(/([\d]+)/).empty?
|
128
|
-
branch_arr[0..-1].join(' ').capitalize
|
129
|
-
else
|
130
|
-
issue = branch_arr[0].upcase
|
131
|
-
|
132
|
-
if issue.include?('-')
|
133
|
-
description = branch_arr[1..-1].join(' ')
|
82
|
+
unless pr_template_options.empty?
|
83
|
+
if pr_template_options.count == 1
|
84
|
+
apply_single_template = cli.apply_template?(pr_template_options.first, 'pull')
|
85
|
+
@template_name_to_apply = pr_template_options.first if apply_single_template
|
134
86
|
else
|
135
|
-
|
136
|
-
|
87
|
+
response = cli.template_to_apply(pr_template_options, 'pull')
|
88
|
+
@template_name_to_apply = response unless response == 'None'
|
137
89
|
end
|
138
|
-
|
139
|
-
"#{issue} #{description.capitalize}"
|
140
90
|
end
|
141
|
-
end
|
142
91
|
|
143
|
-
|
144
|
-
@default_branch ||= octokit_client.repository(local_repo).default_branch
|
92
|
+
@template_name_to_apply
|
145
93
|
end
|
146
94
|
|
147
|
-
private def
|
148
|
-
|
149
|
-
|
150
|
-
|
95
|
+
private def pr_template_options
|
96
|
+
@pr_template_options ||= local_code.template_options({
|
97
|
+
nested_directory_name: 'PULL_REQUEST_TEMPLATE',
|
98
|
+
non_nested_file_name: 'pull_request_template'
|
99
|
+
})
|
151
100
|
end
|
152
101
|
|
153
|
-
private def
|
154
|
-
|
155
|
-
complete_options = pr_template_options << 'None'
|
156
|
-
index = cli.ask_options("Which pull request template should be applied?", complete_options)
|
157
|
-
@template_to_apply = complete_options[index]
|
102
|
+
private def pr_id
|
103
|
+
@pr_id ||= cli.code_request_id('Pull')
|
158
104
|
end
|
159
105
|
|
160
|
-
private def
|
161
|
-
|
162
|
-
nested_templates = Dir.glob(File.join("**/PULL_REQUEST_TEMPLATE", "*.md"), File::FNM_DOTMATCH | File::FNM_CASEFOLD)
|
163
|
-
non_nested_templates = Dir.glob(File.join("**", "pull_request_template.md"), File::FNM_DOTMATCH | File::FNM_CASEFOLD)
|
164
|
-
@pr_template_options = nested_templates.concat(non_nested_templates)
|
106
|
+
private def merge_method
|
107
|
+
@merge_method ||= merge_options.length == 1 ? merge_options.first : cli.merge_method(merge_options)
|
165
108
|
end
|
166
109
|
|
167
|
-
private def
|
168
|
-
|
169
|
-
|
110
|
+
private def merge_options
|
111
|
+
return @merge_options if @merge_options
|
112
|
+
merge_options = []
|
113
|
+
merge_options << 'merge' if existing_project.allow_merge_commit
|
114
|
+
merge_options << 'squash' if existing_project.allow_squash_merge
|
115
|
+
merge_options << 'rebase' if existing_project.allow_rebase_merge
|
116
|
+
@merge_options = merge_options
|
170
117
|
end
|
171
118
|
|
172
|
-
private def
|
173
|
-
|
174
|
-
|
175
|
-
if title
|
176
|
-
answer = cli.ask("Accept the autogenerated merge request title '#{title}'? (y/n)")
|
177
|
-
!!(answer =~ /^y/i)
|
178
|
-
else
|
179
|
-
false
|
180
|
-
end
|
119
|
+
private def existing_project
|
120
|
+
@existing_project ||= octokit_client.repository(local_repo)
|
181
121
|
end
|
182
122
|
|
183
|
-
private def
|
184
|
-
|
185
|
-
!!(answer =~ /^y/i)
|
123
|
+
private def existing_pr
|
124
|
+
@existing_pr ||= octokit_client.pull_request(local_repo, pr_id)
|
186
125
|
end
|
187
126
|
|
188
127
|
private def octokit_client
|
189
128
|
@octokit_client ||= GitHelper::OctokitClient.new.client
|
190
129
|
end
|
191
|
-
|
192
|
-
private def cli
|
193
|
-
@cli ||= GitHelper::HighlineCli.new
|
194
|
-
end
|
195
130
|
end
|
196
131
|
end
|
data/lib/git_helper/version.rb
CHANGED
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'git_helper'
|
3
|
+
|
4
|
+
describe GitHelper::ChangeRemote do
|
5
|
+
let(:remote1) { 'git@github.com:github-username-old/project-1.git' }
|
6
|
+
let(:local_code) do
|
7
|
+
double(:local_code,
|
8
|
+
remotes: [remote1],
|
9
|
+
remote_name: 'origin',
|
10
|
+
ssh_remote?: true,
|
11
|
+
https_remote?: false,
|
12
|
+
remote_project: 'project-1',
|
13
|
+
remote_source: 'github.com',
|
14
|
+
change_remote: true
|
15
|
+
)
|
16
|
+
end
|
17
|
+
let(:cli) { double(:highline_cli, process_directory_remotes?: true) }
|
18
|
+
let(:old_owner) { 'github-username-old' }
|
19
|
+
let(:new_owner) { 'github-username-new' }
|
20
|
+
let(:directory_entries) { [ '.', '..', 'project-1', 'project-2', 'project-3' ] }
|
21
|
+
|
22
|
+
subject { GitHelper::ChangeRemote.new(old_owner, new_owner) }
|
23
|
+
|
24
|
+
before do
|
25
|
+
allow(GitHelper::HighlineCli).to receive(:new).and_return(cli)
|
26
|
+
allow(GitHelper::LocalCode).to receive(:new).and_return(local_code)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#execute' do
|
30
|
+
before do
|
31
|
+
allow(Dir).to receive(:pwd).and_return('/Users/firstname/lastname/path/to/project')
|
32
|
+
allow(Dir).to receive(:entries).and_return(directory_entries)
|
33
|
+
allow(File).to receive(:join).and_return('/Users/firstname/lastname/path/to/project/project-1')
|
34
|
+
allow(File).to receive(:directory?).and_return(true)
|
35
|
+
allow(subject).to receive(:process_dir)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should call to process at least one directory' do
|
39
|
+
expect(subject).to receive(:process_dir).at_least(:once)
|
40
|
+
subject.execute
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should definitely look in the file structure' do
|
44
|
+
expect(Dir).to receive(:pwd)
|
45
|
+
expect(Dir).to receive(:entries)
|
46
|
+
expect(File).to receive(:join)
|
47
|
+
expect(File).to receive(:directory?)
|
48
|
+
subject.execute
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#process_dir' do
|
53
|
+
before do
|
54
|
+
allow(Dir).to receive(:chdir).and_return(nil)
|
55
|
+
allow(File).to receive(:exist?).and_return(true)
|
56
|
+
allow(subject).to receive(:process_git_repository)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should definitely look in the file structure' do
|
60
|
+
expect(Dir).to receive(:chdir)
|
61
|
+
expect(File).to receive(:exist?)
|
62
|
+
subject.send(:process_dir, '/Users/firstname/lastname/path/to/project', '/Users/firstname/lastname/path/to/project/project-1')
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when the user says to process the directory' do
|
66
|
+
it 'should call to process the git repository at least once' do
|
67
|
+
expect(subject).to receive(:process_git_repository).at_least(:once)
|
68
|
+
subject.send(:process_dir, '/Users/firstname/lastname/path/to/project', '/Users/firstname/lastname/path/to/project/project-1')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when the user says not to process the directory' do
|
73
|
+
let(:cli) { double(:highline_cli, process_directory_remotes?: false) }
|
74
|
+
|
75
|
+
it 'should not call to process the directory' do
|
76
|
+
expect(subject).not_to receive(:process_git_repository)
|
77
|
+
subject.send(:process_dir, '/Users/firstname/lastname/path/to/project', '/Users/firstname/lastname/path/to/project/project-1')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#process_git_repository' do
|
83
|
+
before do
|
84
|
+
allow(subject).to receive(:process_remote).and_return(nil)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should call local_code' do
|
88
|
+
expect(GitHelper::LocalCode).to receive(:new)
|
89
|
+
subject.send(:process_git_repository)
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when the remote includes the old owner' do
|
93
|
+
it 'should call to process the remote' do
|
94
|
+
expect(subject).to receive(:process_remote)
|
95
|
+
subject.send(:process_git_repository)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when the remote does not include the old owner' do
|
100
|
+
let(:remote1) { 'git@github.com:github-username-new/project-1.git' }
|
101
|
+
|
102
|
+
it 'should not call to process the remote' do
|
103
|
+
expect(subject).not_to receive(:process_remote)
|
104
|
+
subject.send(:process_git_repository)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#process_remote' do
|
110
|
+
it 'should always get the remote name' do
|
111
|
+
expect(local_code).to receive(:remote_name)
|
112
|
+
subject.send(:process_remote, remote1)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should always attempt to change the remote' do
|
116
|
+
expect(local_code).to receive(:change_remote)
|
117
|
+
subject.send(:process_remote, remote1)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should attempt to get the remote repo exactly once' do
|
121
|
+
expect(local_code).to receive(:remote_project).exactly(:once)
|
122
|
+
subject.send(:process_remote, remote1)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should attempt to get the remote source exactly once' do
|
126
|
+
expect(local_code).to receive(:remote_source).exactly(:once)
|
127
|
+
subject.send(:process_remote, remote1)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should ask if the remote is SSH' do
|
131
|
+
expect(local_code).to receive(:ssh_remote?)
|
132
|
+
subject.send(:process_remote, remote1)
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'https remote' do
|
136
|
+
let(:local_code) do
|
137
|
+
double(:local_code,
|
138
|
+
remotes: [remote1],
|
139
|
+
remote_name: 'origin',
|
140
|
+
ssh_remote?: false,
|
141
|
+
https_remote?: false,
|
142
|
+
remote_project: 'project-1',
|
143
|
+
remote_source: 'github.com',
|
144
|
+
change_remote: true
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should ask if the remote is SSH' do
|
149
|
+
expect(local_code).to receive(:ssh_remote?)
|
150
|
+
subject.send(:process_remote, remote1)
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should ask if the remote is https' do
|
154
|
+
expect(local_code).to receive(:https_remote?)
|
155
|
+
subject.send(:process_remote, remote1)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#local_code' do
|
161
|
+
it 'should create a new local code instance' do
|
162
|
+
expect(GitHelper::LocalCode).to receive(:new)
|
163
|
+
subject.send(:local_code)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#cli' do
|
168
|
+
it 'should create a new highline CLI instance' do
|
169
|
+
expect(GitHelper::HighlineCli).to receive(:new)
|
170
|
+
subject.send(:cli)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'git_helper'
|
3
|
+
|
4
|
+
describe GitHelper::CheckoutDefault do
|
5
|
+
let(:local_code) { double(:local_code, checkout_default: :done) }
|
6
|
+
|
7
|
+
subject { GitHelper::CheckoutDefault.new }
|
8
|
+
|
9
|
+
it 'should call GitHelper::LocalCode' do
|
10
|
+
expect(GitHelper::LocalCode).to receive(:new).and_return(local_code)
|
11
|
+
subject.execute
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should call the checkout_default method from the local code class' do
|
15
|
+
allow(GitHelper::LocalCode).to receive(:new).and_return(local_code)
|
16
|
+
expect(local_code).to receive(:checkout_default)
|
17
|
+
subject.execute
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'git_helper'
|
3
|
+
|
4
|
+
describe GitHelper::CleanBranches do
|
5
|
+
let(:local_code) { double(:local_code, clean_branches: :commit) }
|
6
|
+
|
7
|
+
subject { GitHelper::CleanBranches.new }
|
8
|
+
|
9
|
+
it 'should call GitHelper::LocalCode' do
|
10
|
+
expect(GitHelper::LocalCode).to receive(:new).and_return(local_code)
|
11
|
+
subject.execute
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should call the clean_branches method from the local code class' do
|
15
|
+
allow(GitHelper::LocalCode).to receive(:new).and_return(local_code)
|
16
|
+
expect(local_code).to receive(:clean_branches)
|
17
|
+
subject.execute
|
18
|
+
end
|
19
|
+
end
|