diff --git a/lib/git.rb b/lib/git.rb index acf7d1b3..c4a09846 100644 --- a/lib/git.rb +++ b/lib/git.rb @@ -13,6 +13,7 @@ require 'git/working_directory' require 'git/log' +require 'git/blame' require 'git/object' require 'git/branches' diff --git a/lib/git/base.rb b/lib/git/base.rb index 5ad8906a..c589c9d7 100644 --- a/lib/git/base.rb +++ b/lib/git/base.rb @@ -150,7 +150,10 @@ def config(name = nil, value = nil) # on the objectish and determine the type of the object and return # an appropriate object for that type def object(objectish) + return nil unless objectish Git::Object.new(self, objectish) + rescue Git::GitExecuteError + return nil # unknown revision end def gtree(objectish) @@ -170,6 +173,12 @@ def log(count = 30) Git::Log.new(self, count) end + # returns a Git::BlameResult object with the commits that most recently + # modified each line + def blame(file, opts={}) + Git::Blame.new(self, file, opts).result + end + # returns a Git::Status object def status Git::Status.new(self) diff --git a/lib/git/blame.rb b/lib/git/blame.rb new file mode 100644 index 00000000..3651fdf3 --- /dev/null +++ b/lib/git/blame.rb @@ -0,0 +1,46 @@ +class Git::Blame + attr_reader :result + + def initialize(base, file, opts={}) + @base = base + @file = file + @options = opts + + @result = run_blame + end + + private + + def run_blame + lines = @base.lib.blame(@file, @options) + result = Git::BlameResult.new(@base) + lines.each do |line| + words = line.split(/\s+/) + next unless words.first.length == 40 #TODO this breaks if git ever introduces a 40-character attribute name + ref = words[0] + block_start = words[2].to_i + block_length = words[3].to_i + result.add_block ref, block_start, block_length + end + result + end +end + +class Git::BlameResult + delegate :[], :each, :each_with_index, to: :lines + + def initialize(base) + @base = base + @commit_cache = {} + @lines = [] + end + + def add_block(ref, start, length) + commit = (@commit_cache[ref] ||= @base.object(ref)) + length.times { |i| @lines[start + i] = commit } + commit + end + + attr_reader :lines + private :lines +end diff --git a/lib/git/lib.rb b/lib/git/lib.rb index 52fb2e6c..433b253b 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -47,6 +47,7 @@ def clone(repository, name, opts = {}) arr_opts = [] arr_opts << "--bare" if opts[:bare] + arr_opts << "--mirror" if opts[:mirror] arr_opts << "-o" << opts[:remote] if opts[:remote] arr_opts << "--depth" << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0 @@ -56,7 +57,7 @@ def clone(repository, name, opts = {}) command('clone', arr_opts) - opts[:bare] ? {:repository => clone_dir} : {:working_directory => clone_dir} + (opts[:bare] || opts[:mirror]) ? {:repository => clone_dir} : {:working_directory => clone_dir} end @@ -92,6 +93,31 @@ def full_log_commits(opts = {}) full_log = command_lines('log', arr_opts, true) process_commit_data(full_log) end + + def blame(file, opts={}) + arr_opts = %w( --incremental ) + arr_opts << "-L#{opts[:start]},#{opts[:end]}" if opts[:start] && opts[:end] + arr_opts << '--reverse' if opts[:reverse] + case opts[:detect_intrafile_moves] + when true then arr_opts << '-M' + when Fixnum then arr_opts << "-M#{opts[:detect_intrafile_moves]}" + end + if opts[:detect_interfile_moves] + c_opt = if opts[:check_all_commits_for_interfile_moves] + '-CCC' + elsif opts[:check_first_file_commit_for_interfile_moves] + '-CC' + else + '-C' + end + c_opt << opts[:detect_interfile_moves] if opts[:detect_interfile_moves].kind_of?(Fixnum) + end + arr_opts << '-w' if opts[:ignore_whitespace] + arr_opts << opts[:revision] if opts[:revision] + arr_opts << '--' << file + + command_lines 'blame', arr_opts + end def revparse(string) return string if string =~ /[A-Fa-f0-9]{40}/ # passing in a sha - just no-op it @@ -168,7 +194,7 @@ def object_contents(sha, &block) end def ls_tree(sha) - data = {'blob' => {}, 'tree' => {}} + data = {'blob' => {}, 'tree' => {}, 'commit' => {}, 'tag' => {}} command_lines('ls-tree', sha).each do |line| (info, filenm) = line.split("\t") diff --git a/lib/git/path.rb b/lib/git/path.rb index 87f5c84e..f81f6f5b 100644 --- a/lib/git/path.rb +++ b/lib/git/path.rb @@ -7,7 +7,7 @@ def initialize(path, check_path = true) if !check_path || File.exists?(path) @path = File.expand_path(path) else - raise ArgumentError, "path does not exist", File.expand_path(path) + raise ArgumentError, "path does not exist: #{File.expand_path(path)}" end end @@ -24,4 +24,4 @@ def to_s end end -end \ No newline at end of file +end