Skip to content

Commit 01476ff

Browse files
author
Tim Morgan
committed
Added git-blame command
1 parent b79de3a commit 01476ff

File tree

4 files changed

+78
-0
lines changed

4 files changed

+78
-0
lines changed

lib/git.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require 'git/working_directory'
1414

1515
require 'git/log'
16+
require 'git/blame'
1617
require 'git/object'
1718

1819
require 'git/branches'

lib/git/base.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ def log(count = 30)
170170
Git::Log.new(self, count)
171171
end
172172

173+
# returns a Git::BlameResult object with the commits that most recently
174+
# modified each line
175+
def blame(file, opts={})
176+
Git::Blame.new(self, file, opts).result
177+
end
178+
173179
# returns a Git::Status object
174180
def status
175181
Git::Status.new(self)

lib/git/blame.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
class Git::Blame
2+
attr_reader :result
3+
4+
def initialize(base, file, opts={})
5+
@base = base
6+
@file = file
7+
@options = opts
8+
9+
@result = run_blame
10+
end
11+
12+
private
13+
14+
def run_blame
15+
lines = @base.lib.blame(@file, @options)
16+
result = Git::BlameResult.new(@base)
17+
lines.each do |line|
18+
words = line.split(/\s+/)
19+
next unless words.first.length == 40 #TODO this breaks if git ever introduces a 40-character attribute name
20+
ref = words[0]
21+
block_start = words[2].to_i
22+
block_length = words[3].to_i
23+
result.add_block ref, block_start, block_length
24+
end
25+
result
26+
end
27+
end
28+
29+
class Git::BlameResult
30+
delegate :[], :each, :each_with_index, to: :lines
31+
32+
def initialize(base)
33+
@base = base
34+
@commit_cache = {}
35+
@lines = []
36+
end
37+
38+
def add_block(ref, start, length)
39+
commit = (@commit_cache[ref] ||= @base.object(ref))
40+
length.times { |i| @lines[start + i] = commit }
41+
commit
42+
end
43+
44+
attr_reader :lines
45+
private :lines
46+
end

lib/git/lib.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,31 @@ def full_log_commits(opts = {})
9292
full_log = command_lines('log', arr_opts, true)
9393
process_commit_data(full_log)
9494
end
95+
96+
def blame(file, opts={})
97+
arr_opts = %w( --incremental )
98+
arr_opts << "-L#{opts[:start]},#{opts[:end]}" if opts[:start] && opts[:end]
99+
arr_opts << '--reverse' if opts[:reverse]
100+
case opts[:detect_intrafile_moves]
101+
when true then arr_opts << '-M'
102+
when Fixnum then arr_opts << "-M#{opts[:detect_intrafile_moves]}"
103+
end
104+
if opts[:detect_interfile_moves]
105+
c_opt = if opts[:check_all_commits_for_interfile_moves]
106+
'-CCC'
107+
elsif opts[:check_first_file_commit_for_interfile_moves]
108+
'-CC'
109+
else
110+
'-C'
111+
end
112+
c_opt << opts[:detect_interfile_moves] if opts[:detect_interfile_moves].kind_of?(Fixnum)
113+
end
114+
arr_opts << '-w' if opts[:ignore_whitespace]
115+
arr_opts << opts[:revision] if opts[:revision]
116+
arr_opts << '--' << file
117+
118+
command_lines 'blame', arr_opts
119+
end
95120

96121
def revparse(string)
97122
return string if string =~ /[A-Fa-f0-9]{40}/ # passing in a sha - just no-op it

0 commit comments

Comments
 (0)