git-autobisect 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- git-autobisect (0.1.1)
4
+ git-autobisect (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
data/Rakefile CHANGED
@@ -3,3 +3,20 @@ require 'bundler/gem_tasks'
3
3
  task :default do
4
4
  sh "rspec spec/"
5
5
  end
6
+
7
+ # extracted from https://github.com/grosser/project_template
8
+ rule /^version:bump:.*/ do |t|
9
+ sh "git status | grep 'nothing to commit'" # ensure we are not dirty
10
+ index = ['major', 'minor','patch'].index(t.name.split(':').last)
11
+ file = 'lib/git/autobisect/version.rb'
12
+
13
+ version_file = File.read(file)
14
+ old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
15
+ version_parts[index] = version_parts[index].to_i + 1
16
+ version_parts[2] = 0 if index < 2 # remove patch for minor
17
+ version_parts[1] = 0 if index < 1 # remove minor for major
18
+ new_version = version_parts * '.'
19
+ File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
20
+
21
+ sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
22
+ end
data/Readme.md CHANGED
@@ -2,7 +2,7 @@ Find the first broken commit without having to learn git bisect.
2
2
 
3
3
  - automagically bundles if necessary
4
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)
5
+ - takes binary steps (HEAD~1, HEAD~2, HEAD~4, HEAD~8)
6
6
 
7
7
  Install
8
8
  =======
@@ -19,10 +19,13 @@ Usage
19
19
  ---> The first bad commit is a4328fa
20
20
  git show
21
21
 
22
+ ### Options
23
+
24
+ -m, --max [N] Inspect commits between HEAD..HEAD~<max>
25
+
22
26
  TODO
23
27
  ====
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
28
+ - option for max-step-size so you can use a finer grained approach
26
29
  - option to disable `bundle check || bundle` injection
27
30
  - option to consider a build failed if it finishes faster then x seconds
28
31
 
data/bin/git-autobisect CHANGED
@@ -1,94 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
-
3
- require 'rubygems'
4
2
  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
-
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require 'git/autobisect'
5
+ exit Git::Autobisect.cli(ARGV)
@@ -1,8 +1,8 @@
1
1
  $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
2
  name = "git-autobisect"
3
- version = File.read(File.expand_path("../VERSION", __FILE__))
3
+ require "git/autobisect/version"
4
4
 
5
- Gem::Specification.new name, version do |s|
5
+ Gem::Specification.new name, Git::Autobisect::Version do |s|
6
6
  s.summary = "Find the first broken commit without having to learn git bisect"
7
7
  s.authors = ["Michael Grosser"]
8
8
  s.email = "michael@grosser.it"
@@ -0,0 +1,116 @@
1
+ require "git/autobisect/version"
2
+
3
+ module Git
4
+ module Autobisect
5
+ class << self
6
+ def cli(argv)
7
+ options = extract_options(argv)
8
+
9
+ command = argv.first
10
+ if command.to_s.empty?
11
+ puts "Usage instructions: git-autobisect --help"
12
+ return 1
13
+ end
14
+
15
+ command = "(bundle check || bundle) && (#{command})" if File.exist?("Gemfile")
16
+
17
+ run_command(command, options) || 0
18
+ end
19
+
20
+ def run_command(command, options)
21
+ commits = `git log --pretty=format:'%h' | head -n #{options[:max]}`.split("\n")
22
+ good, bad = find_good_and_bad_commit(commits, command)
23
+
24
+ if good == commits.first
25
+ puts " ---> HEAD is not broken"
26
+ return 1
27
+ elsif not good
28
+ puts " ---> No good commit found before HEAD~#{options[:max]}"
29
+ return 1
30
+ end
31
+
32
+ if exact_commit_known?(commits, good, bad)
33
+ # return same result as git bisect
34
+ run! "git checkout #{bad}"
35
+ puts "#{bad} is the first bad commit"
36
+ puts `git show #{bad}`
37
+ else
38
+ first_bad = bisect_to_exact_match(command, good, bad)
39
+ run! "git checkout #{first_bad}"
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def extract_options(argv)
46
+ options = {
47
+ :max => 1000
48
+ }
49
+ OptionParser.new do |opts|
50
+ opts.banner = <<-BANNER.gsub(" "*12, "")
51
+ Find the commit that broke the build
52
+
53
+ Usage:
54
+ git-autobisect 'ruby test/foo_test.rb -n "/xxx/"' [options]
55
+
56
+ Options:
57
+ BANNER
58
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
59
+ opts.on("-v", "--version", "Show Version"){ puts "git-autobisect #{Version}"; exit }
60
+ opts.on("-m", "--max [N]", Integer, "Inspect commits between HEAD..HEAD~<max>"){|max| options[:max] = max }
61
+ end.parse!(argv)
62
+ options
63
+ end
64
+
65
+ def run(cmd)
66
+ all = ""
67
+ puts cmd
68
+ IO.popen(cmd) do |pipe|
69
+ while str = pipe.gets
70
+ all << str
71
+ puts str
72
+ end
73
+ end
74
+ [$?.success?, all]
75
+ end
76
+
77
+ def run!(command)
78
+ raise "Command failed #{command}" unless run(command).first
79
+ end
80
+
81
+ def find_good_and_bad_commit(commits, command)
82
+ i = 0
83
+ maybe_good = commits.first
84
+
85
+ loop do
86
+ # scan backwards through commits to find a good
87
+ offset = [2**i - 1, commits.size-1].min
88
+ maybe_good, bad = commits[offset], maybe_good
89
+ return if i > 0 and bad == maybe_good # we reached the end
90
+
91
+ # see if it works
92
+ puts " ---> Now trying #{maybe_good} (HEAD~#{offset})"
93
+ run!("git checkout #{maybe_good}")
94
+ return [maybe_good, bad] if run(command).first
95
+ i += 1
96
+ end
97
+ end
98
+
99
+ def bisect_to_exact_match(command, good, bad)
100
+ run! "git bisect reset"
101
+ run! "git bisect start"
102
+ run! "git checkout #{bad}"
103
+ run! "git bisect bad"
104
+ run! "git checkout #{good}"
105
+ run! "git bisect good"
106
+ success, output = run("git bisect run sh -c '#{command}'")
107
+ raise "error while bisecting" unless success
108
+ output.match(/([\da-f]+) is the first bad commit/)[1]
109
+ end
110
+
111
+ def exact_commit_known?(commits, good, bad)
112
+ (commits.index(good) - commits.index(bad)).abs <= 1
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,5 @@
1
+ module Git
2
+ module Autobisect
3
+ VERSION = Version = "0.2.0"
4
+ end
5
+ end
@@ -16,8 +16,8 @@ describe "git-autobisect" do
16
16
  run "git log --oneline | head -1"
