gitcycle 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|