From 01476fffba362826cf7f39f3d3af56fd3c3dc625 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Fri, 11 May 2012 15:34:16 -0700 Subject: [PATCH 1/5] Added git-blame command --- lib/git.rb | 1 + lib/git/base.rb | 6 ++++++ lib/git/blame.rb | 46 ++++++++++++++++++++++++++++++++++++++++++++++ lib/git/lib.rb | 25 +++++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 lib/git/blame.rb 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..75abe4a3 100644 --- a/lib/git/base.rb +++ b/lib/git/base.rb @@ -170,6 +170,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..d1145e22 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -92,6 +92,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 From 0e64b62c8bd80cd4b3d3abaa3a44eaf18f3e3e2c Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Mon, 14 May 2012 10:52:13 -0700 Subject: [PATCH 2/5] Return nil if the objectish doesn't exist --- lib/git/base.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/git/base.rb b/lib/git/base.rb index 75abe4a3..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) From da66aa81ff9a8d0654891dd28227419d8132d983 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Mon, 14 May 2012 14:45:54 -0700 Subject: [PATCH 3/5] Add --mirror option to git-clone --- lib/git/lib.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/git/lib.rb b/lib/git/lib.rb index d1145e22..41184452 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 From e6a872b87743f528a0e0332514dd1d141146d963 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Mon, 14 May 2012 15:29:30 -0700 Subject: [PATCH 4/5] Fix for JRuby --- lib/git/path.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 473d8ecc1fd49cbfcbabff0061484319d4bac9fa Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Thu, 21 Jun 2012 11:03:55 -0700 Subject: [PATCH 5/5] I guess trees can have commits in them too --- lib/git/lib.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git/lib.rb b/lib/git/lib.rb index 41184452..433b253b 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -194,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")