git-autobisect 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'rspec', '~>2'
data/Gemfile.lock ADDED
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git-autobisect (0.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.3)
10
+ rake (0.9.2.2)
11
+ rspec (2.11.0)
12
+ rspec-core (~> 2.11.0)
13
+ rspec-expectations (~> 2.11.0)
14
+ rspec-mocks (~> 2.11.0)
15
+ rspec-core (2.11.1)
16
+ rspec-expectations (2.11.3)
17
+ diff-lcs (~> 1.1.3)
18
+ rspec-mocks (2.11.3)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ git-autobisect!
25
+ rake
26
+ rspec (~> 2)
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ task :default do
4
+ sh "rspec spec/"
5
+ end
data/Readme.md ADDED
@@ -0,0 +1,36 @@
1
+ Find the first broken commit without having to learn git bisect.
2
+
3
+ - automagically bundles if necessary
4
+ - stops at first bad commit
5
+ - starts slow (HEAD~1, HEAD~2, HEAD~3) then goes in steps of 10 (..., HEAD~10, HEAD~20, HEAD~30)
6
+
7
+ Install
8
+ =======
9
+
10
+ gem install git-autobisect
11
+
12
+ Usage
13
+ =====
14
+
15
+ cd your project
16
+ # run a test that has a non-0 exit status
17
+ git-autobisect 'rspec spec/models/user_spec.rb'
18
+ ... grab a coffee ...
19
+ ---> The first bad commit is a4328fa
20
+ git show
21
+
22
+ TODO
23
+ ====
24
+ - go with 1 2 4 8 16 16 16 16 commits back
25
+ - option for max-step -> if you think the problem is very fresh/very old
26
+
27
+ Development
28
+ ===========
29
+ - `bundle && bundle exec rake`
30
+ - Tests run a lot faster without `bundle exec`
31
+
32
+ Author
33
+ ======
34
+ [Michael Grosser](http://grosser.it)<br/>
35
+ michael@grosser.it<br/>
36
+ License: MIT<br/>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,94 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+
6
+ OptionParser.new do |opts|
7
+ opts.banner = <<BANNER
8
+ Find the commit that broke the build
9
+
10
+ Usage:
11
+ git-autobisect 'ruby test/foo_test.rb -n "/xxx/"' [options]
12
+
13
+ Options:
14
+ BANNER
15
+ opts.on("-h", "--help","Show this.") { puts opts; exit }
16
+ opts.on("-v", "--version","Show Version"){
17
+ version = File.read(File.expand_path("../../VERSION", __FILE__))
18
+ puts "git-autobisect #{version}"; exit
19
+ }
20
+ end.parse!
21
+
22
+ command = ARGV.first
23
+ if command.to_s.empty?
24
+ puts "Usage instructions: git-autobisect --help"
25
+ exit
26
+ end
27
+
28
+ def run(cmd)
29
+ all = ""
30
+ puts cmd
31
+ IO.popen(cmd) do |pipe|
32
+ while str = pipe.gets
33
+ all << str
34
+ puts str
35
+ end
36
+ end
37
+ [$?.success?, all]
38
+ end
39
+
40
+ def run!(command)
41
+ raise "Command failed #{command}" unless run(command).first
42
+ end
43
+
44
+ def find_first_good_commit(commits, command)
45
+ # scan backwards through commits to find a good
46
+ i = 0
47
+ stay_slow_until = 3
48
+
49
+ loop do
50
+ # pick next commit (start slow, then get faster)
51
+ i += 1
52
+ offset = (i <= stay_slow_until ? i-1 : (i-1)*10)
53
+ break unless commit = commits[offset]
54
+
55
+ # see if it works
56
+ puts " ---> Now trying #{commit}"
57
+ run!("git checkout #{commit}")
58
+ return commit if run(command).first
59
+ end
60
+ end
61
+
62
+ command = "(bundle check || bundle) && (#{command})" if File.exist?("Gemfile")
63
+ puts " ---> Initial run:"
64
+ if run(command).first
65
+ puts " ---> Current commit is not broken"
66
+ exit 1
67
+ end
68
+
69
+ puts " ---> Trying to find first good commit:"
70
+ max_commits = 1000
71
+ commits = `git log --pretty=format:'%h' | head -n #{max_commits}`.split("\n")
72
+ unless good = find_first_good_commit(commits[1..-1], command)
73
+ puts " --> No good commit found"
74
+ exit 1
75
+ end
76
+
77
+ # bisect to get exact match
78
+ bad = commits[0]
79
+ run! "git bisect reset"
80
+ run! "git bisect start"
81
+ run! "git checkout #{bad}"
82
+ run! "git bisect bad"
83
+ run! "git checkout #{good}"
84
+ run! "git bisect good"
85
+ success, output = run("git bisect run sh -c '#{command}'")
86
+ if success
87
+ # git bisect randomly stops at a commit
88
+ first_bad = output.match(/([\da-f]+) is the first bad commit/)[1]
89
+ run! "git checkout #{first_bad}"
90
+ exit 0
91
+ else
92
+ exit 1
93
+ end
94
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
+ name = "git-autobisect"
3
+ version = File.read(File.expand_path("../VERSION", __FILE__))
4
+
5
+ Gem::Specification.new name, version do |s|
6
+ s.summary = "Find the first broken commit without having to learn git bisect"
7
+ s.authors = ["Michael Grosser"]
8
+ s.email = "michael@grosser.it"
9
+ s.homepage = "http://github.com/grosser/#{name}"
10
+ s.files = `git ls-files`.split("\n")
11
+ s.license = "MIT"
12
+ end
@@ -0,0 +1,134 @@
1
+ ROOT = File.expand_path('../../', __FILE__)
2
+
3
+ describe "git-autobisect" do
4
+ def run(command, options={})
5
+ result = `#{command} 2>&1`
6
+ message = (options[:fail] ? "SUCCESS BUT SHOULD FAIL" : "FAIL")
7
+ raise "[#{message}] #{result} [#{command}]" if $?.success? == !!options[:fail]
8
+ result
9
+ end
10
+
11
+ def autobisect(args, options={})
12
+ run "#{ROOT}/bin/git-autobisect #{args}", options
13
+ end
14
+
15
+ def current_commit
16
+ run "git log --oneline | head -1"
17
+ end
18
+
19
+ def add_irrelevant_commit(name)
20
+ run "touch #{name} && git add #{name} && git commit -m 'added #{name}'"
21
+ end
22
+
23
+ def remove_a
24
+ run "git rm a && git commit -m 'remove a'"
25
+ end
26
+
27
+ before do
28
+ Dir.chdir ROOT
29
+ end
30
+
31
+ describe "basics" do
32
+ it "shows its usage without arguments" do
33
+ autobisect("").should include("Usage")
34
+ end
35
+
36
+ it "shows its usage with -h" do
37
+ autobisect("-h").should include("Usage")
38
+ end
39
+
40
+ it "shows its usage with --help" do
41
+ autobisect("--help").should include("Usage")
42
+ end
43
+
44
+ it "shows its version with -v" do
45
+ autobisect("-v").should =~ /^git-autobisect \d+\.\d+\.\d+$/
46
+ end
47
+
48
+ it "shows its version with --version" do
49
+ autobisect("-v").should =~ /^git-autobisect \d+\.\d+\.\d+$/
50
+ end
51
+ end
52
+
53
+ describe "bisecting" do
54
+ before do
55
+ run "rm -rf spec/tmp ; mkdir spec/tmp"
56
+ Dir.chdir "spec/tmp"
57
+ run "git init && touch a && git add a && git commit -m 'added a'"
58
+ end
59
+
60
+ it "stops when the first commit works" do
61
+ autobisect("'test 1'", :fail => true).should include("Current commit is not broken")
62
+ end
63
+
64
+ it "stops when no commit works" do
65
+ autobisect("test", :fail => true).should include("No good commit found")
66
+ end
67
+
68
+ it "finds the first broken commit for 1 commit" do
69
+ remove_a
70
+ result = autobisect("'test -e a'")
71
+ result.should include("bisect run success")
72
+ result.should =~ /is the first bad commit.*remove a/m
73
+ end
74
+
75
+ it "can run a complex command" do
76
+ remove_a
77
+ result = autobisect("'sleep 0.01 && test -e a'")
78
+ result.should include("bisect run success")
79
+ result.should =~ /is the first bad commit.*remove a/m
80
+ end
81
+
82
+ it "is fast for a large number of commits" do
83
+ # build a ton of commits
84
+ 40.times do |i|
85
+ add_irrelevant_commit("#{i}_good")
86
+ end
87
+ run "git rm a && git commit -m 'remove a'"
88
+ 40.times do |i|
89
+ add_irrelevant_commit("#{i}_bad")
90
+ end
91
+
92
+ # ran successful ?
93
+ result = autobisect("'echo a >> count && test -e a'")
94
+ result.should include("bisect run success")
95
+ result.should =~ /is the first bad commit.*remove a/m
96
+
97
+ # ran fast?
98
+ File.read('count').count('a').should < 20
99
+ end
100
+
101
+ it "stays at the first broken commit" do
102
+ remove_a
103
+ autobisect("'test -e a'")
104
+ current_commit.should include("remove a")
105
+ end
106
+
107
+ context "with multiple good commits after broken commit" do
108
+ before do
109
+ add_irrelevant_commit "b"
110
+ add_irrelevant_commit "c"
111
+ add_irrelevant_commit "d"
112
+ add_irrelevant_commit "e" # first good
113
+ remove_a
114
+ add_irrelevant_commit "f" # last bad
115
+ add_irrelevant_commit "g"
116
+ end
117
+
118
+ it "finds the first broken commit for n commits" do
119
+ result = autobisect("'test -e a'")
120
+ result.should include("bisect run success")
121
+ result.should =~ /is the first bad commit.*remove a/m
122
+ current_commit.should include("remove a")
123
+ end
124
+
125
+ it "does not run test too often" do
126
+ result = autobisect("'echo a >> count && test -e a'")
127
+ result.should include("bisect run success")
128
+ result.should include("added e")
129
+ result.should_not include("added d")
130
+ File.read('count').count('a').should == 6
131
+ end
132
+ end
133
+ end
134
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-autobisect
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Grosser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-03 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: michael@grosser.it
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - Gemfile
21
+ - Gemfile.lock
22
+ - Rakefile
23
+ - Readme.md
24
+ - VERSION
25
+ - bin/git-autobisect
26
+ - git-autobisect.gemspec
27
+ - spec/git-autobisect_spec.rb
28
+ homepage: http://github.com/grosser/git-autobisect
29
+ licenses:
30
+ - MIT
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ segments:
42
+ - 0
43
+ hash: 2506649951595822978
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ segments:
51
+ - 0
52
+ hash: 2506649951595822978
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 1.8.24
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Find the first broken commit without having to learn git bisect
59
+ test_files: []