git_tracking 0.1.0

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