git-process 0.9.4 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/README.md +61 -5
- data/bin/git-sync +3 -1
- data/lib/git-process/git_process.rb +5 -0
- data/lib/git-process/new_fb.rb +1 -1
- data/lib/git-process/sync.rb +34 -14
- data/lib/git-process/version.rb +1 -1
- data/spec/new_fb_spec.rb +9 -0
- data/spec/sync_spec.rb +78 -57
- metadata +3 -3
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,22 +1,39 @@
|
|
1
1
|
[![Build Status](https://secure.travis-ci.org/jdigger/git-process.png)](http://travis-ci.org/jdigger/git-process)
|
2
2
|
|
3
|
-
# Purpose #
|
3
|
+
# Purpose and Motivation #
|
4
4
|
|
5
5
|
This provides an easy way to work with a sane git workflow process.
|
6
|
-
|
6
|
+
Short-lived feature branches can work quite well, but the formal git-flow process can be rather heavy in cost.
|
7
7
|
|
8
8
|
# Installation #
|
9
9
|
|
10
|
-
$ gem install git-process
|
10
|
+
$ sudo gem install git-process
|
11
|
+
|
12
|
+
## Configurables ##
|
13
|
+
See notes for more details
|
11
14
|
|
15
|
+
* OAuth2 Token
|
16
|
+
* The name of the integration branch (defaults to `origin/master`, but can be set to `develop` or other)
|
12
17
|
|
18
|
+
---
|
13
19
|
# Overview #
|
14
20
|
|
21
|
+
## Anticipated Use Cases ##
|
22
|
+
|
23
|
+
1. User Creates new local branch
|
24
|
+
1. User pushes local branch to remote (as feature branch) by rebasing integration branch, then pushing branch to remote
|
25
|
+
1. User closes local branch by rebasing integration branch first, then pushing local to integration
|
26
|
+
1. User initiates GitHub "pull request"
|
27
|
+
|
28
|
+
## Command List ##
|
29
|
+
|
15
30
|
* `git new-fb` - Create a new feature branch based on the integration branch.
|
16
31
|
* `git sync` - Gets the latest changes that have happened on the integration branch, then pushes your changes to a "private" branch on the server.
|
17
32
|
* `git pull-request` - Creates a Pull Request for the current branch.
|
18
33
|
* `git to-master` - Rebase against the integration branch, then pushes to it.
|
19
34
|
|
35
|
+
**All commands are well documented within themselves: Use the "-h" switch to see the full documentation.** (e.g., "`git sync -h`")
|
36
|
+
|
20
37
|
# Workflow #
|
21
38
|
|
22
39
|
_The following assumes that the integration branch is "origin/master"._
|
@@ -26,7 +43,7 @@ _The following assumes that the integration branch is "origin/master"._
|
|
26
43
|
1. When starting work on a new feature, use "`git new-fb feature-name`".
|
27
44
|
* This creates a new branch called "`feature-name`" based on "`origin/master`".
|
28
45
|
2. After making some changes, if you want to pick up any changes other people have made, as well
|
29
|
-
as save your work on the server, do "`git
|
46
|
+
as save your work on the server, do "`git sync`".
|
30
47
|
* That will merge in the changes that have occurred in "`origin/master`" and then push the
|
31
48
|
result to the "`feature_branch`" branch to the server.
|
32
49
|
3. When you feel your work is ready for others to look at, do another "`git sync`" to post your
|
@@ -41,7 +58,7 @@ _The following assumes that the integration branch is "origin/master"._
|
|
41
58
|
1. When starting work on a new feature, use "`git new-fb feature-name`".
|
42
59
|
* This creates a new branch called "`feature-name`" based on "`origin/master`".
|
43
60
|
2. After making some changes, if you want to pick up any changes other people have made, as well
|
44
|
-
as save your work on the server, do "`git
|
61
|
+
as save your work on the server, do "`git sync`".
|
45
62
|
* That will merge in the changes that have occurred in "`origin/master`" and then push the
|
46
63
|
result to the "`feature_branch`" branch to the server.
|
47
64
|
3. When you are ready to merge your work into the mainline, "`git to-master`".
|
@@ -71,6 +88,45 @@ _The following assumes that the integration branch is "origin/master"._
|
|
71
88
|
* This tries to respond "intelligently" to the use of 'rerere'.
|
72
89
|
|
73
90
|
|
91
|
+
# FAQ #
|
92
|
+
|
93
|
+
## Q: How is this different from git-flow or GitHub flow? ##
|
94
|
+
|
95
|
+
["git-flow"](http://nvie.com/posts/a-successful-git-branching-model/) is designed around having a very strongly defined process around keeping new development, hotfixes, release process changes, etc. all clearly separated. The problem I have with it is that it's too much "process" for not enough gain. (It has a waterfall feel to it, very much against the more modern [Continuous Delivery](http://continuousdelivery.com/) approach.)
|
96
|
+
|
97
|
+
["GitHub Flow"](http://scottchacon.com/2011/08/31/github-flow.html) is a lot cleaner, but relies too heavily (IMHO) on web-based tools and on merging instead of rebasing. It is also focussed very tightly on a Continuous Deployment process, which is great for them, but not practical for everyone.
|
98
|
+
|
99
|
+
|
100
|
+
## Q: Wait, I heard "branches are evil." Why should I do something evil? ##
|
101
|
+
|
102
|
+
Branches are extremely powerful tools that allow for clean organization/modularization of development.
|
103
|
+
|
104
|
+
* Branches make it easy to sandbox changes while they are in a state of flux, while at the same time be very fearless about making potentially breaking changes.
|
105
|
+
* For example, I commit "green to green": Doing [TDD](http://en.wikipedia.org/wiki/Test-driven_development), I commit every time I have a newly passing test case. So, assuming I'm in a regular development flow, I'm committing my changes every five minutes. Tiny commits, but lots of them. What that means is that if I make a "less than wise choice" at some point, it's trivial to rewind to before I'd made the mistake, potentially keep the throw-away code in another branch while I do my cleanup, and generally use the full power of a revision control system to make my life safer and easier. The branch(es) are pretty chaotic, but that's not a problem because before integrating with the mainline, I take a moment to cleanup: Squash related commits together, write clearer commit messages (since now I know what "the answer" is), and generally move from my drafts to a more finished result. (See below on objections related to "lying with rebase.") That may just be me, though, because I'm very paranoid when it comes to computers. I tend to automatically hit Cmd/Ctl-S every time I type a period when I'm writing, or when I close a block when I'm programming. I have a minimum of three copies/backups around the world of all my important documents. And I "`git sync`" frequently to make sure my machine isn't the only place where all my hard work is being stored. Have I mentioned I don't trust computers?
|
106
|
+
|
107
|
+
* Branches allow for focused collaboration. Because a branch is about exactly one thing, it means that a team can collaborate around a feature/bug (especially when used in conjunction with a "pull request"), and keep such changes sandboxed until such time that they are ready to bring a larger audience into the mix.
|
108
|
+
* Branches encourage being less "shy" about your code. I have heard, on a number of occasions, developers say "I'm not ready to push this to the server yet because [it's still rough (and embarrassing)]/[it may break other people]/etc." All of those reasons for "hoarding" code are moot with branches.
|
109
|
+
|
110
|
+
Jez Humble, a brilliant Principle at ThoughtWorks Studios, talks a lot about how "branches are evil." Unfortunately, people hear that, know how smart he is, and simply repeat it without really understanding what his objections are. Fortunately, he [posted clarification about what's really meant by that](http://continuousdelivery.com/2011/07/on-dvcs-continuous-integration-and-feature-branches/). He essentially says that the problem is that developers abuse branches by not merging with mainline (i.e., "master") on a regular basis. Not constantly getting changes *from* mainline makes life rough when it comes time to integrate. Not putting your changes *into* mainline means that your changes are not being validated (via [Continuous Integration](http://martinfowler.com/articles/continuousIntegration.html), or -- better -- with [Continuous Delivery](http://continuousdelivery.com/)). Both are, in fact, sins akin to not doing automated testing.
|
111
|
+
|
112
|
+
Making it "easier to do things right than wrong" (i.e., using branches and keeping them synced with mainline) was the primary motivation for this project. This should be especially evident in the "`git sync`" and "`git to-master`" commands.
|
113
|
+
|
114
|
+
|
115
|
+
## Q: Why so much emphasis on rebasing? Isn't rebasing a dangerous lie? ##
|
116
|
+
|
117
|
+
Like any powerful tool, "`git rebase`" is "dangerous" if used incorrectly, just like "`rm -rf`". You simply need to know when and how to use it safely. And in the world of version control systems, "rebasing" is easily one of the most _**useful**_ tools to come around since the "`commit`" command.
|
118
|
+
|
119
|
+
[A famous article](http://paul.stadig.name/2010/12/thou-shalt-not-lie-git-rebase-ammend.html) that people have been parroting in various forms for a while makes the case that rebasing (and its various forms, such as squashing, amending commits, etc.) is a "lie." As with so many things, context is everything.
|
120
|
+
|
121
|
+
You almost certainly should *not* rebase things that you have "published." Generally this really means "Don't rebase the 'master' branch!" Fortunately, these scripts make it impossible to rebase the mainline by accident. By default "`git sync`" uses "merge" instead of "rebase" to encourage collaboration. (Though you can easily use "-r" if you know no one else is working on the branch.) When it's time to actually merge your work into the mainline (and thus no one is working against it except in the context of mainline), that's when it gets rebased in.
|
122
|
+
|
123
|
+
Rebasing "your" code is an extremely useful way of communicating clearly. In the "green to green" scenario above about branches, a lot of noise is generated. If someone wants to review my code, or cherry-pick in my changes, it's too much of a mess to effectively do so. Also, as part of the process of squashing, I have the opportunity to write clearer commit message based upon my newly enhanced understanding. The intermediate commits were my "drafts" and I'm now submitting my cleaned up copy.
|
124
|
+
|
125
|
+
If you have ever seen an "active" project that uses a process like "git-flow" that encourages a lot of branching and merging, you've seen how hard it can be to follow a particular line of development. Branch lines are flying around everywhere, and half the commits are pretty much pure noise. (e.g., "Merge branch 'develop' of ... into develop".) It's also hard to follow the order in which commits actually impacted the mainline. In many ways, in practice merges turn into "a truth effectively being a lie (because it's buried in the noise)" versus rebases that are "a lie (changed from it's 'original' form) to tell an effective truth (clean and very clear about its impact)."
|
126
|
+
|
127
|
+
I am trying to promote clear communication about current reality over micro-management over no-longer-relevant history. Thus the judicious use of rebase.
|
128
|
+
|
129
|
+
|
74
130
|
# Contributing #
|
75
131
|
|
76
132
|
## Coding Setup ##
|
data/bin/git-sync
CHANGED
@@ -17,7 +17,7 @@ DESCRIPTION
|
|
17
17
|
|
18
18
|
This fetches the latest repository from the server, rebases/merges the current branch \
|
19
19
|
against the changes in the integration branch, then pushes the result up to a branch on \
|
20
|
-
the server of the same name.
|
20
|
+
the server of the same name. (Unless told not to.)
|
21
21
|
|
22
22
|
If there is a problem, such as a merge conflict, this tries to \
|
23
23
|
resolve it automatically. If it can not do so in an automated way, \
|
@@ -40,8 +40,10 @@ DESC
|
|
40
40
|
parser.opt :rebase, "Rebase instead of merge against the integration branch"
|
41
41
|
parser.opt :merge, "Merge instead of rebase against the integration branch", :short => :none, :default => true
|
42
42
|
parser.opt :force, "Force the push; defaults to true if --rebase is used", :short => :f, :default => false
|
43
|
+
parser.opt :local, "Do not do a push; gets remote changes, but does not update the server", :short => :l, :default => false
|
43
44
|
|
44
45
|
parser.conflicts :rebase, :merge
|
46
|
+
parser.conflicts :local, :force
|
45
47
|
end
|
46
48
|
|
47
49
|
|
data/lib/git-process/new_fb.rb
CHANGED
data/lib/git-process/sync.rb
CHANGED
@@ -21,8 +21,17 @@ module GitProc
|
|
21
21
|
class Sync < Process
|
22
22
|
|
23
23
|
def initialize(dir, opts)
|
24
|
+
opts[:force] = true if opts[:rebase]
|
25
|
+
|
26
|
+
if !opts[:merge].nil? and opts[:merge] == opts[:rebase]
|
27
|
+
raise ArgumentError.new(":merge = #{opts[:merge]} and :rebase = #{opts[:rebase]}")
|
28
|
+
end
|
29
|
+
|
30
|
+
raise ArgumentError.new(":rebase is not set") if opts[:rebase].nil?
|
31
|
+
|
24
32
|
@do_rebase = opts[:rebase]
|
25
33
|
@force = opts[:force]
|
34
|
+
@local = opts[:local]
|
26
35
|
super
|
27
36
|
end
|
28
37
|
|
@@ -31,8 +40,8 @@ module GitProc
|
|
31
40
|
raise UncommittedChangesError.new unless status.clean?
|
32
41
|
raise ParkedChangesError.new(self) if is_parked?
|
33
42
|
|
34
|
-
current_branch
|
35
|
-
remote_branch
|
43
|
+
@current_branch ||= branches.current
|
44
|
+
@remote_branch ||= "#{server_name}/#{@current_branch}"
|
36
45
|
|
37
46
|
fetch(server_name)
|
38
47
|
|
@@ -42,19 +51,30 @@ module GitProc
|
|
42
51
|
proc_merge(remote_master_branch)
|
43
52
|
end
|
44
53
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
new_sha = command('rev-parse', remote_branch) rescue ''
|
50
|
-
unless old_sha == new_sha
|
51
|
-
logger.warn("'#{current_branch}' changed on '#{server_name}'"+
|
52
|
-
" [#{old_sha[0..5]}->#{new_sha[0..5]}]; trying sync again.")
|
53
|
-
sync_with_server(@do_rebase, @force)
|
54
|
-
end
|
55
|
-
push(server_name, current_branch, current_branch, :force => @force)
|
54
|
+
if @local
|
55
|
+
logger.debug("Not pushing to the server because the user selected local-only.")
|
56
|
+
elsif @current_branch == master_branch
|
57
|
+
logger.warn("Not pushing to the server because the current branch is the mainline branch.")
|
56
58
|
else
|
57
|
-
|
59
|
+
old_sha = rev_parse(@remote_branch) rescue ''
|
60
|
+
|
61
|
+
handle_remote_changed(old_sha)
|
62
|
+
|
63
|
+
push(server_name, @current_branch, @current_branch, :force => @force)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
|
71
|
+
def handle_remote_changed(old_sha)
|
72
|
+
fetch(server_name)
|
73
|
+
new_sha = rev_parse(@remote_branch) rescue ''
|
74
|
+
unless old_sha == new_sha
|
75
|
+
logger.warn("'#{@current_branch}' changed on '#{server_name}'"+
|
76
|
+
" [#{old_sha[0..5]}->#{new_sha[0..5]}]; trying sync again.")
|
77
|
+
runner # try again
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
data/lib/git-process/version.rb
CHANGED
data/spec/new_fb_spec.rb
CHANGED
@@ -56,6 +56,15 @@ describe GitProc::NewFeatureBranch do
|
|
56
56
|
end
|
57
57
|
|
58
58
|
|
59
|
+
it "should use 'integration_branch' instead of 'remote_master_branch'" do
|
60
|
+
change_file_and_commit('a', '')
|
61
|
+
|
62
|
+
new_branch = gitprocess.run
|
63
|
+
|
64
|
+
new_branch.name.should == 'test_branch'
|
65
|
+
end
|
66
|
+
|
67
|
+
|
59
68
|
it "should bring new/uncommitted changes on _parking_ over to the new branch" do
|
60
69
|
gitprocess.branch('origin/master', :base_branch => 'master')
|
61
70
|
gitprocess.checkout('_parking_', :new_branch => 'master')
|
data/spec/sync_spec.rb
CHANGED
@@ -15,51 +15,69 @@ describe GitProc::Sync do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
|
18
|
+
def log_level
|
19
|
+
Logger::ERROR
|
20
|
+
end
|
21
|
+
|
22
|
+
|
18
23
|
def create_process(dir, opts)
|
19
|
-
opts
|
20
|
-
opts[:force] = false
|
21
|
-
GitProc::Sync.new(dir, opts)
|
24
|
+
GitProc::Sync.new(dir, opts.merge({:rebase => false, :force => false}))
|
22
25
|
end
|
23
26
|
|
24
27
|
|
25
|
-
|
28
|
+
it "should work when pushing with fast-forward" do
|
29
|
+
change_file_and_commit('a', '')
|
26
30
|
|
27
|
-
|
28
|
-
|
31
|
+
gitprocess.branch('fb', :base_branch => 'master')
|
32
|
+
|
33
|
+
clone('fb') do |gp|
|
34
|
+
change_file_and_commit('a', 'hello', gp)
|
35
|
+
gp.branches.include?('origin/fb').should be_true
|
36
|
+
gp.runner
|
37
|
+
gp.branches.include?('origin/fb').should be_true
|
38
|
+
gitprocess.branches.include?('fb').should be_true
|
29
39
|
end
|
40
|
+
end
|
30
41
|
|
31
42
|
|
32
|
-
|
33
|
-
|
43
|
+
it "should work with a different remote server name" do
|
44
|
+
change_file_and_commit('a', '')
|
34
45
|
|
35
|
-
|
46
|
+
gitprocess.branch('fb', :base_branch => 'master')
|
36
47
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
48
|
+
clone('fb', 'a_remote') do |gp|
|
49
|
+
change_file_and_commit('a', 'hello', gp)
|
50
|
+
gp.branches.include?('a_remote/fb').should be_true
|
51
|
+
gp.runner
|
52
|
+
gp.branches.include?('a_remote/fb').should be_true
|
53
|
+
gitprocess.branches.include?('fb').should be_true
|
44
54
|
end
|
55
|
+
end
|
45
56
|
|
46
57
|
|
47
|
-
|
48
|
-
|
58
|
+
it "should fail when pushing with non-fast-forward and no force" do
|
59
|
+
change_file_and_commit('a', '')
|
49
60
|
|
50
|
-
|
61
|
+
gitprocess.branch('fb', :base_branch => 'master')
|
51
62
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
gp.run
|
56
|
-
gp.branches.include?('a_remote/fb').should be_true
|
57
|
-
gitprocess.branches.include?('fb').should be_true
|
63
|
+
clone('fb') do |gp|
|
64
|
+
gitprocess.checkout('fb') do
|
65
|
+
change_file_and_commit('a', 'hello', gitprocess)
|
58
66
|
end
|
67
|
+
|
68
|
+
expect {gp.runner}.should raise_error GitProc::GitExecuteError
|
59
69
|
end
|
70
|
+
end
|
71
|
+
|
60
72
|
|
73
|
+
describe "when forcing the push" do
|
74
|
+
|
75
|
+
def create_process(dir, opts)
|
76
|
+
GitProc::Sync.new(dir, opts.merge({:rebase => false, :force => true}))
|
77
|
+
end
|
61
78
|
|
62
|
-
|
79
|
+
|
80
|
+
it "should work when pushing with non-fast-forward" do
|
63
81
|
change_file_and_commit('a', '')
|
64
82
|
|
65
83
|
gitprocess.branch('fb', :base_branch => 'master')
|
@@ -69,81 +87,84 @@ describe GitProc::Sync do
|
|
69
87
|
change_file_and_commit('a', 'hello', gitprocess)
|
70
88
|
end
|
71
89
|
|
72
|
-
expect {gp.
|
90
|
+
expect {gp.runner}.should_not raise_error GitProc::GitExecuteError
|
73
91
|
end
|
74
92
|
end
|
75
93
|
|
76
94
|
end
|
77
95
|
|
78
96
|
|
79
|
-
describe "when
|
97
|
+
describe "when rebasing" do
|
80
98
|
|
81
99
|
def create_process(dir, opts)
|
82
|
-
opts
|
83
|
-
opts[:force] = true
|
84
|
-
GitProc::Sync.new(dir, opts)
|
100
|
+
GitProc::Sync.new(dir, opts.merge({:rebase => true, :force => false}))
|
85
101
|
end
|
86
102
|
|
87
103
|
|
88
|
-
it "should work when pushing
|
104
|
+
it "should work when pushing (non-fast-forward)" do
|
89
105
|
change_file_and_commit('a', '')
|
90
106
|
|
91
107
|
gitprocess.branch('fb', :base_branch => 'master')
|
92
108
|
|
93
109
|
clone('fb') do |gp|
|
94
110
|
gitprocess.checkout('fb') do
|
95
|
-
change_file_and_commit('a', 'hello',
|
111
|
+
change_file_and_commit('a', 'hello', gitprocess)
|
96
112
|
end
|
97
113
|
|
98
|
-
expect {gp.
|
114
|
+
expect {gp.runner}.should_not raise_error GitProc::GitExecuteError
|
99
115
|
end
|
100
116
|
end
|
101
117
|
|
102
118
|
end
|
103
119
|
|
104
120
|
|
105
|
-
describe "
|
106
|
-
|
107
|
-
def log_level
|
108
|
-
Logger::ERROR
|
109
|
-
end
|
110
|
-
|
121
|
+
describe "when forcing local-only" do
|
111
122
|
|
112
123
|
def create_process(dir, opts)
|
113
|
-
opts
|
114
|
-
opts[:force] = true
|
115
|
-
gp = GitProc::Sync.new(dir, opts)
|
116
|
-
gp.instance_variable_set('@server_name', 'a_remote')
|
117
|
-
gp
|
124
|
+
GitProc::Sync.new(dir, opts.merge({:rebase => true, :force => false, :local => true}))
|
118
125
|
end
|
119
126
|
|
120
127
|
|
121
|
-
it "should
|
128
|
+
it "should not try to push" do
|
122
129
|
change_file_and_commit('a', '')
|
123
130
|
|
124
131
|
gitprocess.branch('fb', :base_branch => 'master')
|
125
132
|
|
126
|
-
clone('fb'
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
133
|
+
clone('fb') do |gp|
|
134
|
+
gitprocess.checkout('fb') do
|
135
|
+
change_file_and_commit('a', 'hello', gitprocess)
|
136
|
+
end
|
137
|
+
|
138
|
+
gp.should_receive(:fetch) # want to get remote changes
|
139
|
+
gp.should_not_receive(:push) # ...but not push any
|
140
|
+
|
141
|
+
gp.runner
|
132
142
|
end
|
133
143
|
end
|
134
144
|
|
135
145
|
end
|
136
146
|
|
137
147
|
|
138
|
-
|
148
|
+
it "should work with a different remote server name than 'origin'" do
|
149
|
+
change_file_and_commit('a', '')
|
139
150
|
|
140
|
-
|
141
|
-
gitprocess.checkout('_parking_', :new_branch => 'master')
|
142
|
-
change_file_and_commit('a', '')
|
151
|
+
gitprocess.branch('fb', :base_branch => 'master')
|
143
152
|
|
144
|
-
|
153
|
+
clone('fb', 'a_remote') do |gp|
|
154
|
+
change_file_and_commit('a', 'hello', gp)
|
155
|
+
gp.branches.include?('a_remote/fb').should be_true
|
156
|
+
gp.runner
|
157
|
+
gp.branches.include?('a_remote/fb').should be_true
|
158
|
+
gitprocess.branches.include?('fb').should be_true
|
145
159
|
end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
it 'should fail when removing current feature while on _parking_' do
|
164
|
+
gitprocess.checkout('_parking_', :new_branch => 'master')
|
165
|
+
change_file_and_commit('a', '')
|
146
166
|
|
167
|
+
expect {gitprocess.runner}.should raise_error GitProc::ParkedChangesError
|
147
168
|
end
|
148
169
|
|
149
170
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 9
|
8
|
-
-
|
9
|
-
version: 0.9.
|
8
|
+
- 5
|
9
|
+
version: 0.9.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jim Moore
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-07-
|
17
|
+
date: 2012-07-18 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|