git-autobisect 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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