gitcycle 0.1.0
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.
- data/.gitignore +8 -0
- data/Gemfile +3 -0
- data/LICENSE +18 -0
- data/README.md +16 -0
- data/Rakefile +1 -0
- data/bin/gitc +16 -0
- data/features/config.example.yml +9 -0
- data/features/gitcycle.feature +155 -0
- data/features/steps/gitcycle_steps.rb +225 -0
- data/gitcycle.gemspec +31 -0
- data/lib/ext/string.rb +20 -0
- data/lib/gitcycle.rb +498 -0
- metadata +149 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2010
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/gitc
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path("../../lib/gitcycle", __FILE__)
|
4
|
+
|
5
|
+
gitcycle = Gitcycle.new
|
6
|
+
command = ARGV.shift
|
7
|
+
|
8
|
+
if command.nil?
|
9
|
+
puts "\nNo command specified\n".red
|
10
|
+
elsif gitcycle.respond_to?(command)
|
11
|
+
gitcycle.send(command, *ARGV)
|
12
|
+
elsif ARGV.empty?
|
13
|
+
gitcycle.create_branch(command)
|
14
|
+
else
|
15
|
+
puts "\nCommand '#{command}' not found.\n".red
|
16
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
lighthouse:
|
2
|
+
account: # lighthouse subdomain
|
3
|
+
project: # lighthouse project id integer
|
4
|
+
token: # lighthouse api token hash
|
5
|
+
owner: # user of parent repo
|
6
|
+
repo: # repo name
|
7
|
+
token_dev: # gitcycle api token (dev)
|
8
|
+
token_qa: # gitcycle api token (qa)
|
9
|
+
user: # user of forked repo
|
@@ -0,0 +1,155 @@
|
|
1
|
+
Feature: gitcycle
|
2
|
+
|
3
|
+
Scenario: No command given
|
4
|
+
When I execute gitcycle with ""
|
5
|
+
Then output includes "No command specified"
|
6
|
+
|
7
|
+
Scenario: Non-existent command
|
8
|
+
When I execute gitcycle with "blah blah"
|
9
|
+
Then output includes "Command 'blah' not found"
|
10
|
+
|
11
|
+
Scenario: Setup
|
12
|
+
When I execute gitcycle setup
|
13
|
+
Then output includes "Configuration saved."
|
14
|
+
And gitcycle.yml should be valid
|
15
|
+
|
16
|
+
Scenario: Feature branch w/ custom branch name
|
17
|
+
Given a fresh set of repositories
|
18
|
+
And a new Lighthouse ticket
|
19
|
+
When I cd to the user repo
|
20
|
+
And I execute gitcycle with the Lighthouse ticket URL
|
21
|
+
And I enter "n"
|
22
|
+
And I enter "ticket.id-rename"
|
23
|
+
Then output includes "Retrieving branch information from gitcycle."
|
24
|
+
And output includes "Would you like to name your branch 'ticket.id'?"
|
25
|
+
And output includes "What would you like to name your branch?"
|
26
|
+
And output includes "Creating 'ticket.id-rename' from 'master'."
|
27
|
+
And output includes "Checking out branch 'ticket.id-rename'."
|
28
|
+
And output includes "Pushing 'ticket.id-rename'."
|
29
|
+
And output includes "Sending branch information to gitcycle."
|
30
|
+
And redis entries valid
|
31
|
+
|
32
|
+
Scenario: Feature branch
|
33
|
+
Given a fresh set of repositories
|
34
|
+
And a new Lighthouse ticket
|
35
|
+
When I cd to the user repo
|
36
|
+
And I execute gitcycle with the Lighthouse ticket URL
|
37
|
+
And I enter "y"
|
38
|
+
Then output includes "Retrieving branch information from gitcycle."
|
39
|
+
And output includes "Would you like to name your branch 'ticket.id'?"
|
40
|
+
And output does not include "What would you like to name your branch?"
|
41
|
+
And output includes "Creating 'ticket.id' from 'master'."
|
42
|
+
And output includes "Checking out branch 'ticket.id'."
|
43
|
+
And output includes "Pushing 'ticket.id'."
|
44
|
+
And output includes "Sending branch information to gitcycle."
|
45
|
+
And redis entries valid
|
46
|
+
|
47
|
+
Scenario: Checkout via ticket w/ existing branch
|
48
|
+
When I cd to the user repo
|
49
|
+
And I execute gitcycle with the Lighthouse ticket URL
|
50
|
+
Then output includes "Retrieving branch information from gitcycle."
|
51
|
+
And output does not include "Would you like to name your branch 'ticket.id'?"
|
52
|
+
And output does not include "What would you like to name your branch?"
|
53
|
+
And output does not include "Creating 'ticket.id' from 'master'."
|
54
|
+
And output includes "Checking out branch 'ticket.id'."
|
55
|
+
And output does not include "Pushing 'ticket.id'."
|
56
|
+
And output does not include "Sending branch information to gitcycle."
|
57
|
+
And current branch is "ticket.id"
|
58
|
+
|
59
|
+
Scenario: Checkout via ticket w/ fresh repo
|
60
|
+
Given a fresh set of repositories
|
61
|
+
When I cd to the user repo
|
62
|
+
And I execute gitcycle with the Lighthouse ticket URL
|
63
|
+
Then output includes "Retrieving branch information from gitcycle."
|
64
|
+
And output does not include "Would you like to name your branch 'ticket.id'?"
|
65
|
+
And output does not include "What would you like to name your branch?"
|
66
|
+
And output does not include "Creating 'ticket.id' from 'master'."
|
67
|
+
And output includes "Tracking branch 'ticket.id'."
|
68
|
+
And output does not include "Pushing 'ticket.id'."
|
69
|
+
And output does not include "Sending branch information to gitcycle."
|
70
|
+
And current branch is "ticket.id"
|
71
|
+
|
72
|
+
Scenario: Pull changes from upstream
|
73
|
+
When I cd to the owner repo
|
74
|
+
And I checkout master
|
75
|
+
And I commit something
|
76
|
+
And I cd to the user repo
|
77
|
+
And I execute gitcycle with "pull"
|
78
|
+
Then output includes "Retrieving branch information from gitcycle."
|
79
|
+
And output includes "Adding remote repo 'config.owner/config.repo'."
|
80
|
+
And output includes "Fetching remote branch 'master'."
|
81
|
+
And output includes "Merging remote branch 'master' from 'config.owner/config.repo'."
|
82
|
+
And git log should contain the last commit
|
83
|
+
|
84
|
+
Scenario: Discuss commits w/ no parameters and nothing committed
|
85
|
+
When I cd to the user repo
|
86
|
+
And I checkout ticket.id
|
87
|
+
And I execute gitcycle with "discuss"
|
88
|
+
Then output includes "Retrieving branch information from gitcycle."
|
89
|
+
And output includes "Creating GitHub pull request."
|
90
|
+
And output does not include "Branch not found."
|
91
|
+
And output does not include "Opening issue"
|
92
|
+
And output includes "You must push code before opening a pull request."
|
93
|
+
And redis entries valid
|
94
|
+
|
95
|
+
Scenario: Discuss commits w/ no parameters and something committed
|
96
|
+
When I cd to the user repo
|
97
|
+
And I checkout ticket.id
|
98
|
+
And I commit something
|
99
|
+
And I execute gitcycle with "discuss"
|
100
|
+
Then output includes "Retrieving branch information from gitcycle."
|
101
|
+
And output includes "Creating GitHub pull request."
|
102
|
+
And output does not include "Branch not found."
|
103
|
+
And output includes "Opening issue" with URL
|
104
|
+
And output does not include "You must push code before opening a pull request."
|
105
|
+
And URL is a valid issue
|
106
|
+
And redis entries valid
|
107
|
+
|
108
|
+
Scenario: Discuss commits w/ parameters
|
109
|
+
When I cd to the user repo
|
110
|
+
And I checkout ticket.id
|
111
|
+
And I execute gitcycle with "discuss issue.id"
|
112
|
+
Then output includes "Retrieving branch information from gitcycle."
|
113
|
+
And output does not include "Creating GitHub pull request."
|
114
|
+
And output does not include "Branch not found."
|
115
|
+
And output does not include "You must push code before opening a pull request."
|
116
|
+
And output includes "Opening issue" with URL
|
117
|
+
And URL is a valid issue
|
118
|
+
|
119
|
+
Scenario: Ready issue w/ no parameters
|
120
|
+
When I cd to the user repo
|
121
|
+
And I checkout ticket.id
|
122
|
+
And I execute gitcycle with "ready"
|
123
|
+
Then output includes "Labeling issue as 'Pending Review'."
|
124
|
+
|
125
|
+
Scenario: Ready issue w/ parameters
|
126
|
+
When I cd to the user repo
|
127
|
+
And I execute gitcycle with "ready issue.id"
|
128
|
+
Then output includes "Labeling issues as 'Pending Review'."
|
129
|
+
|
130
|
+
Scenario: Reviewed issue w/ no parameters
|
131
|
+
When I cd to the user repo
|
132
|
+
And I checkout ticket.id
|
133
|
+
And I execute gitcycle with "reviewed"
|
134
|
+
Then output includes "Labeling issue as 'Pending QA'."
|
135
|
+
|
136
|
+
Scenario: Reviewed issue w/ parameters
|
137
|
+
When I cd to the user repo
|
138
|
+
And I execute gitcycle with "reviewed issue.id"
|
139
|
+
Then output includes "Labeling issues as 'Pending QA'."
|
140
|
+
|
141
|
+
Scenario: QA issue
|
142
|
+
When I cd to the owner repo
|
143
|
+
And I checkout master
|
144
|
+
And I execute gitcycle with "qa issue.id"
|
145
|
+
Then output includes "Retrieving branch information from gitcycle."
|
146
|
+
And output does not include "Checking out source branch 'master'."
|
147
|
+
And output does not include "Tracking source branch 'master'."
|
148
|
+
And output does not include "Deleting old QA branch 'qa_master'."
|
149
|
+
And output includes "Creating QA branch 'qa_master'."
|
150
|
+
And output includes "Adding remote repo 'config.user/config.repo'."
|
151
|
+
And output includes "Fetching remote branch 'ticket.id'."
|
152
|
+
And output includes "Merging remote branch 'ticket.id' from 'config.user/config.repo'."
|
153
|
+
And output includes "Pushing QA branch 'qa_master'."
|
154
|
+
And output includes "Type 'gitc qa pass' to approve all issues in this branch."
|
155
|
+
And output includes "Type 'gitc qa fail' to reject all issues in this branch."
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'lighthouse'
|
3
|
+
require 'redis'
|
4
|
+
require 'rspec/expectations'
|
5
|
+
require 'yaml'
|
6
|
+
require 'yajl'
|
7
|
+
|
8
|
+
BASE = File.expand_path '../../../', __FILE__
|
9
|
+
BIN = "#{BASE}/bin/gitc"
|
10
|
+
|
11
|
+
ENV['CONFIG'] = GITCYCLE = "#{BASE}/features/fixtures/gitcycle.yml"
|
12
|
+
ENV['ENV'] = 'development'
|
13
|
+
|
14
|
+
$redis = Redis.new
|
15
|
+
|
16
|
+
Before do |scenario|
|
17
|
+
@aruba_timeout_seconds = 10
|
18
|
+
@scenario_title = scenario.title
|
19
|
+
end
|
20
|
+
|
21
|
+
def branches(options={})
|
22
|
+
in_current_dir do
|
23
|
+
b = `git branch#{" -a" if options[:all]}`
|
24
|
+
if options[:current]
|
25
|
+
b.match(/\*\s+(.+)/)[1]
|
26
|
+
elsif options[:match]
|
27
|
+
b.match(/([\s]+|origin\/)(#{options[:match]})/)[2] rescue nil
|
28
|
+
else
|
29
|
+
b
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def config(reload=false)
|
35
|
+
@config = nil if reload
|
36
|
+
@config ||= YAML.load(File.read("#{BASE}/features/config.yml"))
|
37
|
+
Lighthouse.account = @config['lighthouse']['account']
|
38
|
+
Lighthouse.token = @config['lighthouse']['token']
|
39
|
+
@config
|
40
|
+
end
|
41
|
+
|
42
|
+
def gsub_variables(str)
|
43
|
+
if $ticket
|
44
|
+
str = str.gsub('ticket.id', $ticket.attributes['id'])
|
45
|
+
end
|
46
|
+
if $url
|
47
|
+
issue_id = $url.match(/https:\/\/github.com\/.+\/issues\/(\d+)/)[1]
|
48
|
+
str = str.gsub('issue.id', issue_id)
|
49
|
+
end
|
50
|
+
str = str.gsub('config.owner', config['owner'])
|
51
|
+
str = str.gsub('config.repo', config['repo'])
|
52
|
+
str = str.gsub('config.user', config['user'])
|
53
|
+
str
|
54
|
+
end
|
55
|
+
|
56
|
+
def log(match)
|
57
|
+
in_current_dir do
|
58
|
+
log = `git log --pretty=format:%s`
|
59
|
+
match = !(match =~ /^#{match}$/).nil?
|
60
|
+
end
|
61
|
+
match
|
62
|
+
end
|
63
|
+
|
64
|
+
def repos(reload=false)
|
65
|
+
if $repos && !reload
|
66
|
+
$repos
|
67
|
+
else
|
68
|
+
owner = "#{BASE}/features/fixtures/owner"
|
69
|
+
user = "#{BASE}/features/fixtures/user"
|
70
|
+
|
71
|
+
FileUtils.rm_rf(owner)
|
72
|
+
FileUtils.rm_rf(user)
|
73
|
+
|
74
|
+
system [
|
75
|
+
"cd #{BASE}/features/fixtures",
|
76
|
+
"git clone git@github.com:#{config['owner']}/#{config['repo']}.git owner",
|
77
|
+
"git clone git@github.com:#{config['user']}/#{config['repo']}.git user"
|
78
|
+
].join(' && ')
|
79
|
+
|
80
|
+
$repos = { :owner => owner, :user => user }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def run_gitcycle(cmd, interactive=false)
|
85
|
+
cmd = [ BIN, cmd ].join(' ')
|
86
|
+
if interactive
|
87
|
+
run_interactive(unescape(cmd))
|
88
|
+
else
|
89
|
+
run_simple(unescape(cmd), false)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
Given /^a fresh set of repositories$/ do
|
94
|
+
repos(true)
|
95
|
+
end
|
96
|
+
|
97
|
+
Given /^a new Lighthouse ticket$/ do
|
98
|
+
$ticket = Lighthouse::Ticket.new(
|
99
|
+
:body => "test",
|
100
|
+
:project_id => config['lighthouse']['project'],
|
101
|
+
:state => "open",
|
102
|
+
:title => "Test ticket"
|
103
|
+
)
|
104
|
+
$ticket.save
|
105
|
+
end
|
106
|
+
|
107
|
+
When /^I execute gitcycle with "([^\"]*)"$/ do |cmd|
|
108
|
+
cmd = gsub_variables(cmd)
|
109
|
+
run_gitcycle(cmd)
|
110
|
+
end
|
111
|
+
|
112
|
+
When /^I execute gitcycle setup$/ do
|
113
|
+
FileUtils.rm(GITCYCLE) if File.exists?(GITCYCLE)
|
114
|
+
run_gitcycle [
|
115
|
+
"setup",
|
116
|
+
config['user'],
|
117
|
+
config['repo'],
|
118
|
+
config['token_dev']
|
119
|
+
].join(' ')
|
120
|
+
run_gitcycle [
|
121
|
+
"setup",
|
122
|
+
config['user'],
|
123
|
+
"#{config['owner']}/#{config['repo']}",
|
124
|
+
config['token_qa']
|
125
|
+
].join(' ')
|
126
|
+
end
|
127
|
+
|
128
|
+
When /^I execute gitcycle with the Lighthouse ticket URL$/ do
|
129
|
+
run_gitcycle $ticket.url, true
|
130
|
+
end
|
131
|
+
|
132
|
+
When /^I cd to the (.*) repo$/ do |user|
|
133
|
+
dirs.pop
|
134
|
+
cd($repos[user.to_sym])
|
135
|
+
end
|
136
|
+
|
137
|
+
When /^I enter "([^\"]*)"$/ do |input|
|
138
|
+
input = gsub_variables(input)
|
139
|
+
type(input)
|
140
|
+
end
|
141
|
+
|
142
|
+
When /^I commit something$/ do
|
143
|
+
branch = branches(:current => true)
|
144
|
+
in_current_dir do
|
145
|
+
$commit_msg = "#{@scenario_title} - #{rand}"
|
146
|
+
File.open('README', 'w') {|f| f.write($commit_msg) }
|
147
|
+
`git add . && git add . -u && git commit -a -m '#{$commit_msg}'`
|
148
|
+
`git push origin #{branch}`
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
When /^I checkout (.+)$/ do |branch|
|
153
|
+
branch = gsub_variables(branch)
|
154
|
+
in_current_dir do
|
155
|
+
`git checkout #{branch}`
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
Then /^gitcycle\.yml should be valid$/ do
|
160
|
+
gitcycle = YAML.load(File.read(GITCYCLE))
|
161
|
+
|
162
|
+
repo = "#{config['user']}/#{config['repo']}"
|
163
|
+
gitcycle[repo].should == [ config['user'], config['token_dev'] ]
|
164
|
+
|
165
|
+
repo = "#{config['owner']}/#{config['repo']}"
|
166
|
+
gitcycle[repo].should == [ config['user'], config['token_qa'] ]
|
167
|
+
end
|
168
|
+
|
169
|
+
Then /^output includes \"([^\"]*)"$/ do |expected|
|
170
|
+
expected = gsub_variables(expected)
|
171
|
+
assert_partial_output(expected, all_output)
|
172
|
+
end
|
173
|
+
|
174
|
+
Then /^output includes \"([^\"]*)" with URL$/ do |expected|
|
175
|
+
puts all_output.inspect
|
176
|
+
expected = gsub_variables(expected)
|
177
|
+
assert_partial_output(expected, all_output)
|
178
|
+
$url = all_output.match(/^#{expected}.*(https?:\/\/[^\s]+)/)[1]
|
179
|
+
end
|
180
|
+
|
181
|
+
Then /^output does not include \"([^\"]*)"$/ do |expected|
|
182
|
+
expected = gsub_variables(expected)
|
183
|
+
assert_no_partial_output(expected, all_output)
|
184
|
+
end
|
185
|
+
|
186
|
+
Then /^redis entries valid$/ do
|
187
|
+
add = @scenario_title.include?('custom branch name') ? "-rename" : ""
|
188
|
+
branch = $redis.hget(
|
189
|
+
[
|
190
|
+
"users",
|
191
|
+
config['user'],
|
192
|
+
"repos",
|
193
|
+
"#{config['owner']}:#{config['repo']}",
|
194
|
+
"branches"
|
195
|
+
].join('/'),
|
196
|
+
$ticket.attributes['id'] + add
|
197
|
+
)
|
198
|
+
branch = Yajl::Parser.parse(branch)
|
199
|
+
should = {
|
200
|
+
'lighthouse_url' => $ticket.url,
|
201
|
+
'body' => "<div><p>test</p></div>\n\n#{$ticket.url}",
|
202
|
+
'name' => $ticket.attributes['id'] + add,
|
203
|
+
'id' => $ticket.attributes['id'] + add,
|
204
|
+
'title' => $ticket.title,
|
205
|
+
'repo' => "#{config['owner']}:#{config['repo']}",
|
206
|
+
'user' => config['user'],
|
207
|
+
'source' => 'master'
|
208
|
+
}
|
209
|
+
if @scenario_title == 'Discuss commits w/ no parameters and something committed'
|
210
|
+
should['issue_url'] = $url
|
211
|
+
end
|
212
|
+
branch.should == should
|
213
|
+
end
|
214
|
+
|
215
|
+
Then /^current branch is \"([^\"]*)"$/ do |branch|
|
216
|
+
branches(:current => true).should == gsub_variables(branch)
|
217
|
+
end
|
218
|
+
|
219
|
+
Then /^git log should contain the last commit$/ do
|
220
|
+
log($commit_msg).should == true
|
221
|
+
end
|
222
|
+
|
223
|
+
Then /^URL is a valid issue$/ do
|
224
|
+
$url.should =~ /https:\/\/github.com\/.+\/issues\/\d+/
|
225
|
+
end
|
data/gitcycle.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
root = File.expand_path('../', __FILE__)
|
3
|
+
lib = "#{root}/lib"
|
4
|
+
|
5
|
+
$:.unshift lib unless $:.include?(lib)
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "gitcycle"
|
9
|
+
s.version = '0.1.0'
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = [ 'Winton Welsh' ]
|
12
|
+
s.email = [ 'mail@wintoni.us' ]
|
13
|
+
s.homepage = "https://github.com/winton/gitcycle"
|
14
|
+
s.summary = %q{Tame your development cycle}
|
15
|
+
s.description = %q{Tame your development cycle.}
|
16
|
+
|
17
|
+
s.executables = `cd #{root} && git ls-files bin/*`.split("\n").collect { |f| File.basename(f) }
|
18
|
+
s.files = `cd #{root} && git ls-files`.split("\n")
|
19
|
+
s.require_paths = %w(lib)
|
20
|
+
s.test_files = `cd #{root} && git ls-files -- {features,test,spec}/*`.split("\n")
|
21
|
+
|
22
|
+
s.add_development_dependency "aruba"
|
23
|
+
s.add_development_dependency "cucumber"
|
24
|
+
s.add_development_dependency "lighthouse"
|
25
|
+
s.add_development_dependency "redis"
|
26
|
+
s.add_development_dependency "rspec"
|
27
|
+
s.add_development_dependency "yajl-ruby"
|
28
|
+
|
29
|
+
s.add_dependency "launchy", "= 2.0.5"
|
30
|
+
s.add_dependency "yajl-ruby", "= 1.1.0"
|
31
|
+
end
|
data/lib/ext/string.rb
ADDED
data/lib/gitcycle.rb
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
gem 'launchy', '= 2.0.5'
|
6
|
+
require 'launchy'
|
7
|
+
|
8
|
+
gem 'yajl-ruby', '= 1.1.0'
|
9
|
+
require 'yajl'
|
10
|
+
|
11
|
+
$:.unshift File.dirname(__FILE__)
|
12
|
+
|
13
|
+
require "ext/string"
|
14
|
+
|
15
|
+
class Gitcycle
|
16
|
+
|
17
|
+
API =
|
18
|
+
if ENV['ENV'] == 'development'
|
19
|
+
"http://127.0.0.1:8080/api"
|
20
|
+
else
|
21
|
+
"http://gitcycle.bleacherreport.com/api"
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
if ENV['CONFIG']
|
26
|
+
@config_path = File.expand_path(ENV['CONFIG'])
|
27
|
+
else
|
28
|
+
@config_path = File.expand_path("~/.gitcycle.yml")
|
29
|
+
end
|
30
|
+
load_config
|
31
|
+
load_git
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_branch(url_or_title)
|
35
|
+
require_git && require_config
|
36
|
+
|
37
|
+
params = {}
|
38
|
+
|
39
|
+
if url_or_title.strip[0..3] == 'http'
|
40
|
+
if url_or_title.include?('lighthouseapp.com/')
|
41
|
+
params = { 'branch[lighthouse_url]' => url_or_title }
|
42
|
+
elsif url_or_title.include?('github.com/')
|
43
|
+
params = { 'branch[issue_url]' => url_or_title }
|
44
|
+
end
|
45
|
+
else
|
46
|
+
params = {
|
47
|
+
'branch[name]' => url_or_title,
|
48
|
+
'branch[title]' => url_or_title
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
puts "\nRetrieving branch information from gitcycle.\n".green
|
53
|
+
branch = get('branch', params)
|
54
|
+
|
55
|
+
name = branch['name']
|
56
|
+
|
57
|
+
unless branch['exists']
|
58
|
+
branch['source'] = branches(:current => true)
|
59
|
+
|
60
|
+
unless yes?("Would you like to name your branch '#{name}'?")
|
61
|
+
name = q("\nWhat would you like to name your branch?")
|
62
|
+
name = name.gsub(/[\s\W]/, '-')
|
63
|
+
end
|
64
|
+
|
65
|
+
unless branches(:match => name)
|
66
|
+
puts "\nCreating '#{name}' from '#{branch['source']}'.\n".green
|
67
|
+
run("git branch #{name}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if branches(:match => name)
|
72
|
+
puts "Checking out branch '#{name}'.\n".green
|
73
|
+
run("git checkout #{name}")
|
74
|
+
else
|
75
|
+
puts "Tracking branch '#{name}'.\n".green
|
76
|
+
run("git fetch && git checkout -b #{name} origin/#{name}")
|
77
|
+
end
|
78
|
+
|
79
|
+
unless branch['exists']
|
80
|
+
puts "Pushing '#{name}'.".green
|
81
|
+
run("git push origin #{name}")
|
82
|
+
|
83
|
+
puts "Sending branch information to gitcycle.".green
|
84
|
+
get('branch',
|
85
|
+
'branch[name]' => branch['name'],
|
86
|
+
'branch[rename]' => name != branch['name'] ? name : nil,
|
87
|
+
'branch[source]' => branch['source']
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
puts "\n"
|
92
|
+
end
|
93
|
+
|
94
|
+
def discuss(*issues)
|
95
|
+
require_git && require_config
|
96
|
+
|
97
|
+
puts "\nRetrieving branch information from gitcycle.\n".green
|
98
|
+
|
99
|
+
if issues.empty?
|
100
|
+
branch = get('branch',
|
101
|
+
'branch[name]' => branches(:current => true),
|
102
|
+
'create' => 0
|
103
|
+
)
|
104
|
+
|
105
|
+
if branch && !branch['issue_url']
|
106
|
+
puts "Creating GitHub pull request.\n".green
|
107
|
+
branch = get('branch',
|
108
|
+
'branch[create_pull_request]' => true,
|
109
|
+
'branch[name]' => branch['name'],
|
110
|
+
'create' => 0
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
if branch == false
|
115
|
+
puts "Branch not found.\n".red
|
116
|
+
elsif branch['issue_url']
|
117
|
+
puts "Opening issue: #{branch['issue_url']}\n".green
|
118
|
+
Launchy.open(branch['issue_url'])
|
119
|
+
else
|
120
|
+
puts "You must push code before opening a pull request.\n".red
|
121
|
+
end
|
122
|
+
else
|
123
|
+
get('branch', 'issues' => issues, 'scope' => 'repo').each do |branch|
|
124
|
+
if branch['issue_url']
|
125
|
+
puts "Opening issue: #{branch['issue_url']}\n".green
|
126
|
+
Launchy.open(branch['issue_url'])
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def pull
|
133
|
+
require_git && require_config
|
134
|
+
|
135
|
+
puts "\nRetrieving branch information from gitcycle.\n".green
|
136
|
+
branch = get('branch',
|
137
|
+
'branch[name]' => branches(:current => true),
|
138
|
+
'include' => [ 'repo' ],
|
139
|
+
'create' => 0
|
140
|
+
)
|
141
|
+
|
142
|
+
merge_remote_branch(
|
143
|
+
:owner => branch['repo']['owner'],
|
144
|
+
:repo => branch['repo']['name'],
|
145
|
+
:branch => branch['source']
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def qa(*issues)
|
150
|
+
require_git && require_config
|
151
|
+
|
152
|
+
if issues.empty?
|
153
|
+
puts "\n"
|
154
|
+
get('qa_branch').each do |branches|
|
155
|
+
puts "qa_#{branches['source']}".green
|
156
|
+
branches['branches'].each do |branch|
|
157
|
+
puts " #{"issue ##{branch['issue']}".yellow}\t#{branch['user']}/#{branch['branch']}"
|
158
|
+
end
|
159
|
+
puts "\n"
|
160
|
+
end
|
161
|
+
elsif issues.first == 'fail' || issues.first == 'pass'
|
162
|
+
branch = branches(:current => true)
|
163
|
+
label = issues.first.capitalize
|
164
|
+
|
165
|
+
if branch =~ /^qa_/
|
166
|
+
puts "\nRetrieving branch information from gitcycle.\n".green
|
167
|
+
qa_branch = get('qa_branch', :source => branch.gsub(/^qa_/, ''))
|
168
|
+
|
169
|
+
puts "Checking out #{qa_branch['source']}.".green
|
170
|
+
run("git checkout #{qa_branch['source']}")
|
171
|
+
|
172
|
+
if issues[1..-1].empty?
|
173
|
+
puts "Merging '#{branch}' into '#{qa_branch['source']}'.\n".green
|
174
|
+
run("git merge #{branch}")
|
175
|
+
run("git push origin #{qa_branch['source']}")
|
176
|
+
|
177
|
+
puts "\nLabeling all issues as '#{label}'.\n".green
|
178
|
+
get('label',
|
179
|
+
'qa_branch[source]' => branch.gsub(/^qa_/, ''),
|
180
|
+
'labels' => [ label ]
|
181
|
+
)
|
182
|
+
|
183
|
+
branches = qa_branch['branches']
|
184
|
+
else
|
185
|
+
issues = [1..-1]
|
186
|
+
|
187
|
+
branches = qa_branch['branches'].select do |b|
|
188
|
+
issues.include?(b['issue'])
|
189
|
+
end
|
190
|
+
|
191
|
+
branches.each do |branch|
|
192
|
+
merge_remote_branch(
|
193
|
+
:user => branch['user'],
|
194
|
+
:repo => branch['repo'].split(':'),
|
195
|
+
:branch => branch['branch']
|
196
|
+
)
|
197
|
+
|
198
|
+
puts "\nLabeling issue #{branch['issue']} as '#{label}'.\n".green
|
199
|
+
get('label',
|
200
|
+
'qa_branch[source]' => branch.gsub(/^qa_/, ''),
|
201
|
+
'issue' => branch['issue'],
|
202
|
+
'labels' => [ label ]
|
203
|
+
)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
puts "\nMarking Lighthouse tickets as 'pending-approval'.\n".green
|
208
|
+
branches = branches.collect do |b|
|
209
|
+
{ :name => b['branch'], :repo => b['repo'], :user => b['user'] }
|
210
|
+
end
|
211
|
+
get('ticket_resolve', 'branches' => Yajl::Encoder.encode(branches))
|
212
|
+
else
|
213
|
+
puts "\nYou are not in a QA branch.\n".red
|
214
|
+
end
|
215
|
+
elsif issues.first == 'resolved'
|
216
|
+
branch = branches(:current => true)
|
217
|
+
|
218
|
+
if branch =~ /^qa_/
|
219
|
+
puts "\nRetrieving branch information from gitcycle.\n".green
|
220
|
+
qa_branch = get('qa_branch', :source => branch.gsub(/^qa_/, ''))
|
221
|
+
|
222
|
+
if qa_branch
|
223
|
+
branches = qa_branch['branches']
|
224
|
+
conflict = branches.detect { |branch| branch['conflict'] }
|
225
|
+
|
226
|
+
if conflict
|
227
|
+
puts "Committing merge resolution of #{conflict['branch']} (issue ##{conflict['issue']}).\n".green
|
228
|
+
run("git add . && git add . -u && git commit -a -m 'Merge conflict resolution of #{conflict['branch']} (issue ##{conflict['issue']})'")
|
229
|
+
|
230
|
+
puts "Pushing merge resolution of #{conflict['branch']} (issue ##{conflict['issue']}).\n".green
|
231
|
+
run("git push origin qa_#{qa_branch['source']}")
|
232
|
+
|
233
|
+
create_qa_branch(
|
234
|
+
:preserve => true,
|
235
|
+
:range => (branches.index(conflict)+1..-1),
|
236
|
+
:qa_branch => qa_branch
|
237
|
+
)
|
238
|
+
else
|
239
|
+
puts "Couldn't find record of a conflicted merge.\n".red
|
240
|
+
end
|
241
|
+
else
|
242
|
+
puts "Couldn't find record of a conflicted merge.\n".red
|
243
|
+
end
|
244
|
+
else
|
245
|
+
puts "\nYou aren't on a QA branch.\n".red
|
246
|
+
end
|
247
|
+
else
|
248
|
+
create_qa_branch(:issues => issues)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def ready(*issues)
|
253
|
+
require_git && require_config
|
254
|
+
|
255
|
+
if issues.empty?
|
256
|
+
puts "\nLabeling issue as 'Pending Review'.\n".green
|
257
|
+
get('label',
|
258
|
+
'branch[name]' => branches(:current => true),
|
259
|
+
'labels' => [ 'Pending Review' ]
|
260
|
+
)
|
261
|
+
else
|
262
|
+
puts "\nLabeling issues as 'Pending Review'.\n".green
|
263
|
+
get('label',
|
264
|
+
'issues' => issues,
|
265
|
+
'labels' => [ 'Pending Review' ],
|
266
|
+
'scope' => 'repo'
|
267
|
+
)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def reviewed(*issues)
|
272
|
+
require_git && require_config
|
273
|
+
|
274
|
+
if issues.empty?
|
275
|
+
puts "\nLabeling issue as 'Pending QA'.\n".green
|
276
|
+
get('label',
|
277
|
+
'branch[name]' => branches(:current => true),
|
278
|
+
'labels' => [ 'Pending QA' ]
|
279
|
+
)
|
280
|
+
else
|
281
|
+
puts "\nLabeling issues as 'Pending QA'.\n".green
|
282
|
+
get('label',
|
283
|
+
'issues' => issues,
|
284
|
+
'labels' => [ 'Pending QA' ],
|
285
|
+
'scope' => 'repo'
|
286
|
+
)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def setup(login, repo, token)
|
291
|
+
repo = "#{login}/#{repo}" unless repo.include?('/')
|
292
|
+
@config[repo] = [ login, token ]
|
293
|
+
save_config
|
294
|
+
puts "\nConfiguration saved.\n".green
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
|
299
|
+
def branches(options={})
|
300
|
+
b = `git branch#{" -a" if options[:all]}`
|
301
|
+
if options[:current]
|
302
|
+
b.match(/\*\s+(.+)/)[1]
|
303
|
+
elsif options[:match]
|
304
|
+
b.match(/([\s]+|origin\/)(#{options[:match]})/)[2] rescue nil
|
305
|
+
else
|
306
|
+
b
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def create_qa_branch(options)
|
311
|
+
issues = options[:issues]
|
312
|
+
range = options[:range] || (0..-1)
|
313
|
+
|
314
|
+
if (issues && !issues.empty?) || options[:qa_branch]
|
315
|
+
if options[:qa_branch]
|
316
|
+
qa_branch = options[:qa_branch]
|
317
|
+
else
|
318
|
+
puts "\nRetrieving branch information from gitcycle.\n".green
|
319
|
+
qa_branch = get('qa_branch', 'issues' => issues)
|
320
|
+
end
|
321
|
+
|
322
|
+
source = qa_branch['source']
|
323
|
+
name = "qa_#{source}"
|
324
|
+
|
325
|
+
unless qa_branch['branches'].empty?
|
326
|
+
unless options[:preserve]
|
327
|
+
if branches(:current => source)
|
328
|
+
# Do nothing
|
329
|
+
elsif branches(:match => source)
|
330
|
+
puts "Checking out source branch '#{source}'.\n".green
|
331
|
+
run("git checkout #{source}")
|
332
|
+
else
|
333
|
+
puts "Tracking source branch '#{source}'.\n".green
|
334
|
+
run("git fetch && git checkout -b #{source} origin/#{source}")
|
335
|
+
end
|
336
|
+
|
337
|
+
if branches(:match => name)
|
338
|
+
puts "Deleting old QA branch '#{name}'.\n".green
|
339
|
+
run("git branch -D #{name}")
|
340
|
+
run("git push origin :#{name}")
|
341
|
+
end
|
342
|
+
|
343
|
+
puts "Creating QA branch '#{name}'.\n".green
|
344
|
+
run("git branch #{name} && git checkout #{name}")
|
345
|
+
|
346
|
+
puts "\n"
|
347
|
+
end
|
348
|
+
|
349
|
+
qa_branch['branches'][range].each do |branch|
|
350
|
+
issue = branch['issue']
|
351
|
+
owner, repo = branch['repo'].split(':')
|
352
|
+
user = branch['user']
|
353
|
+
branch = branch['branch']
|
354
|
+
|
355
|
+
output = merge_remote_branch(
|
356
|
+
:owner => user,
|
357
|
+
:repo => repo,
|
358
|
+
:branch => branch
|
359
|
+
)
|
360
|
+
|
361
|
+
if output.include?('CONFLICT')
|
362
|
+
puts "Conflict occurred when merging '#{branch}' (issue ##{issue}).\n".red
|
363
|
+
answer = q("Would you like to (s)kip or (r)esolve?")
|
364
|
+
|
365
|
+
issues = qa_branch['branches'].collect { |b| b['issue'] }
|
366
|
+
|
367
|
+
if answer.downcase[0..0] == 's'
|
368
|
+
run("git reset --hard HEAD")
|
369
|
+
issues.delete(issue)
|
370
|
+
|
371
|
+
puts "\nSending QA branch information to gitcycle.\n".green
|
372
|
+
get('qa_branch', 'issues' => issues, 'conflict' => issue)
|
373
|
+
else
|
374
|
+
puts "\nSending conflict information to gitcycle.\n".green
|
375
|
+
get('qa_branch', 'issues' => issues, 'conflict' => issue)
|
376
|
+
|
377
|
+
puts "Type 'gitc qa resolved' when finished resolving.\n".yellow
|
378
|
+
exit
|
379
|
+
end
|
380
|
+
else
|
381
|
+
puts "Pushing QA branch '#{name}'.\n".green
|
382
|
+
run("git push origin #{name}")
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
puts "\nType '".yellow + "gitc qa pass".green + "' to approve all issues in this branch.\n".yellow
|
387
|
+
puts "Type '".yellow + "gitc qa fail".red + "' to reject all issues in this branch.\n".yellow
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def get(path, hash={})
|
393
|
+
hash.merge!(
|
394
|
+
:login => @login,
|
395
|
+
:token => @token
|
396
|
+
)
|
397
|
+
|
398
|
+
params = ''
|
399
|
+
hash[:session] = 0
|
400
|
+
hash.each do |k, v|
|
401
|
+
if v
|
402
|
+
params << "#{URI.escape(k.to_s)}=#{URI.escape(v.to_s)}&"
|
403
|
+
end
|
404
|
+
end
|
405
|
+
params.chop! # trailing &
|
406
|
+
|
407
|
+
json = open("#{API}/#{path}.json?#{params}").read
|
408
|
+
Yajl::Parser.parse(json)
|
409
|
+
end
|
410
|
+
|
411
|
+
def load_config
|
412
|
+
if File.exists?(@config_path)
|
413
|
+
@config = YAML.load(File.read(@config_path))
|
414
|
+
else
|
415
|
+
@config = {}
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def load_git
|
420
|
+
path = "#{Dir.pwd}/.git/config"
|
421
|
+
if File.exists?(path)
|
422
|
+
@git_url = File.read(path).match(/\[remote "origin"\][^\[]*url = ([^\n]+)/m)[1]
|
423
|
+
@git_repo = @git_url.match(/\/(.+)\./)[1]
|
424
|
+
@git_login = @git_url.match(/:(.+)\//)[1]
|
425
|
+
@login, @token = @config["#{@git_login}/#{@git_repo}"] rescue [ nil, nil ]
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def merge_remote_branch(options={})
|
430
|
+
owner = options[:owner]
|
431
|
+
repo = options[:repo]
|
432
|
+
branch = options[:branch]
|
433
|
+
|
434
|
+
$remotes ||= {}
|
435
|
+
|
436
|
+
unless $remotes[owner]
|
437
|
+
$remotes[owner] = true
|
438
|
+
puts "Adding remote repo '#{owner}/#{repo}'.\n".green
|
439
|
+
run("git remote rm #{owner}") if remotes(:match => owner)
|
440
|
+
run("git remote add #{owner} git@github.com:#{owner}/#{repo}.git")
|
441
|
+
end
|
442
|
+
|
443
|
+
puts "\nFetching remote branch '#{branch}'.\n".green
|
444
|
+
run("git fetch #{owner}")
|
445
|
+
|
446
|
+
puts "\nMerging remote branch '#{branch}' from '#{owner}/#{repo}'.\n".green
|
447
|
+
run("git merge #{owner}/#{branch}")
|
448
|
+
end
|
449
|
+
|
450
|
+
def remotes(options={})
|
451
|
+
b = `git remote`
|
452
|
+
if options[:match]
|
453
|
+
b.match(/^(#{options[:match]})$/)[1] rescue nil
|
454
|
+
else
|
455
|
+
b
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def require_config
|
460
|
+
unless @login && @token
|
461
|
+
puts "\nGitcycle configuration not found.".red
|
462
|
+
puts "Are you in the right repository?".yellow
|
463
|
+
puts "Have you set up this repository at http://gitcycle.com?\n".yellow
|
464
|
+
exit
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def require_git
|
469
|
+
unless @git_url && @git_repo && @git_login
|
470
|
+
puts "\norigin entry within '.git/config' not found!".red
|
471
|
+
puts "Are you sure you are in a git repository?\n".yellow
|
472
|
+
exit
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
def save_config
|
477
|
+
File.open(@config_path, 'w') do |f|
|
478
|
+
f.write(YAML.dump(@config))
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def q(question, extra='')
|
483
|
+
puts "#{question.yellow}#{extra}"
|
484
|
+
$stdin.gets.strip
|
485
|
+
end
|
486
|
+
|
487
|
+
def run(cmd)
|
488
|
+
if ENV['RUN'] == '0'
|
489
|
+
puts cmd
|
490
|
+
else
|
491
|
+
`#{cmd}`
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def yes?(question)
|
496
|
+
q(question, " (#{"y".green}/#{"n".red})").downcase[0..0] == 'y'
|
497
|
+
end
|
498
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitcycle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Winton Welsh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aruba
|
16
|
+
requirement: &70199852896960 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70199852896960
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: cucumber
|
27
|
+
requirement: &70199852895300 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70199852895300
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: lighthouse
|
38
|
+
requirement: &70199852894720 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70199852894720
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: redis
|
49
|
+
requirement: &70199852894100 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70199852894100
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &70199852893240 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70199852893240
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yajl-ruby
|
71
|
+
requirement: &70199852892360 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70199852892360
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: launchy
|
82
|
+
requirement: &70199852879100 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - =
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 2.0.5
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70199852879100
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: yajl-ruby
|
93
|
+
requirement: &70199852877580 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - =
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.1.0
|
99
|
+
type: :runtime
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70199852877580
|
102
|
+
description: Tame your development cycle.
|
103
|
+
email:
|
104
|
+
- mail@wintoni.us
|
105
|
+
executables:
|
106
|
+
- gitc
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- .gitignore
|
111
|
+
- Gemfile
|
112
|
+
- LICENSE
|
113
|
+
- README.md
|
114
|
+
- Rakefile
|
115
|
+
- bin/gitc
|
116
|
+
- features/config.example.yml
|
117
|
+
- features/gitcycle.feature
|
118
|
+
- features/steps/gitcycle_steps.rb
|
119
|
+
- gitcycle.gemspec
|
120
|
+
- lib/ext/string.rb
|
121
|
+
- lib/gitcycle.rb
|
122
|
+
homepage: https://github.com/winton/gitcycle
|
123
|
+
licenses: []
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ! '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 1.8.10
|
143
|
+
signing_key:
|
144
|
+
specification_version: 3
|
145
|
+
summary: Tame your development cycle
|
146
|
+
test_files:
|
147
|
+
- features/config.example.yml
|
148
|
+
- features/gitcycle.feature
|
149
|
+
- features/steps/gitcycle_steps.rb
|