ninny 0.1.8 → 0.1.13
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/.github/pull_request_template.md +47 -0
- data/.github/workflows/main.yml +34 -0
- data/.github/workflows/scheduled.yml +49 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +27 -0
- data/.ruby-version +1 -1
- data/Gemfile +4 -2
- data/Guardfile +7 -0
- data/LICENSE.txt +1 -1
- data/README.md +78 -12
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/exe/ninny +4 -3
- data/lib/ninny.rb +4 -2
- data/lib/ninny/cli.rb +6 -12
- data/lib/ninny/commands/create_dated_branch.rb +13 -9
- data/lib/ninny/commands/output_dated_branch.rb +2 -1
- data/lib/ninny/commands/pull_request_merge.rb +10 -4
- data/lib/ninny/commands/setup.rb +32 -14
- data/lib/ninny/git.rb +21 -25
- data/lib/ninny/project_config.rb +4 -2
- data/lib/ninny/repository/gitlab.rb +24 -14
- data/lib/ninny/repository/pull_request.rb +5 -2
- data/lib/ninny/user_config.rb +4 -4
- data/lib/ninny/version.rb +1 -1
- data/ninny.gemspec +34 -58
- metadata +122 -38
- data/.travis.yml +0 -7
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile.lock +0 -159
- data/lib/ninny/commands/.gitkeep +0 -1
- data/lib/ninny/templates/.gitkeep +0 -1
@@ -4,12 +4,13 @@ module Ninny
|
|
4
4
|
module Commands
|
5
5
|
class CreateDatedBranch < Ninny::Command
|
6
6
|
attr_reader :branch_type, :should_delete_old_branches
|
7
|
+
|
7
8
|
def initialize(options)
|
8
9
|
@branch_type = options[:branch_type] || Git::STAGING_PREFIX
|
9
10
|
@should_delete_old_branches = options[:delete_old_branches]
|
10
11
|
end
|
11
12
|
|
12
|
-
def execute(
|
13
|
+
def execute(output: $stdout)
|
13
14
|
create_branch
|
14
15
|
delete_old_branches
|
15
16
|
output.puts "#{branch_name} created"
|
@@ -22,7 +23,7 @@ module Ninny
|
|
22
23
|
|
23
24
|
# Public: The date suffix to append to the branch name
|
24
25
|
def date_suffix
|
25
|
-
Date.today.strftime(
|
26
|
+
Date.today.strftime('%Y.%m.%d')
|
26
27
|
end
|
27
28
|
|
28
29
|
# Public: The name of the branch to create
|
@@ -35,12 +36,15 @@ module Ninny
|
|
35
36
|
# Public: If necessary, and if user opts to, delete old branches of its type
|
36
37
|
def delete_old_branches
|
37
38
|
return unless extra_branches.any?
|
38
|
-
should_delete = should_delete_old_branches || prompt.yes?("Do you want to delete the old #{branch_type} branch(es)? (#{extra_branches.join(", ")})")
|
39
39
|
|
40
|
-
|
41
|
-
extra_branches.
|
42
|
-
|
43
|
-
|
40
|
+
should_delete = should_delete_old_branches || prompt.yes?(
|
41
|
+
"Do you want to delete the old #{branch_type} branch(es)? (#{extra_branches.join(', ')})"
|
42
|
+
)
|
43
|
+
|
44
|
+
return unless should_delete
|
45
|
+
|
46
|
+
extra_branches.each do |extra|
|
47
|
+
Ninny.git.delete_branch(extra)
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
@@ -49,11 +53,11 @@ module Ninny
|
|
49
53
|
# Returns an Array of Strings of the branch names
|
50
54
|
def extra_branches
|
51
55
|
with_branch_type do
|
52
|
-
Ninny.git.branches_for(branch_type).
|
56
|
+
Ninny.git.branches_for(branch_type).reject { |branch| branch.name == branch_name }
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
56
|
-
def with_branch_type
|
60
|
+
def with_branch_type
|
57
61
|
case branch_type
|
58
62
|
when Git::DEPLOYABLE_PREFIX, Git::STAGING_PREFIX, Git::QAREADY_PREFIX
|
59
63
|
yield
|
@@ -6,12 +6,13 @@ module Ninny
|
|
6
6
|
module Commands
|
7
7
|
class OutputDatedBranch < Ninny::Command
|
8
8
|
attr_reader :branch_type
|
9
|
+
|
9
10
|
def initialize(options)
|
10
11
|
@branch_type = options[:branch_type] || Git::STAGING_PREFIX
|
11
12
|
@options = options
|
12
13
|
end
|
13
14
|
|
14
|
-
def execute(
|
15
|
+
def execute(output: $stdout)
|
15
16
|
output.puts Ninny.git.latest_branch_for(branch_type)
|
16
17
|
end
|
17
18
|
end
|
@@ -14,11 +14,12 @@ module Ninny
|
|
14
14
|
self.options = options
|
15
15
|
end
|
16
16
|
|
17
|
-
def execute(
|
18
|
-
|
17
|
+
def execute(*)
|
18
|
+
unless pull_request_id
|
19
19
|
current = Ninny.repo.current_pull_request
|
20
20
|
self.pull_request_id = current.number if current
|
21
21
|
end
|
22
|
+
|
22
23
|
self.pull_request_id ||= select_pull_request
|
23
24
|
|
24
25
|
check_out_branch
|
@@ -26,13 +27,15 @@ module Ninny
|
|
26
27
|
comment_about_merge
|
27
28
|
end
|
28
29
|
|
29
|
-
|
30
|
+
def select_pull_request
|
30
31
|
choices = Ninny.repo.open_pull_requests.map { |pr| { name: pr.title, value: pr.number } }
|
31
32
|
prompt.select("Which #{Ninny.repo.pull_request_label}?", choices)
|
32
33
|
end
|
34
|
+
private :select_pull_request
|
33
35
|
|
34
|
-
|
36
|
+
# Public: Check out the branch
|
35
37
|
def check_out_branch
|
38
|
+
prompt.say "Checking out #{branch_to_merge_into}."
|
36
39
|
Ninny.git.check_out(branch_to_merge_into, false)
|
37
40
|
Ninny.git.track_current_branch
|
38
41
|
rescue Ninny::Git::NoBranchOfType
|
@@ -42,6 +45,7 @@ module Ninny
|
|
42
45
|
|
43
46
|
# Public: Merge the pull request's branch into the checked-out branch
|
44
47
|
def merge_pull_request
|
48
|
+
prompt.say "Merging #{pull_request.branch} to #{branch_to_merge_into}."
|
45
49
|
Ninny.git.merge(pull_request.branch)
|
46
50
|
end
|
47
51
|
|
@@ -60,9 +64,11 @@ module Ninny
|
|
60
64
|
# Public: Find the pull request
|
61
65
|
#
|
62
66
|
# Returns a Ninny::Repository::PullRequest
|
67
|
+
# rubocop:disable Lint/DuplicateMethods
|
63
68
|
def pull_request
|
64
69
|
@pull_request ||= Ninny.repo.pull_request(pull_request_id)
|
65
70
|
end
|
71
|
+
# rubocop:enable Lint/DuplicateMethods
|
66
72
|
|
67
73
|
# Public: Find the branch
|
68
74
|
#
|
data/lib/ninny/commands/setup.rb
CHANGED
@@ -6,40 +6,58 @@ module Ninny
|
|
6
6
|
module Commands
|
7
7
|
class Setup < Ninny::Command
|
8
8
|
attr_reader :config
|
9
|
+
|
9
10
|
def initialize(options)
|
10
11
|
@options = options
|
11
12
|
@config = Ninny.user_config
|
12
13
|
end
|
13
14
|
|
14
|
-
def execute(
|
15
|
+
def execute(output: $stdout)
|
15
16
|
try_reading_user_config
|
16
17
|
|
17
|
-
prompt_for_gitlab_private_token
|
18
|
+
private_token = prompt_for_gitlab_private_token
|
19
|
+
|
20
|
+
begin
|
21
|
+
# TODO: This only works with thor gem < 1. So, we need to make this work when TTY
|
22
|
+
# releases versions compatible with thor versions >= 1 as well.
|
23
|
+
config.write(force: true)
|
24
|
+
rescue StandardError
|
25
|
+
puts ' Unable to write config file via TTY... continuing anyway...'
|
26
|
+
File.open("#{ENV['HOME']}/.ninny.yml", 'w') do |file|
|
27
|
+
file.puts "gitlab_private_token: #{private_token}"
|
28
|
+
end
|
29
|
+
end
|
18
30
|
|
19
|
-
|
20
|
-
# Command logic goes here ...
|
21
|
-
output.puts "User config #{@result}"
|
31
|
+
output.puts "User config #{@result}!"
|
22
32
|
end
|
23
33
|
|
24
34
|
def try_reading_user_config
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@result = 'created'
|
30
|
-
end
|
35
|
+
config.read
|
36
|
+
@result = 'updated'
|
37
|
+
rescue MissingUserConfig
|
38
|
+
@result = 'created'
|
31
39
|
end
|
32
40
|
|
33
41
|
def prompt_for_gitlab_private_token
|
34
42
|
begin
|
35
43
|
new_token_text = config.gitlab_private_token ? ' new' : ''
|
36
44
|
rescue MissingUserConfig
|
37
|
-
new_token_text = '
|
45
|
+
new_token_text = ''
|
38
46
|
end
|
39
|
-
|
40
|
-
|
47
|
+
|
48
|
+
return unless prompt.yes?("Do you have a#{new_token_text} GitLab private token?")
|
49
|
+
|
50
|
+
private_token = prompt.ask('Enter private token:', required: true)
|
51
|
+
|
52
|
+
begin
|
53
|
+
# TODO: This only works with thor gem < 1. So, we need to make this work when TTY
|
54
|
+
# releases versions compatible with thor versions >= 1 as well.
|
41
55
|
config.set(:gitlab_private_token, value: private_token)
|
56
|
+
rescue ArgumentError
|
57
|
+
puts ' Unable to set new token via TTY... continuing anyway...'
|
42
58
|
end
|
59
|
+
|
60
|
+
private_token
|
43
61
|
end
|
44
62
|
end
|
45
63
|
end
|
data/lib/ninny/git.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Ninny
|
4
|
+
# rubocop:disable Metrics/ClassLength
|
4
5
|
class Git
|
5
6
|
extend Forwardable
|
6
|
-
NO_BRANCH =
|
7
|
-
DEFAULT_DIRTY_MESSAGE =
|
8
|
-
|
7
|
+
NO_BRANCH = '(no branch)'
|
8
|
+
DEFAULT_DIRTY_MESSAGE = 'Your Git index is not clean. Commit, stash, or otherwise clean' \
|
9
|
+
' up the index before continuing.'
|
10
|
+
DIRTY_CONFIRM_MESSAGE = 'Your Git index is not clean. Do you want to continue?'
|
9
11
|
|
10
12
|
# branch prefixes
|
11
|
-
DEPLOYABLE_PREFIX =
|
12
|
-
STAGING_PREFIX =
|
13
|
-
QAREADY_PREFIX =
|
13
|
+
DEPLOYABLE_PREFIX = 'deployable'
|
14
|
+
STAGING_PREFIX = 'staging'
|
15
|
+
QAREADY_PREFIX = 'qaready'
|
14
16
|
|
15
17
|
def_delegators :git, :branch
|
16
18
|
|
@@ -29,12 +31,9 @@ module Ninny
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def current_branch_name
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
else
|
36
|
-
name
|
37
|
-
end
|
34
|
+
raise NotOnBranch, 'Not currently checked out to a particular branch' if git.current_branch == NO_BRANCH
|
35
|
+
|
36
|
+
git.current_branch
|
38
37
|
end
|
39
38
|
|
40
39
|
def merge(branch_name)
|
@@ -42,6 +41,7 @@ module Ninny
|
|
42
41
|
git.fetch
|
43
42
|
command 'merge', ['--no-ff', "origin/#{branch_name}"]
|
44
43
|
raise MergeFailed unless clean?
|
44
|
+
|
45
45
|
push
|
46
46
|
end
|
47
47
|
end
|
@@ -64,24 +64,21 @@ module Ninny
|
|
64
64
|
#
|
65
65
|
# branch_name - The name of the branch to check out
|
66
66
|
# do_after_pull - Should a pull be done after checkout?
|
67
|
-
def check_out(branch, do_after_pull=true)
|
67
|
+
def check_out(branch, do_after_pull = true)
|
68
68
|
git.fetch
|
69
|
-
|
69
|
+
git.checkout(branch)
|
70
70
|
pull if do_after_pull
|
71
|
-
unless current_branch.name == branch.name
|
72
|
-
raise CheckoutFailed, "Failed to check out '#{branch}'"
|
73
|
-
end
|
71
|
+
raise CheckoutFailed, "Failed to check out '#{branch}'" unless current_branch.name == branch.name
|
74
72
|
end
|
75
73
|
|
76
74
|
# Public: Track remote branch matching current branch
|
77
75
|
#
|
78
76
|
# do_after_pull - Should a pull be done after tracking?
|
79
|
-
def track_current_branch(do_after_pull=true)
|
77
|
+
def track_current_branch(do_after_pull = true)
|
80
78
|
command('branch', ['-u', "origin/#{current_branch_name}"])
|
81
79
|
pull if do_after_pull
|
82
80
|
end
|
83
81
|
|
84
|
-
|
85
82
|
# Public: Create a new branch from the given source
|
86
83
|
#
|
87
84
|
# new_branch_name - The name of the branch to create
|
@@ -108,7 +105,7 @@ module Ninny
|
|
108
105
|
# Returns an Array of Strings containing the branch names
|
109
106
|
def remote_branches
|
110
107
|
git.fetch
|
111
|
-
git.branches.remote.map{ |branch| git.branch(branch.name) }.sort_by(&:name)
|
108
|
+
git.branches.remote.map { |branch| git.branch(branch.name) }.sort_by(&:name)
|
112
109
|
end
|
113
110
|
|
114
111
|
# Public: List of branches starting with the given string
|
@@ -131,7 +128,6 @@ module Ninny
|
|
131
128
|
branches_for(prefix).last || raise(NoBranchOfType, "No #{prefix} branch")
|
132
129
|
end
|
133
130
|
|
134
|
-
|
135
131
|
# Public: Whether the Git index is clean (has no uncommited changes)
|
136
132
|
#
|
137
133
|
# Returns a Boolean
|
@@ -140,7 +136,7 @@ module Ninny
|
|
140
136
|
end
|
141
137
|
|
142
138
|
# Public: Perform the block if the Git index is clean
|
143
|
-
def if_clean(message=DEFAULT_DIRTY_MESSAGE)
|
139
|
+
def if_clean(message = DEFAULT_DIRTY_MESSAGE)
|
144
140
|
if clean? || prompt.yes?(DIRTY_CONFIRM_MESSAGE)
|
145
141
|
yield
|
146
142
|
else
|
@@ -151,9 +147,9 @@ module Ninny
|
|
151
147
|
|
152
148
|
# Public: Display the message and show the git status
|
153
149
|
def alert_dirty_index(message)
|
154
|
-
prompt.say
|
150
|
+
prompt.say ' '
|
155
151
|
prompt.say message
|
156
|
-
prompt.say
|
152
|
+
prompt.say ' '
|
157
153
|
prompt.say command('status')
|
158
154
|
raise DirtyIndex
|
159
155
|
end
|
@@ -163,11 +159,11 @@ module Ninny
|
|
163
159
|
TTY::Prompt.new(options)
|
164
160
|
end
|
165
161
|
|
166
|
-
|
167
162
|
# Exceptions
|
168
163
|
CheckoutFailed = Class.new(StandardError)
|
169
164
|
NotOnBranch = Class.new(StandardError)
|
170
165
|
NoBranchOfType = Class.new(StandardError)
|
171
166
|
DirtyIndex = Class.new(StandardError)
|
172
167
|
end
|
168
|
+
# rubocop:enable Metrics/ClassLength
|
173
169
|
end
|
data/lib/ninny/project_config.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Ninny
|
2
4
|
class ProjectConfig
|
3
5
|
attr_reader :config
|
@@ -31,14 +33,14 @@ module Ninny
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def gitlab_endpoint
|
34
|
-
config.fetch(:gitlab_endpoint, default:
|
36
|
+
config.fetch(:gitlab_endpoint, default: 'https://gitlab.com/api/v4')
|
35
37
|
end
|
36
38
|
|
37
39
|
def repo
|
38
40
|
return unless repo_type
|
39
41
|
|
40
42
|
repo_class = { gitlab: Repository::Gitlab }[repo_type.to_sym]
|
41
|
-
repo_class
|
43
|
+
repo_class&.new
|
42
44
|
end
|
43
45
|
|
44
46
|
def self.config
|
@@ -1,18 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Ninny
|
2
4
|
module Repository
|
3
5
|
class Gitlab
|
4
|
-
attr_reader :gitlab
|
5
|
-
|
6
|
+
attr_reader :gitlab, :project_id
|
7
|
+
|
6
8
|
def initialize
|
7
|
-
@gitlab = ::Gitlab.client(
|
8
|
-
|
9
|
+
@gitlab = ::Gitlab.client(
|
10
|
+
endpoint: Ninny.project_config.gitlab_endpoint,
|
11
|
+
private_token: Ninny.user_config.gitlab_private_token
|
12
|
+
)
|
9
13
|
@project_id = Ninny.project_config.gitlab_project_id
|
10
14
|
end
|
11
15
|
|
12
16
|
def current_pull_request
|
13
|
-
to_pr(
|
14
|
-
|
15
|
-
|
17
|
+
to_pr(
|
18
|
+
gitlab.merge_requests(
|
19
|
+
project_id,
|
20
|
+
{ source_branch: Ninny.git.current_branch.name, target_branch: Ninny.project_config.deploy_branch }
|
21
|
+
).last
|
22
|
+
)
|
16
23
|
end
|
17
24
|
|
18
25
|
def pull_request_label
|
@@ -20,7 +27,7 @@ module Ninny
|
|
20
27
|
end
|
21
28
|
|
22
29
|
def open_pull_requests
|
23
|
-
gitlab.merge_requests(project_id, { state: 'opened' }).map{ |mr| to_pr(mr) }
|
30
|
+
gitlab.merge_requests(project_id, { state: 'opened' }).map { |mr| to_pr(mr) }
|
24
31
|
end
|
25
32
|
|
26
33
|
def pull_request(id)
|
@@ -31,13 +38,16 @@ module Ninny
|
|
31
38
|
gitlab.create_merge_request_note(project_id, id, body)
|
32
39
|
end
|
33
40
|
|
34
|
-
|
35
|
-
request && PullRequest.new(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
def to_pr(request)
|
42
|
+
request && PullRequest.new(
|
43
|
+
number: request.iid,
|
44
|
+
title: request.title,
|
45
|
+
branch: request.source_branch,
|
46
|
+
description: request.description,
|
47
|
+
comment_lambda: ->(body) { Ninny.repo.create_merge_request_note(request.iid, body) }
|
48
|
+
)
|
40
49
|
end
|
50
|
+
private :to_pr
|
41
51
|
end
|
42
52
|
end
|
43
53
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Ninny
|
2
4
|
module Repository
|
3
5
|
class PullRequest
|
4
6
|
attr_accessor :number, :title, :description, :branch, :comment_lambda
|
5
|
-
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
6
9
|
self.number = opts[:number]
|
7
10
|
self.title = opts[:title]
|
8
11
|
self.description = opts[:description]
|
@@ -11,7 +14,7 @@ module Ninny
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def write_comment(body)
|
14
|
-
|
17
|
+
comment_lambda.call(body)
|
15
18
|
end
|
16
19
|
end
|
17
20
|
end
|
data/lib/ninny/user_config.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Ninny
|
2
4
|
class UserConfig
|
3
5
|
attr_reader :config
|
@@ -25,11 +27,9 @@ module Ninny
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def read
|
28
|
-
begin
|
29
30
|
config.read unless @read
|
30
|
-
|
31
|
-
|
32
|
-
end
|
31
|
+
rescue TTY::Config::ReadError
|
32
|
+
raise MissingUserConfig, 'User config not found, run `ninny setup`'
|
33
33
|
end
|
34
34
|
|
35
35
|
def with_read
|