git_helper 1.2.0 → 2.0.2
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 +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
|