git_story 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 +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/git-story +5 -0
- data/git_story.gemspec +22 -0
- data/lib/git_story/version.rb +3 -0
- data/lib/git_story.rb +21 -0
- data/lib/manual_git_commit_lister.rb +15 -0
- data/lib/puts_renderer.rb +9 -0
- data/lib/split_and_match_processor.rb +31 -0
- data/lib/tracker_fetched_mapper.rb +40 -0
- data/spec/git_story_spec.rb +40 -0
- data/spec/split_and_match_processor_spec.rb +52 -0
- data/spec/tracker_fetched_state_spec.rb +40 -0
- metadata +79 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Nuttanart Pornprasitsakul
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# GitStory
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'git_story'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install git_story
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/git-story
ADDED
data/git_story.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/git_story/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Nuttanart Pornprasitsakul"]
|
6
|
+
gem.email = ["visibletrap@gmail.com"]
|
7
|
+
gem.description = %q{
|
8
|
+
This gem help you check which commits in your git repository belonges to unaccepted PivotalTracker's story
|
9
|
+
so that you can make a decision whether you should deploy those commits or not.
|
10
|
+
}
|
11
|
+
gem.summary = %q{shows unaccepted status of PivotalTracker's story that git commits belong to}
|
12
|
+
gem.homepage = "http://github.com/visibletrap/git_story"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.name = "git_story"
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.version = GitStory::VERSION
|
20
|
+
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
end
|
data/lib/git_story.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "git_story/version"
|
2
|
+
|
3
|
+
require_relative 'manual_git_commit_lister'
|
4
|
+
require_relative 'split_and_match_processor'
|
5
|
+
require_relative 'tracker_fetched_mapper'
|
6
|
+
require_relative 'puts_renderer'
|
7
|
+
|
8
|
+
module GitStory
|
9
|
+
|
10
|
+
def state(since, until_commit)
|
11
|
+
commit_state_factory.list(since, until_commit)
|
12
|
+
end
|
13
|
+
|
14
|
+
def commit_state_factory
|
15
|
+
renderer = PutsRenderer.new
|
16
|
+
state_mapper = TrackerFetchedMapper.new(renderer)
|
17
|
+
commit_processor = SplitAndMatchProcessor.new(state_mapper)
|
18
|
+
ManualGitCommitLister.new(commit_processor)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class ManualGitCommitLister
|
2
|
+
|
3
|
+
def initialize(commit_processor)
|
4
|
+
@commit_processor = commit_processor
|
5
|
+
end
|
6
|
+
|
7
|
+
def list(since, until_commit)
|
8
|
+
@commit_processor.execute(git_list_commit(since, until_commit))
|
9
|
+
end
|
10
|
+
|
11
|
+
def git_list_commit(since, until_commit)
|
12
|
+
`git log --pretty=oneline #{since}..#{until_commit}`
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class SplitAndMatchProcessor
|
2
|
+
|
3
|
+
def initialize(state_mapper)
|
4
|
+
@state_mapper = state_mapper
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute(raw_commit)
|
8
|
+
@state_mapper.execute(process(raw_commit))
|
9
|
+
end
|
10
|
+
|
11
|
+
def process(raw_commit)
|
12
|
+
commit_lines = raw_commit.split("\n")
|
13
|
+
commits_with_story = reject_no_story(commit_lines)
|
14
|
+
story_commit = commits_with_story.reverse.map do |cl|
|
15
|
+
[match_story(cl)[1], match_commit(cl)[0]]
|
16
|
+
end
|
17
|
+
Hash[story_commit]
|
18
|
+
end
|
19
|
+
|
20
|
+
def reject_no_story(commit_lines)
|
21
|
+
commit_lines.reject { |cl| match_story(cl).nil? }
|
22
|
+
end
|
23
|
+
|
24
|
+
def match_story(commit_line)
|
25
|
+
/\[#(\d*)\]/.match(commit_line)
|
26
|
+
end
|
27
|
+
|
28
|
+
def match_commit(commit_line)
|
29
|
+
/^\w*/.match(commit_line)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class TrackerFetchedMapper
|
2
|
+
|
3
|
+
def initialize(renderer)
|
4
|
+
@renderer = renderer
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute(story_commit)
|
8
|
+
@renderer.render(map(story_commit))
|
9
|
+
end
|
10
|
+
|
11
|
+
def map(story_commit)
|
12
|
+
{}.tap do |h|
|
13
|
+
fetch(story_commit.keys).each do |story, state|
|
14
|
+
h[story_commit[story]] = {'story' => story, 'state' => state}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch(stories)
|
20
|
+
require 'rubygems'
|
21
|
+
require 'net/http'
|
22
|
+
require 'uri'
|
23
|
+
require 'nokogiri'
|
24
|
+
|
25
|
+
request = "http://www.pivotaltracker.com/services/v3/projects/#{ENV['TRACKER_PROJECT_ID']}/stories?filter=id:#{stories.join(',')}&includedone:true"
|
26
|
+
resource_uri = URI.parse(request)
|
27
|
+
response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
|
28
|
+
http.get(resource_uri.to_s, {'X-TrackerToken' => ENV['TRACKER_TOKEN']})
|
29
|
+
end
|
30
|
+
|
31
|
+
doc = Nokogiri::XML(response.body)
|
32
|
+
|
33
|
+
{}.tap do |h|
|
34
|
+
doc.xpath('//stories/story').map do |e|
|
35
|
+
h[e.xpath('id').text] = e.xpath('current_state').text.to_sym
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../lib/git_story'
|
2
|
+
|
3
|
+
describe GitStory do
|
4
|
+
|
5
|
+
subject { Object.new.extend(GitStory) }
|
6
|
+
|
7
|
+
let(:since) { "941a3e6b34" }
|
8
|
+
let(:until_commit) { "13351e6" }
|
9
|
+
|
10
|
+
let(:commits) { %w(8670b5ee48d9ea26a91b22f6eeee2234098b3a08 5257d758a95d37441e65d0b5198cbc9dae8cd2cf 70c9112ddf9f02e6680797f490e418d95a3836ed 13351e6dfc28e05cc4c13ea039654b95c62185a0) }
|
11
|
+
let(:stories) { %w(29981485 29521391 29977427 29977409) }
|
12
|
+
let(:states) { %w(accepted rejected finished started).each(&:to_sym) }
|
13
|
+
|
14
|
+
def stub_git
|
15
|
+
raw_commit = <<raw_git_commit
|
16
|
+
#{commits[3]} [##{stories[3]}] Filtered by platform
|
17
|
+
#{commits[2]} [##{stories[2]}] filter series by genre and country_o
|
18
|
+
#{commits[1]} [##{stories[1]}] Episode index and Episode show page
|
19
|
+
#{commits[0]} [##{stories[0]}] Hardcode country filters for music landing page
|
20
|
+
raw_git_commit
|
21
|
+
ManualGitCommitLister.any_instance.stub(:git_list_commit).and_return(raw_commit)
|
22
|
+
end
|
23
|
+
|
24
|
+
def stub_network
|
25
|
+
TrackerFetchedMapper.any_instance.stub(:fetch).with(stories).and_return(Hash[stories.zip(states)])
|
26
|
+
end
|
27
|
+
|
28
|
+
it "run through CommitLister to Renderer" do
|
29
|
+
stub_git
|
30
|
+
stub_network
|
31
|
+
render_input = {}.tap do |h|
|
32
|
+
commits.zip(stories).zip(states).map(&:flatten).each do |css|
|
33
|
+
h[css[0]] = {'story' => css[1], 'state' => css[2]}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
PutsRenderer.any_instance.should_receive(:render).with(render_input)
|
37
|
+
subject.state(since, until_commit)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative '../lib/split_and_match_processor'
|
2
|
+
|
3
|
+
describe SplitAndMatchProcessor do
|
4
|
+
|
5
|
+
let(:state_mapper) { mock('state_mapper') }
|
6
|
+
|
7
|
+
subject { SplitAndMatchProcessor.new(state_mapper) }
|
8
|
+
|
9
|
+
describe "#execute" do
|
10
|
+
it "process raw commit and pass to state mapper" do
|
11
|
+
raw_commit = 'raw_commit'
|
12
|
+
story_commit = mock('story_commit_hash')
|
13
|
+
subject.stub(:process).with(raw_commit).and_return(story_commit)
|
14
|
+
|
15
|
+
state_mapper.should_receive(:execute).with(story_commit)
|
16
|
+
|
17
|
+
subject.execute(raw_commit)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#process" do
|
22
|
+
it "processes raw commit string to a hash { story => commit }" do
|
23
|
+
raw_commit = <<raw_git_commit
|
24
|
+
13351e6dfc28e05cc4c13ea039654b95c62185a0 [#29977409] Filtered by platform
|
25
|
+
70c9112ddf9f02e6680797f490e418d95a3836ed [#29977427] filter series by genre and country_o
|
26
|
+
5257d758a95d37441e65d0b5198cbc9dae8cd2cf [#29521391] Episode index and Episode show page
|
27
|
+
8670b5ee48d9ea26a91b22f6eeee2234098b3a08 [#29981485] Hardcode country filters for music landing page
|
28
|
+
raw_git_commit
|
29
|
+
|
30
|
+
subject.process(raw_commit).should ==
|
31
|
+
{
|
32
|
+
'29981485' => '8670b5ee48d9ea26a91b22f6eeee2234098b3a08',
|
33
|
+
'29521391' => '5257d758a95d37441e65d0b5198cbc9dae8cd2cf',
|
34
|
+
'29977427' => '70c9112ddf9f02e6680797f490e418d95a3836ed',
|
35
|
+
'29977409' => '13351e6dfc28e05cc4c13ea039654b95c62185a0'
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
it "ignores commit without story id" do
|
40
|
+
raw_commit = <<raw_git_commit
|
41
|
+
13351e6dfc28e05cc4c13ea039654b95c62185a0 [#29977409] Filtered by platform
|
42
|
+
70c9112ddf9f02e6680797f490e418d95a3836ed filter series by genre and country_o
|
43
|
+
raw_git_commit
|
44
|
+
|
45
|
+
subject.process(raw_commit).should ==
|
46
|
+
{
|
47
|
+
'29977409' => '13351e6dfc28e05cc4c13ea039654b95c62185a0'
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../lib/tracker_fetched_mapper'
|
2
|
+
|
3
|
+
describe TrackerFetchedMapper do
|
4
|
+
|
5
|
+
let(:renderer) { mock('renderer') }
|
6
|
+
|
7
|
+
subject { TrackerFetchedMapper.new(renderer) }
|
8
|
+
|
9
|
+
it { should respond_to :fetch }
|
10
|
+
|
11
|
+
describe "#execute" do
|
12
|
+
it "calls #map and passes result to Renderer" do
|
13
|
+
commit_story = mock('commit_story')
|
14
|
+
commit_story_status = mock('commit_story_status')
|
15
|
+
|
16
|
+
subject.should_receive(:map).with(commit_story).and_return(commit_story_status)
|
17
|
+
renderer.should_receive(:render).with(commit_story_status)
|
18
|
+
|
19
|
+
subject.execute(commit_story)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#map' do
|
24
|
+
it "maps { story_id => commit } to { commit => { story => story_id, state => state_symbol } }" do
|
25
|
+
subject.should_receive(:fetch).with(['123', '234']).and_return({ '123' => :accepted, '234' => :rejected })
|
26
|
+
subject.map({ '123' => 'x', '234' => 'y' }).should ==
|
27
|
+
{
|
28
|
+
'x' => { 'story' => '123', 'state' => :accepted },
|
29
|
+
'y' => { 'story' => '234', 'state' => :rejected }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "ignores commit that corresponding story is not returned from #fetch" do
|
34
|
+
subject.should_receive(:fetch).with(['123', '234']).and_return({ '234' => :rejected })
|
35
|
+
subject.map({ '123' => 'x', '234' => 'y' }).should ==
|
36
|
+
{ 'y' => {'story' => '234', 'state' => :rejected } }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git_story
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nuttanart Pornprasitsakul
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-27 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70239544245560 !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: *70239544245560
|
25
|
+
description: ! "\n This gem help you check which commits in your git repository
|
26
|
+
belonges to unaccepted PivotalTracker's story\n so that you can make a decision
|
27
|
+
whether you should deploy those commits or not.\n "
|
28
|
+
email:
|
29
|
+
- visibletrap@gmail.com
|
30
|
+
executables:
|
31
|
+
- git-story
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- .gitignore
|
36
|
+
- Gemfile
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- bin/git-story
|
41
|
+
- git_story.gemspec
|
42
|
+
- lib/git_story.rb
|
43
|
+
- lib/git_story/version.rb
|
44
|
+
- lib/manual_git_commit_lister.rb
|
45
|
+
- lib/puts_renderer.rb
|
46
|
+
- lib/split_and_match_processor.rb
|
47
|
+
- lib/tracker_fetched_mapper.rb
|
48
|
+
- spec/git_story_spec.rb
|
49
|
+
- spec/split_and_match_processor_spec.rb
|
50
|
+
- spec/tracker_fetched_state_spec.rb
|
51
|
+
homepage: http://github.com/visibletrap/git_story
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.10
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: shows unaccepted status of PivotalTracker's story that git commits belong
|
75
|
+
to
|
76
|
+
test_files:
|
77
|
+
- spec/git_story_spec.rb
|
78
|
+
- spec/split_and_match_processor_spec.rb
|
79
|
+
- spec/tracker_fetched_state_spec.rb
|