File tree 4 files changed +78
-0
lines changed
4 files changed +78
-0
lines changed Original file line number Diff line number Diff line change 13
13
require 'git/working_directory'
14
14
15
15
require 'git/log'
16
+ require 'git/blame'
16
17
require 'git/object'
17
18
18
19
require 'git/branches'
Original file line number Diff line number Diff line change @@ -170,6 +170,12 @@ def log(count = 30)
170
170
Git ::Log . new ( self , count )
171
171
end
172
172
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
+
173
179
# returns a Git::Status object
174
180
def status
175
181
Git ::Status . new ( self )
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change @@ -92,6 +92,31 @@ def full_log_commits(opts = {})
92
92
full_log = command_lines ( 'log' , arr_opts , true )
93
93
process_commit_data ( full_log )
94
94
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
95
120
96
121
def revparse ( string )
97
122
return string if string =~ /[A-Fa-f0-9]{40}/ # passing in a sha - just no-op it
You can’t perform that action at this time.
0 commit comments