17
17
  end
18
18
 
19
- def add_irrelevant_commit(name)
20
- run "touch #{name} && git add #{name} && git commit -m 'added #{name}'"
19
+ def add_irrelevant_commit(name="b")
20
+ run "echo #{rand} >> #{name} && git add #{name} && git commit -m 'added #{name}'"
21
21
  end
22
22
 
23
23
  def remove_a
@@ -30,7 +30,7 @@ describe "git-autobisect" do
30
30
 
31
31
  describe "basics" do
32
32
  it "shows its usage without arguments" do
33
- autobisect("").should include("Usage")
33
+ autobisect("", :fail => true).should include("Usage")
34
34
  end
35
35
 
36
36
  it "shows its usage with -h" do
@@ -58,22 +58,47 @@ describe "git-autobisect" do
58
58
  end
59
59
 
60
60
  it "stops when the first commit works" do
61
- autobisect("'test 1'", :fail => true).should include("Current commit is not broken")
61
+ autobisect("'test 1'", :fail => true).should include("HEAD is not broken")
62
62
  end
63
63
 
64
64
  it "stops when no commit works" do
65
65
  autobisect("test", :fail => true).should include("No good commit found")
66
66
  end
67
67
 
68
+ context "--max" do
69
+ let(:command){ "'test -e a' --max 5" }
70
+
71
+ it "finds if a commit works inside of max range" do
72
+ remove_a
73
+ 3.times{ add_irrelevant_commit }
74
+ autobisect(command).should_not include("No good commit found")
75
+ end
76
+
77
+ it "stops when no commit works inside of max range" do
78
+ remove_a
79
+ 5.times{ add_irrelevant_commit }
80
+ autobisect(command, :fail => true).should include("No good commit found")
81
+ end
82
+ end
83
+
68
84
  it "finds the first broken commit for 1 commit" do
69
85
  remove_a
70
86
  result = autobisect("'test -e a'")
71
- result.should include("bisect run success")
87
+ result.should_not include("bisect run")
88
+ result.should =~ /is the first bad commit.*remove a/m
89
+ end
90
+
91
+ it "finds the first broken commit for multiple commits" do
92
+ remove_a
93
+ result = autobisect("'test -e a'")
94
+ result.should_not include("bisect run success")
72
95
  result.should =~ /is the first bad commit.*remove a/m
73
96
  end
74
97
 
75
98
  it "can run a complex command" do
99
+ 10.times{ add_irrelevant_commit }
76
100
  remove_a
101
+ 10.times{ add_irrelevant_commit }
77
102
  result = autobisect("'sleep 0.01 && test -e a'")
78
103
  result.should include("bisect run success")
79
104
  result.should =~ /is the first bad commit.*remove a/m
@@ -126,8 +151,7 @@ describe "git-autobisect" do
126
151
  result = autobisect("'echo a >> count && test -e a'")
127
152
  result.should include("bisect run success")
128
153
  result.should include("added e")
129
- result.should_not include("added d")
130
- File.read('count').count('a').should == 6
154
+ File.read('count').count('a').should == 4
131
155
  end
132
156
  end
133
157
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-autobisect
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-05 00:00:00.000000000 Z
12
+ date: 2012-10-06 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: michael@grosser.it
@@ -23,9 +23,10 @@ files:
23
23
  - Gemfile.lock
24
24
  - Rakefile
25
25
  - Readme.md
26
- - VERSION
27
26
  - bin/git-autobisect
28
27
  - git-autobisect.gemspec
28
+ - lib/git/autobisect.rb
29
+ - lib/git/autobisect/version.rb
29
30
  - spec/git-autobisect_spec.rb
30
31
  homepage: http://github.com/grosser/git-autobisect
31
32
  licenses:
@@ -42,7 +43,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
43
  version: '0'
43
44
  segments:
44
45
  - 0
45
- hash: -2753072243294133809
46
+ hash: -1063815506089341293
46
47
  required_rubygems_version: !ruby/object:Gem::Requirement
47
48
  none: false
48
49
  requirements:
@@ -51,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
52
  version: '0'
52
53
  segments:
53
54
  - 0
54
- hash: -2753072243294133809
55
+ hash: -1063815506089341293
55
56
  requirements: []
56
57
  rubyforge_project:
57
58
  rubygems_version: 1.8.24
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.1