hg-blame-game 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 27d059d640c539503f7c4c313667b765448ccb83
4
+ data.tar.gz: 5065d0e44ca382eb496b20aaa2c113f932218c44
5
+ SHA512:
6
+ metadata.gz: ff9791c8f81b7ea30ffe7737e1d53b389e9756df097f8be450b1a4261421c5fefc599a4f6840de2683932a7d48a71cafbf44ba3910406c3725e6b15f67eae297
7
+ data.tar.gz: c6da93c80c0d8e1e0814cef85e5be5d12835cd01ca0b64f315556cf80d698b2675ffc21e1cadfd6ae80fd883fb6f3f600b440e987ebe9ee6fb5f205c5ee76042
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea
6
+ test/fixtures/sample_hg_repo
7
+ .DS_Store
@@ -0,0 +1 @@
1
+ ruby-2.0.0
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rake'
4
+
5
+ # Specify your gem's dependencies in hg-blame-game.gemspec
6
+ gemspec
@@ -0,0 +1,20 @@
1
+ # hg-blame-game
2
+
3
+ [![Build Status](https://travis-ci.org/charleseff/hg-blame-game.png)](https://travis-ci.org/charleseff/hg-blame-game)
4
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/charleseff/hg-blame-game)
5
+
6
+ hg-blame-game is an interactive command-line tool that allows you to chain multiple 'hg blame' calls together in order to drill down to the actual responsible commit. When one `hg blame` is not enough.
7
+
8
+ ## Installation:
9
+
10
+ gem install hg-blame-game
11
+
12
+ ## Usage:
13
+
14
+ hg-blame-game --help
15
+
16
+ ## Examples:
17
+
18
+ [Happy Path](https://github.com/charleseff/hg-blame-game/blob/master/features/happy_path.feature)
19
+
20
+ [Other Scenarios](https://github.com/charleseff/hg-blame-game/blob/master/features/other_scenarios.feature)
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rubygems'
4
+ require 'cucumber'
5
+ require 'cucumber/rake/task'
6
+ require 'rspec/core/rake_task'
7
+
8
+ Cucumber::Rake::Task.new(:features)
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ task :default => [:spec, :features]
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ - add special highlighting for in the 'hg show' for the line of code (or also the file) that has been chosen to chain into
2
+ - add screencast
3
+ - add ability to go back a step if you made a mistake
4
+ - (maybe) add support for one-file mode (maybe make it default too?)
5
+ - add pull request for aruba?
6
+ - add pull request for colorize (decolorize)
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
3
+ require 'hg-blame-game'
4
+
5
+ aliases = %w(blame praise annotate ann)
6
+
7
+ options = {}
8
+ OptionParser.new do |opts|
9
+ opts.banner = <<-END.gsub(/^[ \t]+/m, '')
10
+ Usage: hg-blame-game [options] path/to/filename
11
+ END
12
+
13
+ opts.separator ""
14
+ opts.separator "Options:"
15
+
16
+ opts.on("-r", "--rev", "--REV [REV]", String, "Set initial revision (defaults to tip)") do |rev|
17
+ options[:changeset_id] = rev
18
+ end
19
+
20
+ opts.on_tail("-h", "--help", "Show this message") do
21
+ puts opts
22
+ puts "\nAliases: " + aliases.map{|a| ["hg-#{a}-game", "hg #{a}-game"]}.flatten.join(", ")
23
+ exit
24
+ end
25
+
26
+ end.parse!
27
+
28
+ path_to_file = ARGV[0]
29
+ raise OptionParser::MissingArgument.new("You must specify a path to a file ") if not path_to_file
30
+
31
+ HgBlameGame.new(path_to_file, options).run
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
3
+ require 'hg-blame-game'
4
+
5
+ aliases = %w(blame praise annotate ann)
6
+
7
+ options = {}
8
+ OptionParser.new do |opts|
9
+ opts.banner = <<-END.gsub(/^[ \t]+/m, '')
10
+ Usage: hg-blame-game [options] path/to/filename
11
+ END
12
+
13
+ opts.separator ""
14
+ opts.separator "Options:"
15
+
16
+ opts.on("-r", "--rev", "--REV [REV]", String, "Set initial revision (defaults to tip)") do |rev|
17
+ options[:changeset_id] = rev
18
+ end
19
+
20
+ opts.on_tail("-h", "--help", "Show this message") do
21
+ puts opts
22
+ puts "\nAliases: " + aliases.map{|a| ["hg-#{a}-game", "hg #{a}-game"]}.flatten.join(", ")
23
+ exit
24
+ end
25
+
26
+ end.parse!
27
+
28
+ path_to_file = ARGV[0]
29
+ raise OptionParser::MissingArgument.new("You must specify a path to a file ") if not path_to_file
30
+
31
+ HgBlameGame.new(path_to_file, options).run
@@ -0,0 +1,147 @@
1
+ Feature: The happy path
2
+
3
+ Scenario: Happy path
4
+ Given I cd to "test/fixtures/sample_hg_repo"
5
+ When I run `../../../bin/hg-blame-game add.rb` interactively
6
+ Then I should see:
7
+ """
8
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:1: module Add
9
+ Danny Dover <developers+danny@foo.com> acb762f5f681:2: def add_4(y)
10
+ Danny Dover <developers+danny@foo.com> acb762f5f681:3: y + 5
11
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:4: end
12
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:5: end
13
+
14
+ (h for help) >
15
+ """
16
+ When I type "3"
17
+ Then I should see:
18
+ """
19
+ # HG changeset patch
20
+ # User Danny Dover <developers+danny@foo.com>
21
+ # Date 1326581406 28800
22
+ # Sat Jan 14 14:50:06 2012 -0800
23
+ # Node ID acb762f5f681d31b710f49f563ba8d1562b9e4c2
24
+ # Parent c8769d6446bc9adf5c0ad0394d87c31594f0cd2b
25
+ I like y's better
26
+
27
+ committer: Charles Finkel <charles.finkel@gmail.com>
28
+
29
+ diff -r c8769d6446bc -r acb762f5f681 add.rb
30
+ --- a/add.rb Sat Jan 14 14:49:00 2012 -0800
31
+ +++ b/add.rb Sat Jan 14 14:50:06 2012 -0800
32
+ @@ -1,5 +1,5 @@
33
+ module Add
34
+ - def add_4(x)
35
+ - x + 5
36
+ + def add_4(y)
37
+ + y + 5
38
+ end
39
+ end
40
+
41
+
42
+ 1) add.rb (or 's' for same)
43
+
44
+ (h for help) >
45
+ """
46
+ When I type "1"
47
+ Then I should see:
48
+ """
49
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:1: module Add
50
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:2: def add_4(x)
51
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:3: x + 5
52
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:4: end
53
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:5: end
54
+
55
+ (h for help) >
56
+ """
57
+ When I type "3"
58
+ Then I should see:
59
+ """
60
+ # HG changeset patch
61
+ # User Carmen Cummings <developers+carmen@foo.com>
62
+ # Date 1326581340 28800
63
+ # Sat Jan 14 14:49:00 2012 -0800
64
+ # Node ID c8769d6446bc9adf5c0ad0394d87c31594f0cd2b
65
+ # Parent c043b110cc46389037e207f814d251f4c7e00a7b
66
+ moving add_4 to module
67
+
68
+ committer: Charles Finkel <charles.finkel@gmail.com>
69
+
70
+ diff -r c043b110cc46 -r c8769d6446bc add.rb
71
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
72
+ +++ b/add.rb Sat Jan 14 14:49:00 2012 -0800
73
+ @@ -0,0 +1,5 @@
74
+ +module Add
75
+ + def add_4(x)
76
+ + x + 5
77
+ + end
78
+ +end
79
+
80
+ diff -r c043b110cc46 -r c8769d6446bc blah.rb
81
+ --- a/blah.rb Sat Jan 14 14:46:53 2012 -0800
82
+ +++ b/blah.rb Sat Jan 14 14:49:00 2012 -0800
83
+ @@ -1,5 +1,5 @@
84
+ -def add_4(x)
85
+ - x + 5
86
+ -end
87
+ +$:.unshift(File.dirname(__FILE__))
88
+ +require 'add'
89
+ +include Add
90
+
91
+ puts add_4(9) # should be 13
92
+
93
+
94
+ 1) blah.rb
95
+ 2) add.rb (or 's' for same)
96
+ """
97
+ When I type "1"
98
+ Then I should see:
99
+ """
100
+ Alice Amos <developers+alice@foo.com> a37def2620e7:1: def add_4(x)
101
+ Bob Barker <developers+bob@foo.com> c043b110cc46:2: x + 5
102
+ Alice Amos <developers+alice@foo.com> a37def2620e7:3: end
103
+ Alice Amos <developers+alice@foo.com> a37def2620e7:4:
104
+ Alice Amos <developers+alice@foo.com> a37def2620e7:5: puts add_4(9) # should be 13
105
+
106
+ (h for help) >
107
+ """
108
+ When I type "2"
109
+ Then I should see:
110
+ """
111
+ # HG changeset patch
112
+ # User Bob Barker <developers+bob@foo.com>
113
+ # Date 1326581213 28800
114
+ # Sat Jan 14 14:46:53 2012 -0800
115
+ # Node ID c043b110cc46389037e207f814d251f4c7e00a7b
116
+ # Parent a37def2620e7853b1c44aafc2b7ae528340c37aa
117
+ being bad
118
+
119
+ committer: Charles Finkel <charles.finkel@gmail.com>
120
+
121
+ diff -r a37def2620e7 -r c043b110cc46 blah.rb
122
+ --- a/blah.rb Sat Jan 14 14:46:18 2012 -0800
123
+ +++ b/blah.rb Sat Jan 14 14:46:53 2012 -0800
124
+ @@ -1,5 +1,5 @@
125
+ def add_4(x)
126
+ - x + 4
127
+ + x + 5
128
+ end
129
+
130
+ puts add_4(9) # should be 13
131
+
132
+
133
+ 1) blah.rb (or 's' for same)
134
+
135
+ (h for help) >
136
+ """
137
+ When I type "q"
138
+ Then I should see:
139
+ """
140
+ The responsible commit is:
141
+
142
+ changeset: 1:c043b110cc46
143
+ user: Bob Barker <developers+bob@foo.com>
144
+ date: Sat Jan 14 14:46:53 2012 -0800
145
+ summary: being bad
146
+ """
147
+
@@ -0,0 +1,253 @@
1
+ Feature: Other scenarios
2
+
3
+ Scenario: Getting help
4
+ When I run `bin/hg-blame-game --help`
5
+ Then it should pass with:
6
+ """
7
+ Usage: hg-blame-game [options] path/to/filename
8
+ """
9
+
10
+ Scenario: Without a filepath:
11
+ When I run `bin/hg-blame-game`
12
+ Then it should fail with:
13
+ """
14
+ missing argument: You must specify a path to a file
15
+ """
16
+
17
+ Scenario: Specifying a rep that doesn't exist:
18
+ When I run `bin/hg-blame-game file/that/doesnt/exist.rb`
19
+ Then it should fail with:
20
+ """
21
+ abort: no repository found in
22
+ """
23
+
24
+ Scenario: Invalid input on hg blame view:
25
+ Given I cd to "test/fixtures/sample_hg_repo"
26
+ When I run `../../../bin/hg-blame-game add.rb` interactively
27
+ And I type "foobar"
28
+ Then I should see:
29
+ """
30
+ Invalid input. Enter:
31
+ - the line number from the above list (from 1 to 5) you are hg blaming.
32
+ - the changeset_id to chain into.
33
+ - 'r' to re-view the hg blame
34
+
35
+ (h for help) >
36
+ """
37
+
38
+ Scenario: Invalid input on hg export view:
39
+ Given I cd to "test/fixtures/sample_hg_repo"
40
+ When I run `../../../bin/hg-blame-game add.rb` interactively
41
+ And I type "3"
42
+ And I type "blah"
43
+ Then I should see:
44
+ """
45
+ Invalid input. Enter:
46
+ - 'q' to quit, if you have found the offending commit
47
+ - the number from the above list (from 1 to 1) of the file to chain into.
48
+ - the filepath to chain into.
49
+ - 's' to chain into the 'same' file as before
50
+ - 'r' to re-view the hg export
51
+
52
+ (h for help) >
53
+ """
54
+
55
+ Scenario: With a SHA:
56
+ Given I cd to "test/fixtures/sample_hg_repo"
57
+ When I run `../../../bin/hg-blame-game blah.rb --rev=c043b110cc46` interactively
58
+ Then I should see:
59
+ """
60
+ Alice Amos <developers+alice@foo.com> a37def2620e7:1: def add_4(x)
61
+ Bob Barker <developers+bob@foo.com> c043b110cc46:2: x + 5
62
+ Alice Amos <developers+alice@foo.com> a37def2620e7:3: end
63
+ Alice Amos <developers+alice@foo.com> a37def2620e7:4:
64
+ Alice Amos <developers+alice@foo.com> a37def2620e7:5: puts add_4(9) # should be 13
65
+ """
66
+
67
+ Scenario: Entering the SHA instead of the number
68
+ Given I cd to "test/fixtures/sample_hg_repo"
69
+ When I run `../../../bin/hg-blame-game add.rb` interactively
70
+ When I type "acb762f5f681"
71
+ Then I should see:
72
+ """
73
+ # HG changeset patch
74
+ # User Danny Dover <developers+danny@foo.com>
75
+ # Date 1326581406 28800
76
+ # Sat Jan 14 14:50:06 2012 -0800
77
+ # Node ID acb762f5f681d31b710f49f563ba8d1562b9e4c2
78
+ # Parent c8769d6446bc9adf5c0ad0394d87c31594f0cd2b
79
+ I like y's better
80
+
81
+ committer: Charles Finkel <charles.finkel@gmail.com>
82
+
83
+ diff -r c8769d6446bc -r acb762f5f681 add.rb
84
+ --- a/add.rb Sat Jan 14 14:49:00 2012 -0800
85
+ +++ b/add.rb Sat Jan 14 14:50:06 2012 -0800
86
+ @@ -1,5 +1,5 @@
87
+ module Add
88
+ - def add_4(x)
89
+ - x + 5
90
+ + def add_4(y)
91
+ + y + 5
92
+ end
93
+ end
94
+
95
+ """
96
+
97
+ Scenario: Entering 's' for the same file to hg blame into
98
+ Given I cd to "test/fixtures/sample_hg_repo"
99
+ When I run `../../../bin/hg-blame-game add.rb` interactively
100
+ When I type "3"
101
+ Then I should see:
102
+ """
103
+ 1) add.rb
104
+ """
105
+ When I type "s"
106
+ Then I should see:
107
+ """
108
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:1: module Add
109
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:2: def add_4(x)
110
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:3: x + 5
111
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:4: end
112
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:5: end
113
+ """
114
+
115
+ Scenario: Entering the filename for the file to hg blame into:
116
+ Given I cd to "test/fixtures/sample_hg_repo"
117
+ When I run `../../../bin/hg-blame-game add.rb` interactively
118
+ When I type "3"
119
+ Then I should see:
120
+ """
121
+ 1) add.rb (or 's' for same)
122
+ """
123
+ When I type "add.rb"
124
+ Then I should see:
125
+ """
126
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:1: module Add
127
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:2: def add_4(x)
128
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:3: x + 5
129
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:4: end
130
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:5: end
131
+ """
132
+
133
+ Scenario: Re-viewing a hg blame:
134
+ Given I cd to "test/fixtures/sample_hg_repo"
135
+ When I run `../../../bin/hg-blame-game add.rb` interactively
136
+ Then I should see:
137
+ """
138
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:1: module Add
139
+ Danny Dover <developers+danny@foo.com> acb762f5f681:2: def add_4(y)
140
+ Danny Dover <developers+danny@foo.com> acb762f5f681:3: y + 5
141
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:4: end
142
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:5: end
143
+ """
144
+ When I type "r"
145
+ Then I should see:
146
+ """
147
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:1: module Add
148
+ Danny Dover <developers+danny@foo.com> acb762f5f681:2: def add_4(y)
149
+ Danny Dover <developers+danny@foo.com> acb762f5f681:3: y + 5
150
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:4: end
151
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:5: end
152
+ """
153
+
154
+ Scenario: Re-viewing a hg export:
155
+ Given I cd to "test/fixtures/sample_hg_repo"
156
+ When I run `../../../bin/hg-blame-game add.rb` interactively
157
+ And I type "3"
158
+ Then I should see:
159
+ """
160
+ HG changeset patch
161
+ # User Danny Dover <developers+danny@foo.com>
162
+ # Date 1326581406 28800
163
+ # Sat Jan 14 14:50:06 2012 -0800
164
+ # Node ID acb762f5f681d31b710f49f563ba8d1562b9e4c2
165
+ # Parent c8769d6446bc9adf5c0ad0394d87c31594f0cd2b
166
+ I like y's better
167
+
168
+ committer: Charles Finkel <charles.finkel@gmail.com>
169
+
170
+ diff -r c8769d6446bc -r acb762f5f681 add.rb
171
+ --- a/add.rb Sat Jan 14 14:49:00 2012 -0800
172
+ +++ b/add.rb Sat Jan 14 14:50:06 2012 -0800
173
+ @@ -1,5 +1,5 @@
174
+ module Add
175
+ - def add_4(x)
176
+ - x + 5
177
+ + def add_4(y)
178
+ + y + 5
179
+ end
180
+ end
181
+
182
+
183
+ 1) add.rb (or 's' for same)
184
+ """
185
+ When I type "r"
186
+ Then I should see:
187
+ """
188
+ HG changeset patch
189
+ # User Danny Dover <developers+danny@foo.com>
190
+ # Date 1326581406 28800
191
+ # Sat Jan 14 14:50:06 2012 -0800
192
+ # Node ID acb762f5f681d31b710f49f563ba8d1562b9e4c2
193
+ # Parent c8769d6446bc9adf5c0ad0394d87c31594f0cd2b
194
+ I like y's better
195
+
196
+ committer: Charles Finkel <charles.finkel@gmail.com>
197
+
198
+ diff -r c8769d6446bc -r acb762f5f681 add.rb
199
+ --- a/add.rb Sat Jan 14 14:49:00 2012 -0800
200
+ +++ b/add.rb Sat Jan 14 14:50:06 2012 -0800
201
+ @@ -1,5 +1,5 @@
202
+ module Add
203
+ - def add_4(x)
204
+ - x + 5
205
+ + def add_4(y)
206
+ + y + 5
207
+ end
208
+ end
209
+
210
+
211
+ 1) add.rb (or 's' for same)
212
+ """
213
+
214
+ Scenario: Getting help interactively for git blame:
215
+ Given I cd to "test/fixtures/sample_hg_repo"
216
+ When I run `../../../bin/hg-blame-game add.rb` interactively
217
+ Then I should see:
218
+ """
219
+ (h for help) >
220
+ """
221
+ When I type "h"
222
+ Then I should see:
223
+ """
224
+ Enter:
225
+ - the line number from the above list (from 1 to 5) you are hg blaming.
226
+ - the changeset_id to chain into.
227
+ - 'r' to re-view the hg blame
228
+
229
+ (h for help) >
230
+ """
231
+
232
+ Scenario: Getting help interactively for hg export:
233
+ Given I cd to "test/fixtures/sample_hg_repo"
234
+ When I run `../../../bin/hg-blame-game add.rb` interactively
235
+ And I type "3"
236
+ Then I should see:
237
+ """
238
+ (h for help) >
239
+ """
240
+ When I type "h"
241
+ Then I should see:
242
+ """
243
+ Enter:
244
+ - 'q' to quit, if you have found the offending commit
245
+ - the number from the above list (from 1 to 1) of the file to chain into.
246
+ - the filepath to chain into.
247
+ - 's' to chain into the 'same' file as before
248
+ - 'r' to re-view the hg export
249
+
250
+ (h for help) >
251
+ """
252
+
253
+
@@ -0,0 +1,26 @@
1
+ World(ArubaExt)
2
+
3
+ Then /^the output should contain, ignoring spaces:$/ do |expected|
4
+ assert_partial_output(expected.gsub("\s", ''), all_output.gsub("\s", ''))
5
+ end
6
+
7
+ Then /^I should see:$/ do |expected|
8
+ assert_seen(expected)
9
+ end
10
+
11
+ Then /^the next bit of output should contain, ignoring spaces:$/ do |expected|
12
+ assert_seen(expected)
13
+ end
14
+
15
+ def assert_seen(expected)
16
+ @seen_output ||= ''
17
+ expected = unescape(expected.gsub("\s", ''))
18
+ wait_until_expectation do
19
+ @all_output = only_processes[0].output
20
+
21
+ actual = unescape(@all_output[@seen_output.size..-1].gsub("\s", ''))
22
+
23
+ actual.should include(expected)
24
+ end
25
+ @seen_output = @all_output
26
+ end
@@ -0,0 +1,41 @@
1
+ # extension methods for Aruba
2
+ module ArubaExt
3
+
4
+ def wait_until(seconds = 5)
5
+ timeout(seconds) { yield }
6
+ end
7
+
8
+ def timeout(seconds = 50, &block)
9
+ start_time = Time.now
10
+
11
+ result = nil
12
+
13
+ until result
14
+ return result if result = yield
15
+
16
+ delay = seconds - (Time.now - start_time)
17
+ if delay <= 0
18
+ raise TimeoutError, "timed out"
19
+ end
20
+
21
+ sleep(0.05)
22
+ end
23
+ end
24
+
25
+ def wait_until_expectation
26
+ begin
27
+ exception = nil
28
+ wait_until do
29
+ begin
30
+ yield
31
+ true
32
+ rescue RSpec::Expectations::ExpectationNotMetError => e
33
+ exception = e
34
+ false
35
+ end
36
+ end
37
+ rescue TimeoutError
38
+ raise exception
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ require 'aruba/cucumber'
2
+
3
+ def unzip_repo_if_needed!
4
+ fixtures_dir = File.expand_path(File.dirname(__FILE__)) + '/../../test/fixtures'
5
+ unless File.directory? fixtures_dir + '/sample_hg_repo'
6
+ `unzip #{fixtures_dir}/sample_hg_repo.zip -d #{fixtures_dir}`
7
+ end
8
+ end
9
+
10
+ # for some reason Aruba sets the default dir to 'tmp/aruba'. Override this:
11
+ def set_relative_dir_for_aruba!
12
+ root_dir = File.expand_path("../../../", __FILE__)
13
+ self.instance_variable_set('@dirs', [root_dir])
14
+ end
15
+
16
+ Before do
17
+ unzip_repo_if_needed!
18
+ set_relative_dir_for_aruba!
19
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "hg-blame-game/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "hg-blame-game"
7
+ s.version = Hg::Blame::Game::VERSION
8
+ s.authors = ["Charles Finkel"]
9
+ s.email = ["charles.finkel@gmail.com"]
10
+ s.homepage = "https://github.com/charleseff/hg-blame-game-ruby"
11
+ s.summary = %q{hg-blame-game is an interactive command for chaining 'hg blame' calls to get to the real culprit for the line of code you care about, when one `hg blame` does not tell the whole story.}
12
+ s.description = %q{When one `hg blame` is not enough}
13
+
14
+ s.rubyforge_project = "hg-blame-game"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency 'aruba'
22
+ s.add_development_dependency 'rspec'
23
+ s.add_runtime_dependency 'colorize'
24
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'colorize'
3
+ require 'optparse'
4
+ require 'hg-blame-game/hg_blame_game'
@@ -0,0 +1,146 @@
1
+ class HgBlameGame
2
+ def initialize(path_to_file, opts={})
3
+ @path_to_file = path_to_file
4
+ @changeset_id = !opts[:changeset_id].nil? ? opts[:changeset_id] : 'tip'
5
+ end
6
+
7
+ def run
8
+ loop do
9
+ p_flush("\n")
10
+
11
+ changeset_id_to_show = show_hg_blame_and_prompt_for_changeset_id
12
+
13
+ p_flush("\n")
14
+
15
+ files_changed = sys("hg status --change #{changeset_id_to_show}").split("\n").map { |g| g.split(' ').last }
16
+
17
+ @path_to_file = prompt_for_file(files_changed, changeset_id_to_show)
18
+ @changeset_id = "#{changeset_id_to_show}^"
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def sys(cmd)
25
+ puts "Executing: #{cmd}" if ENV['DEBUG']
26
+ `#{cmd}`
27
+ end
28
+
29
+ def show_hg_blame_and_prompt_for_changeset_id
30
+ hg_blame_out = sys hg_blame_cmd
31
+ exit $?.exitstatus unless $?.success?
32
+ changeset_id_list = get_changeset_id_list(hg_blame_out)
33
+ print_hg_blame_and_prompt
34
+ prompt_for_changeset_id(changeset_id_list)
35
+ end
36
+
37
+ def prompt_for_changeset_id(changeset_ids)
38
+ loop do
39
+ input = $stdin.gets.strip
40
+ # changeset_id was entered, return it:
41
+ return input if changeset_ids.include? input
42
+
43
+ if input =~ /\A\d+\Z/
44
+ input = input.to_i
45
+ return changeset_ids[input - 1] if input <= changeset_ids.count && input >= 1
46
+ end
47
+
48
+ if input == 'r'
49
+ print_hg_blame_and_prompt
50
+ elsif input == 'h'
51
+ p_flush prompt_for_changeset_id_message(changeset_ids.count)
52
+ else
53
+ p_flush "\nInvalid input. " + prompt_for_changeset_id_message(changeset_ids.count)
54
+ end
55
+ end
56
+ end
57
+
58
+ def print_hg_blame_and_prompt
59
+ system hg_blame_cmd
60
+ p_flush "\n" + simple_prompt
61
+ end
62
+
63
+ def hg_blame_cmd
64
+ "hg blame --rev #{@changeset_id} --verbose --user --line-number --changeset #{@path_to_file}"
65
+ end
66
+
67
+ HG_BLAME_REGEX = / ([^\: ]+):/
68
+
69
+ def get_changeset_id_list(hg_blame_out)
70
+ hg_blame_out.strip.split("\n").map { |line| line[HG_BLAME_REGEX, 1] }
71
+ end
72
+
73
+ def prompt_for_file(files_changed, changeset_id)
74
+ print_file_prompt(files_changed, changeset_id)
75
+
76
+ loop do
77
+ input = $stdin.gets.strip
78
+ if input == 'q'
79
+ p_flush "\n" + color("The responsible commit is:") + "\n\n"
80
+ system "hg log --rev #{changeset_id}"
81
+ exit 0
82
+ end
83
+ return @path_to_file if input == 's'
84
+ return input if files_changed.include? input
85
+
86
+ if input =~ /\A\d+\Z/
87
+ input = input.to_i
88
+ return files_changed[input-1] if input >= 1 && input <= files_changed.count
89
+ end
90
+
91
+ if input == 'r'
92
+ print_file_prompt(files_changed, changeset_id)
93
+ elsif input == 'h'
94
+ p_flush prompt_for_file_message(files_changed.count)
95
+ else
96
+ p_flush "\nInvalid input. " + prompt_for_file_message(files_changed.count)
97
+ end
98
+ end
99
+ end
100
+
101
+ def print_file_prompt(files, changeset_id)
102
+ system "hg export #{changeset_id}"
103
+ print("\n")
104
+ files.each_with_index do |file, index|
105
+ line = sprintf("%3d) #{file}", index+1)
106
+ line += ' ' + orange_color("(or 's' for same)") if file == @path_to_file
107
+ print line + "\n"
108
+ end
109
+ p_flush "\n" + simple_prompt
110
+ end
111
+
112
+ def prompt_for_file_message(count)
113
+ "Enter:\n" +
114
+ " - 'q' to quit, if you have found the offending commit\n" +
115
+ " - the number from the above list (from 1 to #{count}) of the file to chain into.\n" +
116
+ " - the filepath to chain into.\n" +
117
+ " - 's' to chain into the 'same' file as before\n" +
118
+ " - 'r' to re-view the hg export\n\n" +
119
+ simple_prompt
120
+ end
121
+
122
+ def simple_prompt
123
+ color("(h for help) >") + ' '
124
+ end
125
+
126
+ def prompt_for_changeset_id_message(count)
127
+ "Enter:\n" +
128
+ " - the line number from the above list (from 1 to #{count}) you are hg blaming.\n" +
129
+ " - the changeset_id to chain into.\n" +
130
+ " - 'r' to re-view the hg blame\n\n" + simple_prompt
131
+ end
132
+
133
+ def p_flush(str)
134
+ $stdout.print str
135
+ $stdout.flush
136
+ end
137
+
138
+ def color(s)
139
+ s.colorize(:color => :light_white, :background => :magenta)
140
+ end
141
+
142
+ def orange_color(s)
143
+ s.colorize(:color => :yellow, :background => :black)
144
+ end
145
+
146
+ end
@@ -0,0 +1,7 @@
1
+ module Hg
2
+ module Blame
3
+ module Game
4
+ VERSION = "0.1.1"
5
+ end
6
+ end
7
+ end
Binary file
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe HgBlameGame do
4
+
5
+ let(:hg_blame_game) { HgBlameGame.new('some_file') }
6
+ let(:changeset_id_list) { %w(c8769d6446bc acb762f5f681 acb762f5f681 c8769d6446bc c8769d6446bc) }
7
+
8
+ before do
9
+ $stdout.stub(:print)
10
+ end
11
+
12
+ describe "#get_changeset_id_list" do
13
+ let(:hg_blame_out) {
14
+ <<-END.gsub(/^[ \t]+/m, '')
15
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:1: module Add
16
+ Danny Dover <developers+danny@foo.com> acb762f5f681:2: def add_4(y)
17
+ Danny Dover <developers+danny@foo.com> acb762f5f681:3: y + 5
18
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:4: end
19
+ Carmen Cummings <developers+carmen@foo.com> c8769d6446bc:5: end
20
+ END
21
+ }
22
+
23
+ it "should return a list of changeset_ids" do
24
+ hg_blame_game.send(:get_changeset_id_list,hg_blame_out).should == changeset_id_list
25
+ end
26
+ end
27
+
28
+ describe "prompt_for_changeset_id" do
29
+ before do
30
+ $stdin.should_receive(:gets).and_return(input)
31
+ end
32
+ context "when user enters a correct changeset_id" do
33
+ let(:input) { 'c8769d6446bc' }
34
+ it "should return the correct changeset_id" do
35
+ hg_blame_game.send(:prompt_for_changeset_id, changeset_id_list).should == 'c8769d6446bc'
36
+ end
37
+ end
38
+ context "when user enters a correct number" do
39
+ let(:input) { '1' }
40
+ it "should return the correct changeset_id" do
41
+ hg_blame_game.send(:prompt_for_changeset_id, changeset_id_list).should == 'c8769d6446bc'
42
+ end
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1 @@
1
+ require 'hg-blame-game'
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hg-blame-game
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Charles Finkel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aruba
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: When one `hg blame` is not enough
56
+ email:
57
+ - charles.finkel@gmail.com
58
+ executables:
59
+ - hg-annotate-game
60
+ - hg-blame-game
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - .gitignore
65
+ - .ruby-version
66
+ - .travis.yml
67
+ - Gemfile
68
+ - README.md
69
+ - Rakefile
70
+ - TODO
71
+ - bin/hg-annotate-game
72
+ - bin/hg-blame-game
73
+ - features/happy_path.feature
74
+ - features/other_scenarios.feature
75
+ - features/step_definitions/gbc_steps.rb
76
+ - features/support/aruba_ext.rb
77
+ - features/support/env.rb
78
+ - hg-blame-game.gemspec
79
+ - lib/hg-blame-game.rb
80
+ - lib/hg-blame-game/hg_blame_game.rb
81
+ - lib/hg-blame-game/version.rb
82
+ - public/pensive-kanye.png
83
+ - spec/hg-blame-game/hg_blame_game_spec.rb
84
+ - spec/spec_helper.rb
85
+ - test/fixtures/sample_hg_repo.zip
86
+ homepage: https://github.com/charleseff/hg-blame-game-ruby
87
+ licenses: []
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project: hg-blame-game
105
+ rubygems_version: 2.0.5
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: hg-blame-game is an interactive command for chaining 'hg blame' calls to
109
+ get to the real culprit for the line of code you care about, when one `hg blame`
110
+ does not tell the whole story.
111
+ test_files:
112
+ - features/happy_path.feature
113
+ - features/other_scenarios.feature
114
+ - features/step_definitions/gbc_steps.rb
115
+ - features/support/aruba_ext.rb
116
+ - features/support/env.rb
117
+ - spec/hg-blame-game/hg_blame_game_spec.rb
118
+ - spec/spec_helper.rb
119
+ - test/fixtures/sample_hg_repo.zip