git_tracking 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/.git_tracking ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ :raise_on_incomplete_merge: false
3
+ :keys: {}
4
+
5
+ :emails: []
6
+
7
+ :raise_on_debugger: false
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --debug
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in git_tracking.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rspec', '>=2.0'
8
+ gem 'ruby-debug', :platforms => :ruby_18
9
+ gem 'ruby-debug19', :platforms => :ruby_19
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git_tracking (0.0.1)
5
+ highline
6
+ pivotal-tracker
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ archive-tar-minitar (0.5.2)
12
+ builder (2.1.2)
13
+ columnize (0.3.1)
14
+ diff-lcs (1.1.2)
15
+ happymapper (0.3.2)
16
+ libxml-ruby (~> 1.1.3)
17
+ highline (1.6.1)
18
+ libxml-ruby (1.1.4)
19
+ linecache (0.43)
20
+ linecache19 (0.5.11)
21
+ ruby_core_source (>= 0.1.4)
22
+ mime-types (1.16)
23
+ nokogiri (1.4.3.1)
24
+ pivotal-tracker (0.2.2)
25
+ builder
26
+ happymapper (>= 0.3.2)
27
+ nokogiri (~> 1.4.3.1)
28
+ rest-client (~> 1.6.0)
29
+ rest-client (1.6.1)
30
+ mime-types (>= 1.16)
31
+ rspec (2.0.1)
32
+ rspec-core (~> 2.0.1)
33
+ rspec-expectations (~> 2.0.1)
34
+ rspec-mocks (~> 2.0.1)
35
+ rspec-core (2.0.1)
36
+ rspec-expectations (2.0.1)
37
+ diff-lcs (>= 1.1.2)
38
+ rspec-mocks (2.0.1)
39
+ rspec-core (~> 2.0.1)
40
+ rspec-expectations (~> 2.0.1)
41
+ ruby-debug (0.10.3)
42
+ columnize (>= 0.1)
43
+ ruby-debug-base (~> 0.10.3.0)
44
+ ruby-debug-base (0.10.3)
45
+ linecache (>= 0.3)
46
+ ruby-debug-base19 (0.11.24)
47
+ columnize (>= 0.3.1)
48
+ linecache19 (>= 0.5.11)
49
+ ruby_core_source (>= 0.1.4)
50
+ ruby-debug19 (0.11.6)
51
+ columnize (>= 0.3.1)
52
+ linecache19 (>= 0.5.11)
53
+ ruby-debug-base19 (>= 0.11.19)
54
+ ruby_core_source (0.1.4)
55
+ archive-tar-minitar (>= 0.5.2)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ git_tracking!
62
+ highline
63
+ pivotal-tracker
64
+ rspec (>= 2.0)
65
+ ruby-debug
66
+ ruby-debug19
data/README ADDED
@@ -0,0 +1,70 @@
1
+ Git Tracking!
2
+ ============
3
+
4
+ ## Purpose
5
+ `git_tracking` is a gem whose primary purpose is to
6
+ provide (and enforce) tight integration between
7
+ Pivotal Tracker and git for your project.
8
+
9
+ At my office, we have all agreed that we shouldn't
10
+ commit code that doesn't have an associated story
11
+ in Pivotal Tracker. Further, we tend to work on
12
+ code at our glamorous (and blazingly fast) iMac
13
+ pairing stations. Therefore, we have two needs which
14
+ this gem aims to satisfy:
15
+
16
+ 1. To always know *who* was the author of a given
17
+ commit. ('Pairingstation1' is not good enough.)
18
+ 2. To always know *which* story a given commit was
19
+ intended to fix/implement.
20
+ 3. Bonus: While in Tracker, to be able to see
21
+ which commits are tied to a given story.
22
+
23
+ ## Installation
24
+
25
+ gem install git_tracking
26
+ git_tracking # this installs the git hooks and .git_tracking config file
27
+
28
+ ## Usage
29
+ Just use git as normal, and you will be prompted for
30
+ stuff.
31
+
32
+ For example:
33
+
34
+ ! g commit -m "Making detect ignore .git_tracking file"
35
+ The following files have 'debugger' statements in them:
36
+ spec/detect_spec.rb
37
+ Git Author (default will be: Steve & Ghost Co-Pilot):
38
+ 1. Enter new
39
+ ? 1
40
+ New git author: Steve
41
+ Pivotal Tracker email (default is: ):
42
+ 1. Enter new
43
+ ? 1
44
+ New Email: john@doe.net
45
+ Enter your PivotalTracker password: xxxxxxx
46
+ Please enter the PivotalTracker project id for this project
47
+ 137119
48
+ Found a valid story id in your branch or commit: 6131989 - Bugfixing
49
+ Hit enter to confirm story id 6131989, or enter some other story id: |6131989| 6132035
50
+ [master 2162139] [#6132035] Make sure that detect_* ignores .git_tracking file
51
+ 2 files changed, 17 insertions(+), 8 deletions(-)
52
+
53
+ You can set `git_tracking` to raise (ie reject your
54
+ commit) when it detects that you are about to commit
55
+ a `debugger` or an incomplete merge (`<<<<<<<` or `>>>>>>>`).
56
+
57
+ All config options are in the `.git_tracking` file.
58
+
59
+ ## BEWARE!!
60
+
61
+ `git_tracking` will store your api token for tracker
62
+ in the `.git_tracking` file. This is probably fine
63
+ for private repo's where you know everyone who
64
+ has access, but if you are committing to a public
65
+ repo, you may want to put the `.git_tracking` file
66
+ in your .gitignore.
67
+
68
+ If you do accidentally make your PivotalTracker
69
+ token public, I believe you request that they
70
+ generate a new one for you.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/bin/git_tracking ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'git_tracking'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'git_tracking'
8
+ end
9
+
10
+ puts "Thanks for installing the git_tracking gem!"
11
+
12
+ if File.exists?(".git") && File.directory?(".git")
13
+ GitTracking.config
14
+
15
+ unless File.exists?(".git_tracking")
16
+ GitTracking::Config.new.write_to_file
17
+ else
18
+ puts "Not writing .git_tracking config file, as it already exists"
19
+ end
20
+
21
+ unless File.exists?(".git/hooks/pre-commit") && File.read(".git/hooks/pre-commit").include?("GitTracking")
22
+ File.open(".git/hooks/pre-commit", "a") do |file|
23
+ file.print File.read(File.expand_path("../../hooks/pre-commit", __FILE__))
24
+ end
25
+ system "chmod +x .git/hooks/pre-commit"
26
+ else
27
+ puts "Not writing pre-commit hook, as it already exists"
28
+ end
29
+
30
+ unless File.exists?(".git/hooks/prepare-commit-msg") && File.read(".git/hooks/prepare-commit-msg").include?("GitTracking")
31
+ File.open(".git/hooks/prepare-commit-msg", "a") do |file|
32
+ file.print File.read(File.expand_path("../../hooks/prepare-commit-msg", __FILE__))
33
+ end
34
+ system "chmod +x .git/hooks/prepare-commit-msg"
35
+ else
36
+ puts "Not writing prepare-commit-msg hook, as it already exists"
37
+ end
38
+
39
+ unless File.exists?(".git/hooks/post-commit") && File.read(".git/hooks/post-commit").include?("GitTracking")
40
+ File.open(".git/hooks/post-commit", "a") do |file|
41
+ file.print File.read(File.expand_path("../../hooks/post-commit", __FILE__))
42
+ end
43
+ system "chmod +x .git/hooks/post-commit"
44
+ else
45
+ puts "Not writing post-commit hook, as it already exists"
46
+ end
47
+ else
48
+ puts "Error: No .git directory found. The git_tracking gem can only be used in a project tracked by git"
49
+ exit 1
50
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "git_tracking/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "git_tracking"
7
+ s.version = GitTracking::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Steve Hull", "Derrick Camerino"]
10
+ s.email = ["p.witty@gmail.com", "robustdj@gmail.com"]
11
+ s.homepage = "http://rubygems.org/gems/git_tracking"
12
+ s.summary = %q{Better integration between git and PivotalTracker}
13
+ s.description = %q{Usage: after installing the gem, in your project directory, run: git_tracking}
14
+
15
+ s.rubyforge_project = "git_tracking"
16
+
17
+ s.add_dependency('highline')
18
+ s.add_dependency('pivotal-tracker')
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
data/hooks/post-commit ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'git_tracking'
4
+ rescue LoadError
5
+ require 'rubygems'
6
+ require 'git_tracking'
7
+ end
8
+
9
+ GitTracking.post_commit
data/hooks/pre-commit ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'git_tracking'
4
+ rescue LoadError
5
+ require 'rubygems'
6
+ require 'git_tracking'
7
+ end
8
+
9
+ GitTracking.pre_commit
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'git_tracking'
4
+ rescue LoadError
5
+ require 'rubygems'
6
+ require 'git_tracking'
7
+ end
8
+
9
+ GitTracking.prepare_commit_msg
@@ -0,0 +1,81 @@
1
+ class GitTracking
2
+ class Config
3
+ def initialize
4
+ @config = {
5
+ :raise_on_incomplete_merge => true,
6
+ :raise_on_debugger => true,
7
+ :authors => [],
8
+ :keys => {}
9
+ }
10
+ if File.exists? ".git_tracking"
11
+ @config.merge! YAML.load_file(".git_tracking")
12
+ end
13
+ end
14
+
15
+ def raise_on_debugger
16
+ @config[:raise_on_debugger]
17
+ end
18
+
19
+ def raise_on_incomplete_merge
20
+ @config[:raise_on_incomplete_merge]
21
+ end
22
+
23
+ def emails
24
+ @config[:keys].keys
25
+ end
26
+
27
+ def last_email
28
+ @config[:keys].invert[last_api_key]
29
+ end
30
+
31
+ def last_commit_info
32
+ info = `git log -n 1`.split("\n")
33
+ info = "#{info.first}\n#{info.last}"
34
+ end
35
+
36
+ def author
37
+ `git config user.name`.chomp
38
+ end
39
+
40
+ def authors
41
+ @config[:authors]
42
+ end
43
+
44
+ def author=(new_author)
45
+ @config[:authors].push(new_author).uniq!
46
+ write_to_file
47
+ system "git config user.name '#{new_author}'"
48
+ end
49
+
50
+ [:last_story_id, :last_api_key].each do |config_item|
51
+ git_config_key = config_item.to_s.gsub("_","-")
52
+ define_method(config_item) do
53
+ `git config git-tracking.#{git_config_key}`.chomp
54
+ end
55
+
56
+ define_method("#{config_item}=") do |value|
57
+ system "git config git-tracking.#{git_config_key} '#{value}'"
58
+ end
59
+ end
60
+
61
+ def key_for_email(email, key=nil)
62
+ return (self.last_api_key = @config[:keys][email]) if key.nil?
63
+ self.last_api_key = key
64
+ @config[:keys][email] = key
65
+ write_to_file
66
+ end
67
+
68
+ def project_id(id=nil)
69
+ return @config[:project_id] if id.nil?
70
+ @config[:project_id] = id
71
+ write_to_file
72
+ id
73
+ end
74
+
75
+ def write_to_file
76
+ File.open(".git_tracking", "w") do |file|
77
+ YAML.dump(@config, file)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,26 @@
1
+ class GitTracking
2
+ class << self
3
+ def detect_debuggers
4
+ file_names = `git diff-index --cached -Sdebugger --name-only HEAD`.chomp
5
+ file_names = file_names.gsub(".git_tracking\n",'')
6
+ if file_names != ""
7
+ highline.say highline.color("The following files have 'debugger' statements in them: ", :red)
8
+ highline.say file_names
9
+ raise DebuggerException,
10
+ "Please remove debuggers prior to committing" if config.raise_on_debugger
11
+ end
12
+ end
13
+
14
+ def detect_incomplete_merges
15
+ file_names = `git diff-index --cached -S'<<<<<<<' --name-only HEAD`.chomp.split
16
+ file_names += `git diff-index --cached -S'>>>>>>>' --name-only HEAD`.chomp.split
17
+ file_names = file_names.uniq.join("\n")
18
+ if file_names != ""
19
+ highline.say highline.color("The following files have incomplete merges: ", :red)
20
+ highline.say file_names
21
+ raise IncompleteMergeException,
22
+ "Please complete your merge prior to committing" if config.raise_on_incomplete_merge
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ class GitTracking
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,161 @@
1
+ this_dir = File.expand_path(File.dirname(__FILE__))
2
+ $:.unshift this_dir unless $:.include? this_dir
3
+
4
+ # stdlib
5
+ if RUBY_VERSION.match "1.9"
6
+ require 'fileutils'
7
+ else
8
+ require 'ftools'
9
+ end
10
+ require 'yaml'
11
+
12
+ # gems
13
+ require 'highline'
14
+ require 'pivotal-tracker'
15
+
16
+ # stuff frm this library
17
+ require 'git_tracking/config'
18
+ require 'git_tracking/detect'
19
+
20
+ class PreCommitException < Exception; end
21
+ class DebuggerException < PreCommitException; end
22
+ class IncompleteMergeException < PreCommitException; end
23
+
24
+ HighLine.track_eof = false
25
+
26
+ class GitTracking
27
+ class << self
28
+ def highline
29
+ @highline ||= HighLine.new($stdin.reopen("/dev/tty", "a+"), $stdout)
30
+ end
31
+
32
+ def config
33
+ @config ||= Config.new
34
+ end
35
+
36
+ def commit_message
37
+ @commit_message ||= File.read(ARGV[0])
38
+ end
39
+
40
+ def pre_commit
41
+ detect_debuggers
42
+ detect_incomplete_merges
43
+ end
44
+
45
+ def prepare_commit_msg
46
+ author
47
+ commit_message
48
+ File.open(ARGV[0], "w") do |f|
49
+ f.puts story_info
50
+ f.puts
51
+ f.puts " - #{commit_message}"
52
+ end
53
+ end
54
+
55
+ def post_commit
56
+ @api_key = config.last_api_key
57
+ story = get_story(config.last_story_id)
58
+ story.notes.create(:text => config.last_commit_info)
59
+ end
60
+
61
+ def pivotal_project
62
+ return @pivotal_project if @pivotal_project
63
+ PivotalTracker::Client.token = api_key
64
+ @pivotal_project = PivotalTracker::Project.find(project_id)
65
+ end
66
+
67
+ def project_id
68
+ return config.project_id if config.project_id
69
+ id = highline.ask("Please enter the PivotalTracker project id for this project") do |q|
70
+ q.validate = lambda do |a|
71
+ PivotalTracker::Client.token = api_key
72
+ PivotalTracker::Project.find(a) rescue false
73
+ end
74
+ end
75
+ config.project_id(id)
76
+ end
77
+
78
+ def story_info
79
+ "[##{story.id}] #{story.name}"
80
+ end
81
+
82
+ def story
83
+ return @story if @story
84
+
85
+ if story_id && story = get_story(story_id)
86
+ highline.say("Found a valid story id in your branch or commit: #{story.id} - #{story.name}")
87
+ @story = highline.ask("Hit enter to confirm story id #{story.id}, or enter some other story id: ", lambda{|a| get_story(a)}) do |q|
88
+ q.default = story.id
89
+ q.validate = lambda{|a| check_story_id(a)}
90
+ end
91
+ else
92
+ @story = highline.ask("Please enter a valid Pivotal Tracker story id: ", lambda{|a| get_story(a)}) do |q|
93
+ q.validate = lambda{|a| check_story_id(a)}
94
+ end
95
+ end
96
+ config.last_story_id = @story.id
97
+
98
+ @story
99
+ end
100
+
101
+ def branch
102
+ `git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'`.chomp.gsub("* ", "")
103
+ end
104
+
105
+ def story_id
106
+ @story_id ||= (extract_story_id(commit_message) || extract_story_id(branch) || config.last_story_id)
107
+ end
108
+
109
+ def extract_story_id(string)
110
+ the_story_id = string.match(/\d{5,}/)[0] if string.match(/\d{5,}/)
111
+ return the_story_id if check_story_id(the_story_id)
112
+ end
113
+
114
+ def author
115
+ return @author if @author
116
+ @author = highline.choose(*config.authors) do |menu|
117
+ menu.header = "Git Author (default will be: #{config.author})"
118
+ menu.default = config.author
119
+ menu.choice("Enter new") { highline.ask("New git author: ") }
120
+ end
121
+ config.author = @author
122
+ end
123
+
124
+ def api_key
125
+ return @api_key if @api_key
126
+ message, retry_count = nil, 0
127
+ email = highline.choose(*config.emails) do |menu|
128
+ menu.header = "Pivotal Tracker email (default is: #{config.last_email})"
129
+ menu.default = config.last_email
130
+ menu.choice("Enter new") { highline.ask("New Email: ") }
131
+ end
132
+ unless @api_key = config.key_for_email(email.to_s)
133
+ begin
134
+ highline.say message if message
135
+ password = highline.ask("Enter your PivotalTracker password: ") {|q| q.echo = "x" }
136
+ @api_key = PivotalTracker::Client.token(email, password)
137
+ config.key_for_email(email, @api_key)
138
+ rescue RestClient::Request::Unauthorized
139
+ retry_count += 1
140
+ message = "401 Unauthorized. Please try again."
141
+ if retry_count < 3
142
+ retry
143
+ else
144
+ highline.say("Unable to authenticate to Pivotal Tracker. Exiting...")
145
+ raise RestClient::Request::Unauthorized
146
+ end
147
+ end
148
+ end
149
+
150
+ @api_key
151
+ end
152
+
153
+ def check_story_id(id)
154
+ return true if get_story(id)
155
+ end
156
+
157
+ def get_story(id)
158
+ pivotal_project.stories.find(id.to_i) rescue nil
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,199 @@
1
+ require 'lib/git_tracking'
2
+
3
+ describe GitTracking::Config do
4
+ before(:all) do
5
+ File.rename ".git_tracking", ".git_tracking.real" if File.exists?(".git_tracking")
6
+ @orig_author = `git config user.name`.chomp
7
+ @orig_last_api_key = `git config git-tracking.last-api-key`.chomp
8
+ @orig_last_story_id = `git config git-tracking.last-story-id`.chomp
9
+ end
10
+
11
+ after(:all) do
12
+ File.rename ".git_tracking.real", ".git_tracking" if File.exists?(".git_tracking.real")
13
+ system "git config user.name '#{@orig_author}'"
14
+ system "git config git-tracking.last-api-key '#{@orig_last_api_key}'"
15
+ system "git config git-tracking.last-story-id '#{@orig_last_story_id}'"
16
+ end
17
+
18
+ after(:each) { File.delete(".git_tracking") if File.exists?(".git_tracking") }
19
+ let(:config) { GitTracking::Config.new }
20
+
21
+ it "#initialize should merge in config from .git_tracking file" do
22
+ config_hash = config.instance_variable_get("@config")
23
+ config_hash.should == {
24
+ :raise_on_incomplete_merge => true,
25
+ :raise_on_debugger => true,
26
+ :authors => [],
27
+ :keys => {}
28
+ }
29
+ special_options = {
30
+ :raise_on_incomplete_merge => false,
31
+ :raise_on_debugger => false
32
+ }
33
+ File.open(".git_tracking", "w") do |file|
34
+ YAML.dump(special_options, file)
35
+ end
36
+ GitTracking::Config.new.instance_variable_get("@config").should == config_hash.merge(special_options)
37
+ end
38
+
39
+ it "#raise_on_debugger should return the correct config value" do
40
+ config.instance_eval { @config[:raise_on_debugger] = true }
41
+ config.raise_on_debugger.should be_true
42
+ config.instance_eval { @config[:raise_on_debugger] = false }
43
+ config.raise_on_debugger.should be_false
44
+ end
45
+
46
+ it "#raise_on_incomplete_merge should return the correct config value" do
47
+ config.instance_eval { @config[:raise_on_incomplete_merge] = true }
48
+ config.raise_on_incomplete_merge.should be_true
49
+ config.instance_eval { @config[:raise_on_incomplete_merge] = false }
50
+ config.raise_on_incomplete_merge.should be_false
51
+ end
52
+
53
+ it "#emails should return an array of email addresses" do
54
+ config.instance_eval do
55
+ @config[:keys] = {
56
+ "foo@bar.com" => "alsdkjf91",
57
+ "baz@bang.com" => "dsgkj39dk3"
58
+ }
59
+ end
60
+ config.emails.should == ["foo@bar.com", "baz@bang.com"]
61
+ end
62
+
63
+ it "#author should return the user.name value from the git config" do
64
+ system "git config user.name 'Steve'"
65
+ config.author.should == 'Steve'
66
+ end
67
+
68
+ describe "#author=" do
69
+ it "should set the user.name in the git config" do
70
+ (config.author='Ghost').should == "Ghost"
71
+ `git config user.name`.chomp.should == "Ghost"
72
+ end
73
+
74
+ it "should add the author to the list in .git_tracking file" do
75
+ config.instance_eval { @config[:authors] = ["Joe"] }
76
+ config.author = "Steve"
77
+ config.authors.should include("Steve")
78
+ YAML.load_file(".git_tracking")[:authors].should include("Steve")
79
+ end
80
+
81
+ it "should not add the author more than once" do
82
+ config.instance_eval { @config[:authors] = ["Joe"] }
83
+ config.author = "Joe"
84
+ config.authors.should == ["Joe"]
85
+ YAML.load_file(".git_tracking")[:authors].should == ["Joe"]
86
+ end
87
+ end
88
+
89
+ it "#authors should return an array of authors" do
90
+ config.instance_eval { @config[:authors] = ["Joe", "Bob", "Steve"] }
91
+ config.authors.should == ["Joe", "Bob", "Steve"]
92
+ end
93
+
94
+ describe "#last_commit_info" do
95
+ before(:all) do
96
+ File.rename ".git", ".git_old" if File.exists? ".git"
97
+ end
98
+ before(:each) do
99
+ system "git init; git add README; git commit -m 'initial commit'"
100
+ end
101
+ after(:each) do
102
+ File.delete "foo.txt" if File.exists? "foo.txt"
103
+ system "rm -rf .git" if File.exists? ".git"
104
+ end
105
+ after(:all) do
106
+ File.rename ".git_old", ".git" if File.exists? ".git_old"
107
+ end
108
+
109
+ it "should return info about the last commit" do
110
+ f = File.new("foo.txt", "w") {|f| f.puts "lalala"}
111
+ system "git add foo.txt"
112
+ system "git commit -m '[#1235] Story info\n - best commit evar'"
113
+ config.last_commit_info.should match(/commit \w{40,40}\n - best commit evar/)
114
+ end
115
+ end
116
+
117
+ it "#last_story_id should return the git-tracking.last-story-id from git config" do
118
+ system "git config git-tracking.last-story-id '736741'"
119
+ config.last_story_id.should == '736741'
120
+ end
121
+
122
+ it "#last_story_id= should set the git-tracking.last-story-id in git config" do
123
+ (config.last_story_id='234900').should == '234900'
124
+ `git config git-tracking.last-story-id`.chomp.should == '234900'
125
+ end
126
+
127
+ it "#last_api_key should return the git-tracking.last-api-key from git config" do
128
+ system "git config git-tracking.last-api-key '736741'"
129
+ config.last_api_key.should == '736741'
130
+ end
131
+
132
+ it "#last_api_key= should set the git-tracking.last-api-key in git config" do
133
+ (config.last_api_key='123444').should == '123444'
134
+ `git config git-tracking.last-api-key`.chomp.should == '123444'
135
+ end
136
+
137
+ it "#last_email should return the email that corresponds to the last api key used" do
138
+ config.instance_eval do
139
+ @config[:keys] = {
140
+ "foo@bar.com" => "987125jf"
141
+ }
142
+ end
143
+ system "git config git-tracking.last-api-key '987125jf'"
144
+ config.last_email.should == "foo@bar.com"
145
+ end
146
+
147
+ describe "#project_id" do
148
+ it "should return the project_id" do
149
+ config.instance_eval { @config[:project_id] = '7472' }
150
+ config.project_id.should == '7472'
151
+ end
152
+
153
+ it "should set the project id and store it" do
154
+ config.project_id('8765').should == '8765'
155
+ YAML.load_file(".git_tracking")[:project_id].should == '8765'
156
+ end
157
+ end
158
+
159
+ describe "#key_for_email" do
160
+ it "should return the pivotal api key" do
161
+ config.instance_eval do
162
+ @config[:keys] = {
163
+ "foo@bar.com" => "alsdkjf91",
164
+ "baz@bang.com" => "dsgkj39dk3"
165
+ }
166
+ end
167
+ config.key_for_email("foo@bar.com").should == 'alsdkjf91'
168
+ end
169
+
170
+ it "should set the pivotal api key and store it" do
171
+ config.key_for_email("foo@bar.com", 'kdslghj348')
172
+ YAML.load_file(".git_tracking")[:keys]["foo@bar.com"].should == 'kdslghj348'
173
+ end
174
+
175
+ it "should set the last_api_key as well" do
176
+ config.should_receive(:last_api_key=).with('kdslghj348').ordered
177
+ config.should_receive(:last_api_key=).with('dsgkj39dk3').ordered
178
+ config.key_for_email("foo@bar.com", 'kdslghj348')
179
+ config.instance_eval do
180
+ @config[:keys] = {
181
+ "baz@bang.com" => "dsgkj39dk3"
182
+ }
183
+ end
184
+ config.key_for_email("baz@bang.com")
185
+ end
186
+ end
187
+
188
+ it "#write_to_file should write the @config var to the file" do
189
+ special_options = {
190
+ :raise_on_incomplete_merge => false,
191
+ :raise_on_debugger => false,
192
+ :emails => ["your@mom.com"]
193
+ }
194
+ config.instance_variable_set("@config", special_options)
195
+ config.write_to_file
196
+ YAML.load_file(".git_tracking") == special_options
197
+ end
198
+ end
199
+
@@ -0,0 +1,95 @@
1
+ require 'lib/git_tracking'
2
+
3
+ describe GitTracking, "detect" do
4
+ before(:all) do
5
+ File.rename ".git_tracking", ".git_tracking.real" if File.exists?(".git_tracking")
6
+ File.rename ".git", ".git_old" if File.exists? ".git"
7
+ end
8
+
9
+ before(:each) do
10
+ do_cmd "git init; git add README; git commit -m 'initial commit'"
11
+ end
12
+
13
+ after(:each) do
14
+ File.delete "foo.txt" if File.exists? "foo.txt"
15
+ File.delete ".git_tracking" if File.exists? ".git_tracking"
16
+ do_cmd "rm -rf .git" if File.exists? ".git"
17
+ end
18
+
19
+ after(:all) do
20
+ File.rename ".git_old", ".git" if File.exists? ".git_old"
21
+ File.rename ".git_tracking.real", ".git_tracking" if File.exists?(".git_tracking.real")
22
+ end
23
+
24
+ describe ".detect_debuggers" do
25
+ context "configured to reject commits with debuggers" do
26
+ it "should detect debuggers and raise DebuggerException" do
27
+ GitTracking.config.stub(:raise_on_debugger).and_return(true)
28
+ make_file "foo.txt", "debugger"
29
+ make_file ".git_tracking", "debugger"
30
+ do_cmd "git add foo.txt"
31
+ do_cmd "git add .git_tracking"
32
+ GitTracking.highline.should_receive("say")
33
+ GitTracking.highline.should_receive("say").with("foo.txt")
34
+ lambda{GitTracking.detect_debuggers}.should(
35
+ raise_error(DebuggerException, "Please remove debuggers prior to committing"))
36
+ end
37
+ end
38
+
39
+ context "configured to simply warn about commits with debuggers" do
40
+ it "should detect debuggers" do
41
+ GitTracking.config.stub(:raise_on_debugger).and_return(false)
42
+ make_file "foo.txt", "debugger"
43
+ make_file ".git_tracking", "debugger"
44
+ do_cmd "git add foo.txt"
45
+ do_cmd "git add .git_tracking"
46
+ GitTracking.highline.should_receive("say")
47
+ GitTracking.highline.should_receive("say").with("foo.txt")
48
+ lambda{GitTracking.detect_debuggers}.should_not raise_error
49
+ end
50
+ end
51
+ end
52
+
53
+ describe ".detect_incomplete_merges" do
54
+ context "configured to reject commits with incomplete merges" do
55
+ it "should detect incomplete merges and raise IncompleteMergeException" do
56
+ GitTracking.config.stub(:raise_on_incomplete_merge).and_return(true)
57
+ make_file "foo.txt", "<<<<<<<", "your changes", "=======", "my changes", ">>>>>>>"
58
+ do_cmd "git add foo.txt"
59
+ GitTracking.highline.should_receive("say")
60
+ GitTracking.highline.should_receive("say").with("foo.txt")
61
+ lambda{GitTracking.detect_incomplete_merges}.should(
62
+ raise_error(IncompleteMergeException, "Please complete your merge prior to committing"))
63
+ end
64
+ end
65
+
66
+ context "configured to simply warn about commits with incomplete merges" do
67
+ it "should detect incomplete merges and raise IncompleteMergeException" do
68
+ GitTracking.config.stub(:raise_on_incomplete_merge).and_return(false)
69
+ make_file "foo.txt", "<<<<<<<", "your changes", "=======", "my changes", ">>>>>>>"
70
+ do_cmd "git add foo.txt"
71
+ GitTracking.highline.should_receive("say")
72
+ GitTracking.highline.should_receive("say").with("foo.txt")
73
+ lambda{GitTracking.detect_incomplete_merges}.should_not(
74
+ raise_error(IncompleteMergeException, "Please complete your merge prior to committing"))
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def make_file(name, *content)
81
+ f = File.new(name, "w")
82
+ f.puts *content
83
+ f.close
84
+ end
85
+
86
+ def do_cmd(command)
87
+ orig_stdout = $stdout
88
+
89
+ # redirect stdout to /dev/null
90
+ $stdout = File.new('/dev/null', 'w')
91
+ system command
92
+ ensure
93
+ # restore stdout
94
+ $stdout = orig_stdout
95
+ end
@@ -0,0 +1,254 @@
1
+ require 'git_tracking'
2
+ require 'ruby-debug'
3
+
4
+ describe GitTracking do
5
+ before(:all) do
6
+ @original_git_author = `git config --global user.name`.chomp
7
+ end
8
+
9
+ after(:all) do
10
+ system "git config --global user.name '#{@original_git_author}'"
11
+ File.delete("foo.txt") if File.exists?("foo.txt")
12
+ end
13
+
14
+ it ".pre_commit should call detect_debuggers and detect_incomplete_merges" do
15
+ GitTracking.should_receive(:detect_debuggers)
16
+ GitTracking.should_receive(:detect_incomplete_merges)
17
+ GitTracking.pre_commit
18
+ end
19
+
20
+ describe ".prepare_commit_msg" do
21
+ it "should get the message" do
22
+ old_argv = ARGV
23
+ File.open("foo.txt", "w") do |f|
24
+ f.print "My awesome commit msg!"
25
+ end
26
+ ARGV = ["foo.txt"]
27
+ GitTracking.stub(:story_info).and_return "[#12345] Best feature evar"
28
+ GitTracking.stub(:author).and_return "Steve & Ghost Co-Pilot"
29
+ GitTracking.prepare_commit_msg
30
+ commit_msg = File.open("foo.txt", "r").read
31
+ commit_msg.should == <<STRING
32
+ [#12345] Best feature evar
33
+
34
+ - My awesome commit msg!
35
+ STRING
36
+ ARGV = old_argv
37
+ end
38
+
39
+ it "should call story_info and author" do
40
+ ARGV = ["foo.txt"]
41
+ GitTracking.should_receive(:story_info).and_return "[#12345] Best feature evar"
42
+ GitTracking.should_receive(:author)
43
+ GitTracking.prepare_commit_msg
44
+ end
45
+ end
46
+
47
+ it ".post_commit should create a comment on the story with the commit msg and hash" do
48
+ story = mock("story")
49
+ notes = mock("notes")
50
+ GitTracking.stub(:get_story).and_return(story)
51
+ GitTracking.should_not_receive(:story_id)
52
+ story.should_receive(:notes).and_return(notes)
53
+ GitTracking.config.should_receive(:last_commit_info).and_return("984752 [#27491] Best commit evar")
54
+ notes.should_receive(:create).with(:text => "984752 [#27491] Best commit evar")
55
+ GitTracking.post_commit
56
+ end
57
+
58
+ describe ".story" do
59
+ before do
60
+ GitTracking.class_eval{@story = nil}
61
+ end
62
+
63
+ it "should require a story" do
64
+ the_story = mock('story', :name => 'Best feature evar', :id => 12345)
65
+ GitTracking.stub(:story_id).and_return("")
66
+ GitTracking.stub(:get_story).and_return(nil)
67
+ GitTracking.highline.should_receive(:ask).with("Please enter a valid Pivotal Tracker story id: ", an_instance_of(Proc)).and_return(the_story)
68
+ GitTracking.config.should_receive(:last_story_id=).with(12345)
69
+ GitTracking.story.should == the_story
70
+ end
71
+
72
+ it "should not prompt once a story has been confirmed" do
73
+ the_story = mock('story', :name => 'Best feature evar', :id => 12345)
74
+ GitTracking.class_eval {@story = the_story}
75
+ GitTracking.should_not_receive(:highline)
76
+ GitTracking.story.should == the_story
77
+ end
78
+
79
+ it "should allow you to enter an alternate story when it finds a story_id" do
80
+ the_story = mock('story', :name => 'Best feature evar', :id => 85918)
81
+ GitTracking.stub(:story_id).and_return(the_story.id)
82
+ GitTracking.stub(:get_story).and_return(the_story)
83
+ GitTracking.config.should_receive(:last_story_id=).with(85918)
84
+ GitTracking.highline.should_receive(:say).
85
+ with("Found a valid story id in your branch or commit: 85918 - Best feature evar")
86
+ GitTracking.highline.should_receive(:ask).
87
+ with("Hit enter to confirm story id 85918, or enter some other story id: ", an_instance_of(Proc)).
88
+ and_return(the_story)
89
+ GitTracking.story.should == the_story
90
+ end
91
+ end
92
+
93
+ describe ".story_id" do
94
+ before(:each) do
95
+ GitTracking.stub(:check_story_id).and_return(true)
96
+ GitTracking.instance_eval{@story_id = nil}
97
+ end
98
+
99
+ it "should check the commit message for a story id" do
100
+ GitTracking.stub!(:commit_message).and_return("54261 - Fixing Javascript")
101
+ GitTracking.story_id.should == '54261'
102
+ end
103
+
104
+ it "should check the branch name for a story id" do
105
+ GitTracking.stub!(:commit_message).and_return("Fixing Javascript")
106
+ GitTracking.stub!(:branch).and_return("64371-js-bug")
107
+ GitTracking.story_id.should == '64371'
108
+ end
109
+
110
+ it "should check the .git/config file for the last story id" do
111
+ GitTracking.stub!(:commit_message).and_return("Fixing Javascript")
112
+ GitTracking.config.should_receive(:last_story_id).and_return('35236')
113
+ GitTracking.story_id.should == '35236'
114
+ end
115
+
116
+ it "should verify the story_id with the pivotal tracker API" do
117
+ GitTracking.stub!(:commit_message).and_return("Generating 55654 monkeys")
118
+ GitTracking.stub!(:branch).and_return("64371-js-bug")
119
+ GitTracking.should_receive(:check_story_id).with('55654').and_return(false)
120
+ GitTracking.should_receive(:check_story_id).with('64371').and_return(true)
121
+ GitTracking.story_id.should == "64371"
122
+ end
123
+ end
124
+
125
+ describe ".extract_story_id" do
126
+ it "should extract any number that is 5 digits or longer and return it" do
127
+ GitTracking.stub(:check_story_id).and_return(true)
128
+ GitTracking.extract_story_id("45674 - The best feature evar").should == "45674"
129
+ GitTracking.extract_story_id("90873 - The best feature evar").should == "90873"
130
+ end
131
+
132
+ it "should return nil if there is no number that is 5 digits or longer" do
133
+ GitTracking.stub(:check_story_id).and_return(true)
134
+ GitTracking.extract_story_id("The best feature evar").should be_nil
135
+ end
136
+
137
+ it "should return nil if it's not a valid Pivotal Tracker story id" do
138
+ GitTracking.stub(:check_story_id).and_return(false)
139
+ GitTracking.extract_story_id("45674 - The best feature evar").should be_nil
140
+ end
141
+ end
142
+
143
+ describe ".pivotal_project" do
144
+ before(:each) { GitTracking.instance_variable_set("@pivotal_project", nil) }
145
+ it "should get the token" do
146
+ project = mock("project")
147
+ PivotalTracker::Project.should_receive(:find).and_return(project)
148
+ GitTracking.should_receive(:api_key).and_return("alksjd9123lka")
149
+ GitTracking.pivotal_project.should == project
150
+ end
151
+
152
+ it "should get and use the project_id from config" do
153
+ project = mock("project")
154
+ GitTracking.stub(:api_key)
155
+ GitTracking.should_receive(:project_id).and_return(1235)
156
+ PivotalTracker::Project.should_receive(:find).with(1235).and_return(project)
157
+ GitTracking.pivotal_project.should == project
158
+ end
159
+ end
160
+
161
+ describe ".project_id" do
162
+ it "should prompt you to enter the project id if there is none defined" do
163
+ GitTracking.config.stub(:project_id).ordered.and_return(nil)
164
+ GitTracking.config.stub(:project_id).with(54876).ordered.and_return(54876)
165
+ GitTracking.highline.should_receive(:ask).with("Please enter the PivotalTracker project id for this project").and_return(54876)
166
+ GitTracking.project_id.should == 54876
167
+ end
168
+
169
+ it "should get the project id from the config" do
170
+ GitTracking.config.should_receive(:project_id).twice.and_return(9712)
171
+ GitTracking.project_id.should == 9712
172
+ end
173
+ end
174
+
175
+ describe ".check_story_id" do
176
+ before(:each) do
177
+ GitTracking.stub(:api_key).and_return(5678)
178
+ end
179
+
180
+ it "should return true for story id that can be found in tracker" do
181
+ PivotalTracker::Project.stub(:find).and_return(mock("project"))
182
+ GitTracking.pivotal_project.should_receive(:stories).and_return(mock("stories", :find => mock("story")))
183
+ GitTracking.check_story_id(5678).should be_true
184
+ end
185
+
186
+ it "should return false for a valid story id" do
187
+ PivotalTracker::Project.stub(:find).and_return(mock("project"))
188
+ GitTracking.pivotal_project.should_receive(:stories).and_return(mock("stories", :find => nil))
189
+ GitTracking.check_story_id(5678).should be_false
190
+ end
191
+ end
192
+
193
+ describe ".api_key" do
194
+ before(:each) { GitTracking.instance_eval{ @api_key = nil} }
195
+
196
+ it "should prompt for a pivotal login" do
197
+ GitTracking.config.stub(:emails).and_return(["steve@home.com", "john@doe.com"])
198
+ GitTracking.config.should_receive(:key_for_email).with("other@work.net").ordered.and_return(nil)
199
+ GitTracking.config.should_receive(:key_for_email).with("other@work.net", "0987654567").ordered
200
+ GitTracking.highline.should_receive(:choose).with("steve@home.com", "john@doe.com").and_return("other@work.net")
201
+ GitTracking.highline.should_receive(:ask).with("Enter your PivotalTracker password: ").and_return("password")
202
+ PivotalTracker::Client.should_receive(:token).with("other@work.net", "password").and_return("0987654567")
203
+ GitTracking.api_key.should == "0987654567"
204
+ end
205
+
206
+ it "should prompt you to enter an alternate pivotal login" do
207
+ GitTracking.config.stub(:emails).and_return(["steve@home.com", "john@doe.com"])
208
+ GitTracking.config.stub(:key_for_email).and_return("8876567898")
209
+ GitTracking.highline.should_receive(:choose).with("steve@home.com", "john@doe.com").and_return("steve@home.com")
210
+ GitTracking.api_key.should == "8876567898"
211
+ end
212
+
213
+ it "should allow you to re-enter your password if authentication fails" do
214
+ GitTracking.config.stub(:emails).and_return(["steve@home.com", "john@doe.com"])
215
+ GitTracking.config.stub(:key_for_email).and_return(nil)
216
+ GitTracking.highline.should_receive(:choose).and_return("other@work.net")
217
+ GitTracking.highline.should_receive(:ask).exactly(3).times.and_return("password")
218
+ PivotalTracker::Client.should_receive(:token).exactly(3).times.and_raise(RestClient::Request::Unauthorized)
219
+ lambda{GitTracking.api_key}.should raise_error(RestClient::Request::Unauthorized)
220
+ end
221
+ end
222
+
223
+ describe ".get_story" do
224
+ before(:each) do
225
+ GitTracking.stub(:api_key).and_return(5678)
226
+ end
227
+
228
+ it "should return true for story id that can be found in tracker" do
229
+ story = mock("story")
230
+ PivotalTracker::Project.stub(:find).and_return(mock("project"))
231
+ GitTracking.pivotal_project.should_receive(:stories).and_return(mock("stories", :find => story))
232
+ GitTracking.get_story(5678).should == story
233
+ end
234
+
235
+ it "should return false for a valid story id" do
236
+ PivotalTracker::Project.stub(:find).and_return(mock("project"))
237
+ GitTracking.pivotal_project.should_receive(:stories).and_return(mock("stories", :find => nil))
238
+ GitTracking.get_story(5678).should be_nil
239
+ end
240
+ end
241
+
242
+ it ".author should present you with an author menu" do
243
+ GitTracking.instance_eval{ @author = nil }
244
+ GitTracking.config.stub(:authors).and_return(["Ghost", "Steve"])
245
+ GitTracking.highline.should_receive(:choose).with("Ghost", "Steve").and_return("Derrick")
246
+ GitTracking.config.should_receive(:author=).with("Derrick")
247
+ GitTracking.author.should == "Derrick"
248
+ end
249
+
250
+ it ".story_info should format the story info appropriately" do
251
+ GitTracking.should_receive(:story).twice.and_return(mock("story", :name => "Best feature evar", :id => "12345"))
252
+ GitTracking.story_info.should == "[#12345] Best feature evar"
253
+ end
254
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_tracking
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Steve Hull
14
+ - Derrick Camerino
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-08 00:00:00 -08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: highline
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: pivotal-tracker
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: "Usage: after installing the gem, in your project directory, run: git_tracking"
51
+ email:
52
+ - p.witty@gmail.com
53
+ - robustdj@gmail.com
54
+ executables:
55
+ - git_tracking
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - .git_tracking
62
+ - .gitignore
63
+ - .rspec
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - README
67
+ - Rakefile
68
+ - bin/git_tracking
69
+ - git_tracking.gemspec
70
+ - hooks/post-commit
71
+ - hooks/pre-commit
72
+ - hooks/prepare-commit-msg
73
+ - lib/git_tracking.rb
74
+ - lib/git_tracking/config.rb
75
+ - lib/git_tracking/detect.rb
76
+ - lib/git_tracking/version.rb
77
+ - spec/config_spec.rb
78
+ - spec/detect_spec.rb
79
+ - spec/git_tracking_spec.rb
80
+ has_rdoc: true
81
+ homepage: http://rubygems.org/gems/git_tracking
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options: []
86
+
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ requirements: []
108
+
109
+ rubyforge_project: git_tracking
110
+ rubygems_version: 1.3.7
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Better integration between git and PivotalTracker
114
+ test_files:
115
+ - spec/config_spec.rb
116
+ - spec/detect_spec.rb
117
+ - spec/git_tracking_spec.rb