startling 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.ruby-gemset +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +78 -0
- data/Rakefile +15 -0
- data/bin/start +5 -0
- data/lib/generators/startling/configuration_generator.rb +78 -0
- data/lib/startling.rb +30 -0
- data/lib/startling/cache.rb +20 -0
- data/lib/startling/cli_options.rb +46 -0
- data/lib/startling/colorize_string.rb +58 -0
- data/lib/startling/command.rb +67 -0
- data/lib/startling/commands/base.rb +83 -0
- data/lib/startling/commands/check_for_local_mods.rb +17 -0
- data/lib/startling/commands/create_branch.rb +53 -0
- data/lib/startling/commands/create_pull_request.rb +35 -0
- data/lib/startling/commands/label_pull_request.rb +15 -0
- data/lib/startling/configuration.rb +106 -0
- data/lib/startling/git_local.rb +61 -0
- data/lib/startling/github.rb +16 -0
- data/lib/startling/github/api.rb +106 -0
- data/lib/startling/github/pull_request.rb +54 -0
- data/lib/startling/github/repo.rb +44 -0
- data/lib/startling/handlers/default_pull_request_handler.rb +21 -0
- data/lib/startling/handlers/pull_request_handler_base.rb +21 -0
- data/lib/startling/markdown.rb +7 -0
- data/lib/startling/shell.rb +11 -0
- data/lib/startling/version.rb +3 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/startling/commands/base_spec.rb +32 -0
- data/spec/startling/configuration_spec.rb +127 -0
- data/spec/startling/git_local_spec.rb +22 -0
- data/spec/startling/github/pull_request_spec.rb +37 -0
- data/spec/startling_configuration_spec.rb +16 -0
- data/spec/startling_spec.rb +122 -0
- data/spec/support/dotenv.rb +2 -0
- data/spec/support/tokens.rb +5 -0
- data/spec/support/vcr.rb +16 -0
- data/spec/vcr_cassettes/bin_start_starts_stories.yml +564 -0
- data/spec/vcr_cassettes/bin_start_starts_stories_pr_body.yml +644 -0
- data/startling.gemspec +36 -0
- metadata +297 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
module Startling
|
2
|
+
module Github
|
3
|
+
class PullRequest
|
4
|
+
attr_reader :attributes
|
5
|
+
attr_accessor :labels
|
6
|
+
|
7
|
+
def initialize(attributes, prefetch_data: true)
|
8
|
+
@attributes = attributes
|
9
|
+
prefetch_data if prefetch_data
|
10
|
+
end
|
11
|
+
|
12
|
+
def id
|
13
|
+
attributes.number
|
14
|
+
end
|
15
|
+
|
16
|
+
def title
|
17
|
+
attributes.title
|
18
|
+
end
|
19
|
+
|
20
|
+
def branch
|
21
|
+
attributes.head.ref
|
22
|
+
end
|
23
|
+
|
24
|
+
def in_progress?
|
25
|
+
label_names.any? { |label| label.match(/(WIP|REVIEW)/) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def label_names
|
29
|
+
labels.map(&:name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def url
|
33
|
+
attributes.rels[:html].href
|
34
|
+
end
|
35
|
+
|
36
|
+
def created_at
|
37
|
+
attributes.created_at
|
38
|
+
end
|
39
|
+
|
40
|
+
def updated_at
|
41
|
+
attributes.updated_at
|
42
|
+
end
|
43
|
+
|
44
|
+
def author
|
45
|
+
@author ||= attributes.user.rels[:self].get.data.name
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def prefetch_data
|
50
|
+
author
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Startling
|
2
|
+
module Github
|
3
|
+
class Repo
|
4
|
+
attr_reader :api, :name
|
5
|
+
|
6
|
+
def initialize(name, api)
|
7
|
+
@name = name
|
8
|
+
@api = api
|
9
|
+
end
|
10
|
+
|
11
|
+
def attributes
|
12
|
+
@attributes ||= api.repository_attributes(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_branch
|
16
|
+
attributes.default_branch
|
17
|
+
end
|
18
|
+
|
19
|
+
def pull_requests
|
20
|
+
@pull_requests ||= api.pull_requests(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def pull_request(branch)
|
24
|
+
pull_requests.find { |pr| pr.branch == branch }
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_labels_for_issue(issue_id:, labels:)
|
28
|
+
labels = Array(labels)
|
29
|
+
api.set_labels_for_issue(repo_name: name, issue_id: issue_id, labels: labels)
|
30
|
+
end
|
31
|
+
|
32
|
+
def open_pull_request(title: nil, body: nil, branch: nil)
|
33
|
+
pull_request = api.open_pull_request(repo_name: name, destination_branch: default_branch,
|
34
|
+
branch: branch, title: title, body: body)
|
35
|
+
return pull_request if pull_request
|
36
|
+
pull_request(branch) # In case this is already open
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.all
|
40
|
+
@all ||= Startling::REPOS.map { |name| new(name) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "pull_request_handler_base"
|
2
|
+
|
3
|
+
module Startling
|
4
|
+
module Handlers
|
5
|
+
class DefaultPullRequestHandler < PullRequestHandlerBase
|
6
|
+
def title
|
7
|
+
title = ask("Please input a pull request title: ")
|
8
|
+
abort "Title must be specified." if title.empty?
|
9
|
+
title
|
10
|
+
end
|
11
|
+
|
12
|
+
def body
|
13
|
+
Startling.pull_request_body
|
14
|
+
end
|
15
|
+
|
16
|
+
def commit_message
|
17
|
+
Startling.pull_request_commit_message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Startling
|
2
|
+
module Handlers
|
3
|
+
class PullRequestHandlerBase
|
4
|
+
def initialize(args)
|
5
|
+
@args = args
|
6
|
+
end
|
7
|
+
|
8
|
+
def title
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def body
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def commit_message
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.run_all_when_everything_filtered = true
|
9
|
+
config.filter_run :focus
|
10
|
+
|
11
|
+
# Run specs in random order to surface order dependencies. If you find an
|
12
|
+
# order dependency and want to debug it, you can fix the order by providing
|
13
|
+
# the seed, which is printed after each run.
|
14
|
+
# --seed 1234
|
15
|
+
config.order = 'random'
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'pry'
|
19
|
+
Dir['./spec/support/**/*.rb'].map {|f| require f}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'startling/commands/base'
|
4
|
+
|
5
|
+
describe Startling::Commands::Base do
|
6
|
+
it "should assigns key/values passed in as attributes" do
|
7
|
+
base = Startling::Commands::Base.new(foo: 'bar', baz: 'qux')
|
8
|
+
expect(base.foo).to eq 'bar'
|
9
|
+
expect(base.baz).to eq 'qux'
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#execute' do
|
13
|
+
subject { Startling::Commands::Base.new }
|
14
|
+
it 'should throws NotImplementedError' do
|
15
|
+
expect{ subject.execute }.to raise_error(NotImplementedError)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#run' do
|
20
|
+
subject { Startling::Commands::Base.run(foo: 'bar')}
|
21
|
+
|
22
|
+
it 'should assign attributes and call execute' do
|
23
|
+
base = double :base_command
|
24
|
+
allow(Startling::Commands::Base).to receive(:new).and_return(base)
|
25
|
+
|
26
|
+
expect(Startling::Commands::Base).to receive(:new).with(foo: 'bar')
|
27
|
+
expect(base).to receive(:execute)
|
28
|
+
|
29
|
+
subject
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
describe Configuration do
|
6
|
+
let(:configuration) { Configuration.new }
|
7
|
+
|
8
|
+
describe "Default settings" do
|
9
|
+
it "sets the default cache_dir to pwd" do
|
10
|
+
expect(configuration.cache_dir).to eql(Dir.pwd)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "sets the default root_dir to pwd" do
|
14
|
+
expect(configuration.root_dir).to eql(Dir.pwd)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets the default wip limit to WIP_LIMIT" do
|
18
|
+
expect(configuration.wip_limit)
|
19
|
+
.to eql(Configuration::DEFAULT_WIP_LIMIT)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets the default repos to empty" do
|
23
|
+
expect(configuration.repos).to eql([])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "sets the default pull request commit message" do
|
27
|
+
expect(configuration.pull_request_commit_message)
|
28
|
+
.to eql(Configuration::DEFAULT_COMMIT_MESSAGE)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sets the default pull request labels" do
|
32
|
+
expect(configuration.pull_request_labels).to eql([])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#cache_dir" do
|
37
|
+
it "can set the value" do
|
38
|
+
configuration.cache_dir = "new dir"
|
39
|
+
expect(configuration.cache_dir).to eql("new dir")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#root_dir" do
|
44
|
+
it "can set the value" do
|
45
|
+
configuration.root_dir = "new dir"
|
46
|
+
expect(configuration.root_dir).to eql("new dir")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#wip_limit" do
|
51
|
+
it "can set the value" do
|
52
|
+
configuration.wip_limit = 6
|
53
|
+
expect(configuration.wip_limit).to eql(6)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#repos" do
|
58
|
+
it "can set the value" do
|
59
|
+
configuration.repos << "repo path"
|
60
|
+
expect(configuration.repos).to eql(["repo path"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#pull_request_commit_message" do
|
65
|
+
it "can set the value" do
|
66
|
+
configuration.pull_request_commit_message = "The Commit"
|
67
|
+
expect(configuration.pull_request_commit_message).to eql("The Commit")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#pull_request_labels" do
|
72
|
+
it "can set the value" do
|
73
|
+
configuration.pull_request_labels = ["WIP", "REVIEW"]
|
74
|
+
expect(configuration.pull_request_labels).to eql(["WIP", "REVIEW"])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe ".load_configuration" do
|
79
|
+
let(:git) { double(GitLocal) }
|
80
|
+
let(:config_files) {
|
81
|
+
{
|
82
|
+
caps: "Startlingfile.rb",
|
83
|
+
lower: "startlingfile.rb"
|
84
|
+
}
|
85
|
+
}
|
86
|
+
let(:caps_config_file) { "Startlingfile.rb" }
|
87
|
+
let(:lower_config_file) { "startlingfile.rb" }
|
88
|
+
|
89
|
+
before do
|
90
|
+
allow(Startling::GitLocal).to receive(:new) { git }
|
91
|
+
allow(git).to receive(:project_root) { Dir.pwd }
|
92
|
+
end
|
93
|
+
|
94
|
+
after do
|
95
|
+
config_files.each do |_, v|
|
96
|
+
delete_config_file(v)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when no configuration file exists" do
|
101
|
+
it "uses default configuration" do
|
102
|
+
expect(Configuration.load_configuration).to eql(nil)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when a configuration file exists" do
|
107
|
+
it "loads configuration from Startlingfile.rb" do
|
108
|
+
create_config_file(config_files[:caps])
|
109
|
+
expect(Configuration.load_configuration).to eql(config_files[:caps])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "loads configuration from startlingfile.rb" do
|
113
|
+
create_config_file(config_files[:lower])
|
114
|
+
expect(Configuration.load_configuration).to eql(config_files[:lower])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_config_file(config_file)
|
120
|
+
File.open(config_file, "w")
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete_config_file(config_file)
|
124
|
+
File.delete(config_file) if File.exists? config_file
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling/git_local'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
describe GitLocal do
|
6
|
+
subject { GitLocal.new }
|
7
|
+
|
8
|
+
it "parses the repo name out of an https url" do
|
9
|
+
url = 'https://github.com/TeachingChannel/startling.git'
|
10
|
+
allow(subject).to receive(:remote_url) { url }
|
11
|
+
|
12
|
+
expect(subject.repo_name).to eq('TeachingChannel/startling')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "parses the repo name out of an ssh url" do
|
16
|
+
url = 'git@github.com:TeachingChannel/startling.git'
|
17
|
+
allow(subject).to receive(:remote_url) { url }
|
18
|
+
|
19
|
+
expect(subject.repo_name).to eq('TeachingChannel/startling')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling/github/pull_request'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
module Github
|
6
|
+
describe PullRequest, "#in_progress?" do
|
7
|
+
|
8
|
+
it "should be true for WIP label" do
|
9
|
+
pull_request = PullRequest.new(double(:attributes))
|
10
|
+
allow(pull_request).to receive(:label_names) { ["WIP"] }
|
11
|
+
|
12
|
+
expect(pull_request.in_progress?).to be_truthy
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be true for REVIEW label" do
|
16
|
+
pull_request = PullRequest.new(double(:attributes))
|
17
|
+
allow(pull_request).to receive(:label_names) { ["REVIEW"] }
|
18
|
+
|
19
|
+
expect(pull_request.in_progress?).to be_truthy
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be false if for other labels" do
|
23
|
+
pull_request = PullRequest.new(double(:attributes))
|
24
|
+
allow(pull_request).to receive(:label_names) { ["some other thing", "bug"] }
|
25
|
+
|
26
|
+
expect(pull_request.in_progress?).to be_falsey
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be false if no labels" do
|
30
|
+
pull_request = PullRequest.new(double(:attributes))
|
31
|
+
allow(pull_request).to receive(:label_names) { [] }
|
32
|
+
|
33
|
+
expect(pull_request.in_progress?).to be_falsey
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
describe ".configure" do
|
6
|
+
before do
|
7
|
+
Startling.configure do |config|
|
8
|
+
config.cache_dir = "value"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sets the configuration attributes" do
|
13
|
+
expect(Startling.cache_dir).to eql("value")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'startling'
|
4
|
+
require 'startling/git_local'
|
5
|
+
require 'startling/github'
|
6
|
+
|
7
|
+
describe "bin/start" do
|
8
|
+
let(:feature_name) { 'bin_start_starts_stories' }
|
9
|
+
let(:feature_branch) { "feature/#{feature_name}" }
|
10
|
+
let(:repo_default_branch) { 'master' }
|
11
|
+
let(:pull_request_body) { "This is a test body" }
|
12
|
+
let(:git) { Startling::GitLocal.new }
|
13
|
+
let(:repo) { Startling::Github.repo(git.repo_name) }
|
14
|
+
let(:pull_request) { repo.pull_request(feature_branch) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
local_configuration = <<CONFIG
|
18
|
+
Startling.configure do |config|
|
19
|
+
# WIP Limit
|
20
|
+
# config.wip_limit = 4
|
21
|
+
|
22
|
+
# Repos to check against for WIP limit
|
23
|
+
# config.repos << 'substantial/startling-dev'
|
24
|
+
|
25
|
+
# Valid story estimations
|
26
|
+
# config.valid_estimates = [1, 2, 4, 8, 16, 32, 64, 128]
|
27
|
+
|
28
|
+
# Commands to be run before a story is stared
|
29
|
+
# config.hook_commands.before_story_start = [:check_wip]
|
30
|
+
|
31
|
+
# Command to be run after a story has started
|
32
|
+
# config.hook_commands.after_story_start = []
|
33
|
+
|
34
|
+
# Commands to be run before a pull request is created
|
35
|
+
# config.hook_commands.before_pull_request = []
|
36
|
+
|
37
|
+
# Commands to be run after a pull request is created
|
38
|
+
# config.hook_commands.after_pull_request = []
|
39
|
+
|
40
|
+
# Handler used to start a provider specific story related to the pull request
|
41
|
+
# config.story_handler = :pivotal_start
|
42
|
+
|
43
|
+
# Message for pull request commit
|
44
|
+
# config.pull_request_commit_message = "Startling"
|
45
|
+
|
46
|
+
# Message for pull request body
|
47
|
+
config.pull_request_body = "#{pull_request_body}"
|
48
|
+
|
49
|
+
# Labels for a pull request
|
50
|
+
# config.pull_request_labels = [WIP, REVIEW, HOLD]
|
51
|
+
|
52
|
+
# Handler used for setting the title and body of a pull request
|
53
|
+
config.pull_request_handler = :default_pull_request_handler
|
54
|
+
end
|
55
|
+
CONFIG
|
56
|
+
|
57
|
+
File.open('startlingfile.rb', 'w') { |file| file.write(local_configuration) }
|
58
|
+
|
59
|
+
Startling::Commands::Base.load_configuration
|
60
|
+
|
61
|
+
test_repo_path = "tmp/test_repo"
|
62
|
+
FileUtils.mkdir_p "tmp"
|
63
|
+
|
64
|
+
unless File.exists? "tmp/test_repo/.git"
|
65
|
+
`git clone git@github.com:substantial/startling-testing.git #{test_repo_path}`
|
66
|
+
end
|
67
|
+
|
68
|
+
File.write File.join(test_repo_path, ".github_access_token"), Tokens.github
|
69
|
+
|
70
|
+
FileUtils.cd test_repo_path
|
71
|
+
|
72
|
+
Startling.root_dir = Startling.cache_dir = "."
|
73
|
+
|
74
|
+
git.checkout_branch repo_default_branch
|
75
|
+
git.destroy_branch feature_branch
|
76
|
+
end
|
77
|
+
|
78
|
+
after do
|
79
|
+
FileUtils.cd "#{__dir__}/.."
|
80
|
+
FileUtils.rm "startlingfile.rb"
|
81
|
+
end
|
82
|
+
|
83
|
+
# VCR Preconditions:
|
84
|
+
# * There should be no open pull requests:
|
85
|
+
# https://github.com/substantial/startling-testing
|
86
|
+
|
87
|
+
it "starts stories from origin/master",
|
88
|
+
vcr: { cassette_name: "bin_start_starts_stories" } do
|
89
|
+
|
90
|
+
allow_any_instance_of(Startling::Handlers::DefaultPullRequestHandler)
|
91
|
+
.to receive(:ask)
|
92
|
+
.with("Please input a pull request title: ") { "The Title" }
|
93
|
+
|
94
|
+
allow_any_instance_of(Startling::Commands::CreateBranch)
|
95
|
+
.to receive(:ask)
|
96
|
+
.with("Enter branch name (enter for current branch): ") { feature_name }
|
97
|
+
|
98
|
+
command = Startling::Command.new(args: [])
|
99
|
+
command.execute
|
100
|
+
|
101
|
+
expect(git.remote_branches).to include feature_branch
|
102
|
+
expect(git.current_branch).to eq feature_branch
|
103
|
+
expect(repo.default_branch).to eq repo_default_branch
|
104
|
+
end
|
105
|
+
|
106
|
+
it "The pull request body is the same as the config",
|
107
|
+
vcr: { cassette_name: "bin_start_starts_stories_pr_body" } do
|
108
|
+
|
109
|
+
allow_any_instance_of(Startling::Handlers::DefaultPullRequestHandler)
|
110
|
+
.to receive(:ask)
|
111
|
+
.with("Please input a pull request title: ") { "The Title" }
|
112
|
+
|
113
|
+
allow_any_instance_of(Startling::Commands::CreateBranch)
|
114
|
+
.to receive(:ask)
|
115
|
+
.with("Enter branch name (enter for current branch): ") { feature_name }
|
116
|
+
|
117
|
+
command = Startling::Command.new(args: [])
|
118
|
+
command.execute
|
119
|
+
|
120
|
+
expect(pull_request.attributes.body).to eq pull_request_body
|
121
|
+
end
|
122
|
+
end
|