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 ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in git_story.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/git-story ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'git_story'
4
+
5
+ Object.new.extend(GitStory).state(ARGV[0], ARGV[1])
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
@@ -0,0 +1,3 @@
1
+ module GitStory
2
+ VERSION = "0.1.0"
3
+ 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,9 @@
1
+ class PutsRenderer
2
+ def render(commit_story_state)
3
+ commit_story_state.each do |commit, story_state|
4
+ unless story_state['state'] == :accepted
5
+ puts "#{commit} ##{story_state['story']} #{story_state['state']}"
6
+ end
7
+ end
8
+ end
9
+ 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