From 34ff09af00f46741d8202dde21ca202bf3a4e21a Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Sat, 14 Mar 2020 17:32:27 +0100 Subject: [PATCH] Implement ApplyPatchToIndex Class (#50) * Implement ApplyPatchToIndex Class --- Gemfile | 12 +-- lib/commit.rb | 4 +- lib/git.rb | 8 +- lib/rjgit.rb | 222 ++++++++++++++++++++++++++++++++------------- spec/rjgit_spec.rb | 199 +++++++++++++++++++++++++++++----------- 5 files changed, 323 insertions(+), 122 deletions(-) diff --git a/Gemfile b/Gemfile index cb2e097..d5b69bd 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,12 @@ source 'https://rubygems.org' -gem "mime-types", "~> 2.6.2" -gem "rake", "~> 10.4.2" +gem 'mime-types', '~> 2.6.2' +gem 'rake', '>= 12.3.3' -gem 'coveralls', require: false +gem 'coveralls', '~> 0.8.23', require: false group :test do - gem "rspec", "~> 3.4.0" - gem "rspec-collection_matchers", "~> 1.1.2" - gem "simplecov" + gem 'rspec', '~> 3.4.0' + gem 'rspec-collection_matchers', '~> 1.1.2' + gem 'simplecov' end diff --git a/lib/commit.rb b/lib/commit.rb index 48ff1cd..20f40fb 100644 --- a/lib/commit.rb +++ b/lib/commit.rb @@ -100,12 +100,12 @@ def self.new_with_tree(repository, tree, message, actor, parents = nil) Commit.new(repository, RevWalk.new(repository).parseCommit(new_commit)) end - def self.find_head(repository) + def self.find_head(repository, ref = Constants::HEAD) repository = RJGit.repository_type(repository) return nil if repository.nil? begin walk = RevWalk.new(repository) - objhead = repository.resolve(Constants::HEAD) + objhead = repository.resolve(ref) return Commit.new(repository, walk.parseCommit(objhead)) rescue java.lang.NullPointerException => e return nil diff --git a/lib/git.rb b/lib/git.rb index ac8b406..5b2eec0 100644 --- a/lib/git.rb +++ b/lib/git.rb @@ -15,6 +15,8 @@ module RJGit import 'org.eclipse.jgit.api.TransportConfigCallback' import 'org.eclipse.jgit.transport.JschConfigSessionFactory' import 'org.eclipse.jgit.transport.SshTransport' + + class PatchApplyException < StandardError; end class RubyGit @@ -289,7 +291,11 @@ def checkout(branch_name = "master", options = {}) end def apply(input_stream) - apply_result = @jgit.apply.set_patch(input_stream).call + begin + apply_result = @jgit.apply.set_patch(input_stream).call + rescue Java::OrgEclipseJgitApiErrors::PatchApplyException + raise RJGit::PatchApplyException + end updated_files = apply_result.get_updated_files updated_files_parsed = [] updated_files.each do |file| diff --git a/lib/rjgit.rb b/lib/rjgit.rb index 5f08d90..e0fc7dc 100644 --- a/lib/rjgit.rb +++ b/lib/rjgit.rb @@ -1,5 +1,4 @@ module RJGit - begin require 'java' Dir["#{File.dirname(__FILE__)}/java/jars/*.jar"].each { |jar| require jar } @@ -10,7 +9,7 @@ module RJGit def self.version VERSION end - + require 'uri' require 'stringio' # gem requires @@ -19,15 +18,16 @@ def self.version require "#{File.dirname(__FILE__)}/rjgit_helpers.rb" # require everything else begin - Dir["#{File.dirname(__FILE__)}/*.rb"].each do |file| + Dir["#{File.dirname(__FILE__)}/*.rb"].each do |file| require file end end - + import 'org.eclipse.jgit.lib.ObjectId' - + module Porcelain - + + import 'org.eclipse.jgit.lib.Constants' import 'org.eclipse.jgit.api.AddCommand' import 'org.eclipse.jgit.api.CommitCommand' import 'org.eclipse.jgit.api.BlameCommand' @@ -36,20 +36,19 @@ module Porcelain import 'org.eclipse.jgit.treewalk.CanonicalTreeParser' import 'org.eclipse.jgit.diff.DiffFormatter' - # http://wiki.eclipse.org/JGit/User_Guide#Porcelain_API def self.add(repository, file_pattern) repository.add(file_pattern) end - + def self.commit(repository, message="") repository.commit(message) end - + def self.object_for_tag(repository, tag) repository.find(tag.object.name, RJGit.sym_for_type(tag.object_type)) end - + # http://dev.eclipse.org/mhonarc/lists/jgit-dev/msg00558.html def self.cat_file(repository, blob) jrepo = RJGit.repository_type(repository) @@ -65,7 +64,7 @@ def self.cat_file(repository, blob) bytes = jrepo.open(jblob.id).get_bytes return bytes.to_a.pack('c*').force_encoding('UTF-8') end - + def self.ls_tree(repository, path=nil, treeish=Constants::HEAD, options={}) options = {recursive: false, print: false, io: $stdout, path_filter: nil}.merge options jrepo = RJGit.repository_type(repository) @@ -95,7 +94,7 @@ def self.ls_tree(repository, path=nil, treeish=Constants::HEAD, options={}) treewalk.set_recursive(options[:recursive]) treewalk.set_filter(PathFilter.create(options[:path_filter])) if options[:path_filter] entries = [] - + while treewalk.next entry = {} mode = treewalk.get_file_mode(0) @@ -107,8 +106,8 @@ def self.ls_tree(repository, path=nil, treeish=Constants::HEAD, options={}) end options[:io].puts RJGit.stringify(entries) if options[:print] entries - end - + end + def self.blame(repository, file_path, options={}) options = {:print => false, :io => $stdout}.merge(options) jrepo = RJGit.repository_type(repository) @@ -130,28 +129,22 @@ def self.blame(repository, file_path, options={}) options[:io].puts RJGit.stringify(blame) if options[:print] return blame end - + def self.diff(repository, options = {}) options = {:namestatus => false, :patch => false}.merge(options) repo = RJGit.repository_type(repository) git = RubyGit.new(repo).jgit diff_command = git.diff - if options[:old_rev] + [:old, :new].each do |which_rev| + if rev = options["#{which_rev}_rev".to_sym] reader = repo.new_object_reader - old_tree = repo.resolve("#{options[:old_rev]}^{tree}") - old_tree_iter = CanonicalTreeParser.new - old_tree_iter.reset(reader, old_tree) - diff_command.set_old_tree(old_tree_iter) - end - if options[:new_rev] - reader = repo.new_object_reader unless reader - new_tree = repo.resolve("#{options[:new_rev]}^{tree}") - new_tree_iter = CanonicalTreeParser.new - new_tree_iter.reset(reader, new_tree) - diff_command.set_new_tree(new_tree_iter) + parser = CanonicalTreeParser.new + parser.reset(reader, repo.resolve("#{rev}^{tree}")) + diff_command.send("set_#{which_rev}_tree".to_sym, parser) end + end diff_command.set_path_filter(PathFilter.create(options[:file_path])) if options[:file_path] - diff_command.set_show_name_and_status_only(true) if options[:namestatus] + diff_command.set_show_name_and_status_only(true) if options[:namestatus] diff_command.set_cached(true) if options[:cached] diff_entries = diff_command.call diff_entries = diff_entries.to_array.to_ary @@ -169,34 +162,34 @@ def self.diff(repository, options = {}) diff_entries = options[:patch] ? result : diff_entries.map {|entry| [entry]} RJGit.convert_diff_entries(diff_entries) end - + end - + module Plumbing import org.eclipse.jgit.lib.Constants - + class TreeBuilder import org.eclipse.jgit.lib.FileMode import org.eclipse.jgit.lib.TreeFormatter - - + import org.eclipse.jgit.patch.Patch + attr_accessor :treemap attr_reader :log - + def initialize(repository) @jrepo = RJGit.repository_type(repository) @treemap = {} init_log end - + def object_inserter @object_inserter ||= @jrepo.newObjectInserter end - + def init_log @log = {:deleted => [], :added => [] } end - + def only_contains_deletions(hashmap) hashmap.each do |key, value| if value.is_a?(Hash) @@ -207,7 +200,7 @@ def only_contains_deletions(hashmap) end true end - + def build_tree(start_tree, treemap = nil, flush = false) existing_trees = {} untouched_objects = {} @@ -232,7 +225,7 @@ def build_tree(start_tree, treemap = nil, flush = false) end end end - + sorted_treemap = treemap.inject({}) {|h, (k,v)| v.is_a?(Hash) ? h["#{k}/"] = v : h[k] = v; h }.merge(untouched_objects).sort sorted_treemap.each do |object_name, data| case data @@ -252,27 +245,27 @@ def build_tree(start_tree, treemap = nil, flush = false) end object_inserter.insert(formatter) end - + def write_blob(contents, flush = false) blobid = object_inserter.insert(Constants::OBJ_BLOB, contents.to_java_bytes) object_inserter.flush if flush blobid end - + end - + class Index import org.eclipse.jgit.lib.CommitBuilder - + attr_accessor :treemap, :current_tree attr_reader :jrepo - + def initialize(repository) @treemap = {} @jrepo = RJGit.repository_type(repository) @treebuilder = TreeBuilder.new(@jrepo) end - + def add(path, data) path = path[1..-1] if path[0] == '/' path = path.split('/') @@ -289,24 +282,24 @@ def add(path, data) current[filename] = data @treemap end - + def delete(path) path = path[1..-1] if path[0] == '/' path = path.split('/') last = path.pop - + current = self.treemap - + path.each do |dir| current[dir] ||= {} node = current[dir] current = node end - + current[last] = false @treemap end - + def do_commit(message, author, parents, new_tree) commit_builder = CommitBuilder.new person = author.person_ident @@ -323,14 +316,11 @@ def do_commit(message, author, parents, new_tree) @treebuilder.object_inserter.flush result end - - def commit(message, author, parents = nil, ref = nil, force = false) - ref = ref ? ref : "refs/heads/#{Constants::MASTER}" - @current_tree = @current_tree ? RJGit.tree_type(@current_tree) : @jrepo.resolve("refs/heads/#{Constants::MASTER}^{tree}") - @treebuilder.treemap = @treemap - new_tree = @treebuilder.build_tree(@current_tree) + + def commit(message, author, parents = nil, ref = "refs/heads/#{Constants::MASTER}", force = false) + new_tree = build_new_tree(@treemap, "#{ref}^{tree}") return false if @current_tree && new_tree.name == @current_tree.name - + parents = parents ? parents : @jrepo.resolve(ref+"^{commit}") new_head = do_commit(message, author, parents, new_tree) @@ -341,21 +331,129 @@ def commit(message, author, parents = nil, ref = nil, force = false) ru.setRefLogIdent(author.person_ident) ru.setRefLogMessage("commit: #{message}", false) res = ru.update.to_string - - # @treebuilder.object_inserter.release + @current_tree = new_tree log = @treebuilder.log @treebuilder.init_log sha = ObjectId.to_string(new_head) return res, log, sha end - + def self.successful?(result) ["NEW", "FAST_FORWARD", "FORCED"].include?(result) end + private + + def build_new_tree(treemap, ref) + @treebuilder.treemap = treemap + new_tree = @treebuilder.build_tree(@current_tree ? RJGit.tree_type(@current_tree) : @jrepo.resolve(ref)) + end + + end + + class ApplyPatchToIndex < RJGit::Plumbing::Index + + import org.eclipse.jgit.patch.Patch + import org.eclipse.jgit.diff.DiffEntry + + ADD = org.eclipse.jgit.diff.DiffEntry::ChangeType::ADD + COPY = org.eclipse.jgit.diff.DiffEntry::ChangeType::COPY + MODIFY = org.eclipse.jgit.diff.DiffEntry::ChangeType::MODIFY + DELETE = org.eclipse.jgit.diff.DiffEntry::ChangeType::DELETE + RENAME = org.eclipse.jgit.diff.DiffEntry::ChangeType::RENAME + + # Take the result of RJGit::Porcelain.diff with options[:patch] = true and return a patch String + def self.diffs_to_patch(diffs) + diffs.inject(""){|result, diff| result << diff[:patch]} + end + + def initialize(repository, patch, ref = Constants::HEAD) + super(repository) + @ref = ref + @patch = Patch.new + @patch.parse(ByteArrayInputStream.new(patch.to_java_bytes)) + raise_patch_apply_error unless @patch.getErrors.isEmpty() + @current_tree = Commit.find_head(@jrepo, ref).tree + end + + def commit(message, author, parents = nil, force = false) + super(message, author, parents, @ref, force) + end + + def build_map + raise_patch_apply_error if @patch.getFiles.isEmpty() + @patch.getFiles.each do |file_header| + case file_header.getChangeType + when ADD + add(file_header.getNewPath, apply('', file_header)) + when MODIFY + add(file_header.getOldPath, apply(getData(file_header.getOldPath), file_header)) + when DELETE + delete(file_header.getOldPath) + when RENAME + delete(file_header.getOldPath) + add(file_header.getNewPath, getData(file_header.getOldPath)) + when COPY + add(file_header.getNewPath, getData(file_header.getOldPath)) + end + end + @treemap + end + + # Build the new tree based on the patch, but don't commit it + # Return the String object id of the new tree, and an Array of affected paths + def new_tree + map = build_map + return ObjectId.to_string(build_new_tree(map, @ref)), map.keys + end + + private + + def raise_patch_apply_error + raise ::RJGit::PatchApplyException.new('Patch failed to apply') + end + + def getData(path) + begin + (@current_tree / path).data + rescue NoMethodError + raise_patch_apply_error + end + end + + def hunk_sanity_check(hunk, hunk_line, pos, newLines) + raise_patch_apply_error unless newLines[hunk.getNewStartLine - 1 + pos] == hunk_line[1..-1] + end + + def apply(original, file_header) + newLines = original.lines + file_header.getHunks.each do |hunk| + length = hunk.getEndOffset - hunk.getStartOffset + buffer_text = hunk.getBuffer.to_s.slice(hunk.getStartOffset, length) + pos = 0 + buffer_text.each_line do |hunk_line| + case hunk_line[0] + when ' ' + hunk_sanity_check(hunk, hunk_line, pos, newLines) + pos += 1 + when '-' + if hunk.getNewStartLine == 0 + newLines = [] + else + hunk_sanity_check(hunk, hunk_line, pos, newLines) + newLines.slice!(hunk.getNewStartLine - 1 + pos) + end + when '+' + newLines.insert(hunk.getNewStartLine - 1 + pos, hunk_line[1..-1]) + pos += 1 + end + end + end + newLines.join + end end - + end - + end diff --git a/spec/rjgit_spec.rb b/spec/rjgit_spec.rb index dbc2205..17e74b0 100644 --- a/spec/rjgit_spec.rb +++ b/spec/rjgit_spec.rb @@ -7,21 +7,21 @@ @bare_repo = Repo.new(TEST_BARE_REPO_PATH) @git = RubyGit.new(@bare_repo.jrepo) end - + it "has a version" do expect(RJGit.version).to eq(RJGit::VERSION) end - + context "delegating missing methods to the underlying jgit Git object" do it "delegates the method to the JGit object" do expect(@git.send(:rebase)).to be_a org.eclipse.jgit.api.RebaseCommand # :rebase method not implemented in RubyGit, but is implemented in the underlying JGit object end - + it "throws an exception if the JGit object does not know the method" do expect { @git.send(:non_existent_method) }.to raise_error(NoMethodError) end end - + describe Porcelain do before(:all) do @temp_repo_path = create_temp_repo(TEST_REPO_PATH) @@ -29,22 +29,22 @@ @testfile = 'test_file.txt' File.open(File.join(@temp_repo_path, @testfile), 'w') {|file| file.write("This is a new file to add.") } end - + it "looks up the object belonging to a tag" do @repo.git.tag('v0.0', 'initial state commit for a specific commit', @repo.head.jcommit) expect(RJGit::Porcelain.object_for_tag(@repo, @repo.tags.first.last)).to be_kind_of Commit end - + it "mimics git-cat-file" do blob = @bare_repo.blob('lib/grit.rb') expect(RJGit::Porcelain.cat_file(@bare_repo, blob.jblob)).to match /# core\n/ end - + it "adds files to a repository" do Porcelain.add(@repo, @testfile) expect(@repo.jrepo.read_dir_cache.find_entry(@testfile).size).to eq 8 end - + it "commits files to a repository" do message = "Initial commit" Porcelain.commit(@repo, message) @@ -52,7 +52,7 @@ end context "listing trees" do - + it "mimics git-ls-tree" do listing = RJGit::Porcelain.ls_tree(@bare_repo.jrepo) expect(listing).to be_an Array @@ -63,7 +63,7 @@ expect(first_entry[:id]).to match /baaa47163a922b716898936f4ab032db4e08ae8a/ expect(first_entry[:path]).to eq '.gitignore' end - + it "mimics git-ls-tree for a specific path" do listing = RJGit::Porcelain.ls_tree(@bare_repo.jrepo, 'lib', Constants::HEAD, {recursive: false}) first_entry = listing.first @@ -72,7 +72,7 @@ first_entry = listing.first expect(first_entry[:path]).to eq 'lib/grit/actor.rb' end - + it "mimics git-ls-tree for a specific commit" do sha = '8bfefdbc0d901a6e8ccd27b9f20879d109f49c03' listing = RJGit::Porcelain.ls_tree(@bare_repo.jrepo, nil, sha, {recursive: false}) @@ -83,13 +83,13 @@ listing = RJGit::Porcelain.ls_tree(@bare_repo.jrepo, nil, Constants::HEAD, {recursive: true}) expect(listing.length).to eq 539 end - + it "mimics git-ls-tree recursively for a specific path" do listing = RJGit::Porcelain.ls_tree(@bare_repo.jrepo, 'lib/grit/git-ruby', Constants::HEAD, {recursive: true}) first_entry = listing.first expect(first_entry[:path]).to eq 'lib/grit/git-ruby/internal/loose.rb' end - + it "mimics git-ls-tree for a specific treeish object" do commit = @bare_repo.commits.last listing = RJGit::Porcelain.ls_tree(@bare_repo.jrepo, 'lib/grit', commit, {recursive: false}) @@ -111,12 +111,12 @@ end end - + it "mimics git-blame" do RJGit::Porcelain.blame(@bare_repo, 'lib/grit.rb') skip end - + context "producing diffs" do before(:each) do @temp_repo_path = create_temp_repo(TEST_REPO_PATH) @@ -144,7 +144,7 @@ end it "returns a patch for a diff entry with optional formatting" - + it "returns cached diff when adding file" do entry = RJGit::Porcelain.diff(@repo, {cached: true}).first expect(entry).to be_a Hash @@ -153,8 +153,8 @@ @repo.commit("Committing a test file to a test repository.") expect(RJGit::Porcelain.diff(@repo)).to eq [] end - - it "returns cached diff when removing file" do + + it "returns cached diff when removing file" do @repo.commit("Adding rspec-addfile.txt so it can be deleted.") @repo.remove("rspec-addfile.txt") entry = RJGit::Porcelain.diff(@repo, {cached: true}).first @@ -163,22 +163,22 @@ @repo.commit("Removing test file.") expect(RJGit::Porcelain.diff(@repo)).to eq [] end - + after(:each) do @repo = nil remove_temp_repo(@temp_repo_path) - end + end end - + after(:all) do @repo = nil remove_temp_repo(@temp_repo_path) end - + end # end Porcelain - + describe Plumbing do - + describe RJGit::Plumbing::Index do before(:all) do @temp_repo_path = get_new_temp_repo_path(true) @@ -187,33 +187,33 @@ @msg = "Message" @auth = RJGit::Actor.new("test", "test@repotag.org") end - + it "has a treemap" do expect(@index.treemap).to be_kind_of Hash end - + it "adds blobs to the treemap" do @index.add("test", "Test") expect(@index.treemap["test"]). to eq "Test" end - + it "adds trees to the treemap" do @index.add("tree/blob", "Test") expect(@index.treemap["tree"]).to eq({"blob" => "Test"}) end - + it "adds items to delete to the treemap" do @index.delete("tree/blob") expect(@index.treemap["tree"]["blob"]).to be false end - + it "adds commits to an empty repository" do res, log = @index.commit(@msg, @auth) expect(res).to eq "NEW" expect(@repo.blob("test").data).to eq "Test" expect(@repo.commits.first.parents).to be_empty end - + it "adds commits with a parent commit" do @index.add("tree/blob", "Test") res, log = @index.commit(@msg, @auth) @@ -221,27 +221,27 @@ expect(@repo.blob("tree/blob").data).to eq "Test" expect(@repo.commits.first.parents).to_not be_empty end - + it "returns log information after commit" do @index.add("tree/blob2", "Tester") res, log = @index.commit(@msg, @auth) expect(log[:added].select {|x| x.include?("tree")}.first).to include(:tree) end - + it "commits to a non-default branch" do msg = "Branch test" @index.add("tree/blob3", "More testing") res, log = @index.commit(msg, @auth, nil, "refs/heads/newbranch") expect(@repo.commits("newbranch").first.message).to eq msg end - + it "allows setting multiple parents for a commit" do @index.delete("tree/blob2") parents = [@repo.commits.first, @repo.commits.last] res, log = @index.commit(@msg, @auth, parents) expect(@repo.commits.first.parents.length).to eq 2 end - + it "allows setting the departure tree when building a new commit" do @index.add("newtree/blobinnewtree", "contents") res, log = @index.commit(@msg, @auth) @@ -253,21 +253,21 @@ expect(@repo.blob("secondblob").data).to eq "other contents" @index.current_tree = nil end - + it "tells whether a response code indicates a successful response" do ["NEW", "FAST_FORWARD"].each do |s| expect(RJGit::Plumbing::Index.successful?(s)).to be true end expect(RJGit::Plumbing::Index.successful?("FAILED")).to be false end - + after(:all) do remove_temp_repo(@temp_repo_path) @repo = nil end - + end - + describe RJGit::Plumbing::TreeBuilder do before(:all) do @temp_repo_path = get_new_temp_repo_path(true) @@ -279,18 +279,18 @@ @index.commit(@msg, @auth) @tb = RJGit::Plumbing::TreeBuilder.new(@repo) end - + it "initializes with the right defaults" do expect(@tb.object_inserter).to be_kind_of org.eclipse.jgit.lib.ObjectInserter expect(@tb.treemap).to eq({}) expect(@tb.log).to eq({deleted: [], added: [] }) end - + it "adds and deletes objects to a tree" do @tb.treemap = {"newtest/bla" => "test"} tree = @tb.build_tree(@repo.jrepo.resolve("refs/heads/master^{tree}")) expect(tree).to be_kind_of org.eclipse.jgit.lib.ObjectId - + treewalk = TreeWalk.new(@repo.jrepo) treewalk.add_tree(tree) treewalk.set_recursive(true) @@ -299,13 +299,13 @@ objects << treewalk.get_path_string end expect(objects).to include("newtest/bla") - + @index.add('newtest/bla', 'contents') @index.commit(@msg, @auth) - + @tb.treemap = {"newtest" => false} tree = @tb.build_tree(@repo.jrepo.resolve("refs/heads/master^{tree}")) - + treewalk = TreeWalk.new(@repo.jrepo) treewalk.add_tree(tree) treewalk.set_recursive(true) @@ -315,7 +315,7 @@ end expect(objects).to_not include("newtest/bla") end - + it "logs information about added and deleted objects" do @tb.init_log @tb.treemap = {"newtest" => "test"} @@ -327,7 +327,7 @@ expect(@tb.log[:deleted].first).to include(:blob) expect(@tb.log[:deleted].first).to include("newtest") end - + it "does not log information about trees that contain no added objects" do @tb.treemap = {"newtree/test/newblob" => "test"} tree = @tb.build_tree(@repo.jrepo.resolve("refs/heads/master^{tree}")) @@ -336,21 +336,118 @@ @tb.build_tree(tree) expect(@tb.log[:added]).to be_empty end - + it "tells whether a given hashmap contains no added blobs" do expect(@tb.only_contains_deletions({'test' => {'test' => false}})).to be true expect(@tb.only_contains_deletions({'test' => {'test' => false, 'test2' => 'content'}})).to be false end - + after(:all) do remove_temp_repo(@temp_repo_path) @repo = nil end + + end + + describe RJGit::Plumbing::ApplyPatchToIndex do + before(:each) do + @temp_repo_path = create_temp_repo(TEST_BARE_REPO_PATH, true) + @repo = Repo.new(@temp_repo_path) + @diffs = RJGit::Porcelain.diff(@repo, patch: true, new_rev: @repo.commits[20].id, old_rev: @repo.commits[0].id) + @msg = 'Message' + @auth = RJGit::Actor.new('test', 'test@repotag.org') + end + + after(:each) do + remove_temp_repo(@temp_repo_path) + @repo = nil + end + + it 'converts diff entries to a patch' do + result = RJGit::Plumbing::ApplyPatchToIndex.diffs_to_patch(@diffs) + expect(result).to be_a String + expect(result.split("\n").first).to eq 'diff --git a/.gitignore b/.gitignore' + end + + it 'applies a patch and commits the result' do + head_sha = @repo.head.id + expect(head_sha).to eq 'ca8a30f5a7f0f163bbe3b6f0abf18a6c83b0687a' + patch = RJGit::Plumbing::ApplyPatchToIndex.diffs_to_patch(@diffs) + applier = RJGit::Plumbing::ApplyPatchToIndex.new(@repo, patch) + applier.build_map + result, log, sha = applier.commit(@msg, @auth, nil, true) # message, actor, don't specify parents (auto resolves), force update of HEAD + expect(result).to eq 'FORCED' + expect(sha).not_to eq head_sha + expect(@repo.head.id).to eq sha + end + it 'applies a patch and returns the new tree' do + patch = RJGit::Plumbing::ApplyPatchToIndex.diffs_to_patch(@diffs) + applier = RJGit::Plumbing::ApplyPatchToIndex.new(@repo, patch) + id, paths = applier.new_tree + expect(id).to be_a String + expect(paths).to be_a Array + end + + it 'applies patches for new files and renames' do + add = < :blob, Constants::OBJ_COMMIT => :commit, Constants::OBJ_TREE => :tree, Constants::OBJ_TAG => :tag }.each {|k,v| expect(RJGit.sym_for_type(k)).to eq v}} end - + after(:all) do @bare_repo = nil end