From 2e937ac5d5a6e95f4abb9f636273eaa6528f5dae Mon Sep 17 00:00:00 2001 From: peterwald Date: Mon, 16 Feb 2009 20:45:29 -0700 Subject: [PATCH 1/6] bump version to get gem to rebuild. --- ruby-git.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby-git.gemspec b/ruby-git.gemspec index e68d909b..30615708 100644 --- a/ruby-git.gemspec +++ b/ruby-git.gemspec @@ -1,7 +1,7 @@ spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = "git" - s.version = "1.1.1" + s.version = "1.1.2" s.author = "Scott Chacon" s.email = "schacon@gmail.com" s.summary = "A package for using Git in Ruby code." From ad8603892a53da146eec2ec4d3f38622b8f49259 Mon Sep 17 00:00:00 2001 From: Peter Waldschmidt Date: Wed, 18 Feb 2009 22:08:10 -0500 Subject: [PATCH 2/6] Added fast-import streaming functionality (stream.rb). Just basic support at this point. Updated the author class to support direct creation in addition to parsing. --- lib/git.rb | 2 + lib/git/author.rb | 5 ++ lib/git/stream.rb | 149 +++++++++++++++++++++++++++++++++++++ tests/units/test_stream.rb | 112 ++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+) create mode 100644 lib/git/stream.rb create mode 100644 tests/units/test_stream.rb diff --git a/lib/git.rb b/lib/git.rb index 9dcc79ce..2dd91752 100644 --- a/lib/git.rb +++ b/lib/git.rb @@ -26,6 +26,8 @@ require 'git/stashes' require 'git/stash' +require 'git/stream' + # Git/Ruby Library # diff --git a/lib/git/author.rb b/lib/git/author.rb index 545abb9b..07880533 100644 --- a/lib/git/author.rb +++ b/lib/git/author.rb @@ -10,5 +10,10 @@ def initialize(author_string) end end + def initialize(name, email, date = Time.now) + @name = name + @email = email + @date = date + end end end \ No newline at end of file diff --git a/lib/git/stream.rb b/lib/git/stream.rb new file mode 100644 index 00000000..2f9ff66a --- /dev/null +++ b/lib/git/stream.rb @@ -0,0 +1,149 @@ +module Git + + require 'time' + + #==================== + # Stream Classes + # + # These classes must support the following methods + # + # to_s - write the command to the git protocol stream + #==================== + + class StreamCommit + + + attr_accessor :branch, :mark, :author, :committer, :message, :ancestor, :changes + + def initialize() + @branch = nil + @mark = StreamMark.new + @author = nil + @committer = nil + @message = nil + @ancestor = nil + @changes = [] + end + + def to_s + out = "commit refs/heads/#{branch.to_s}\n" + out << "mark #{mark}\n" + out << "author #{author.name} <#{author.email}> #{author.date.rfc2822}\n" unless author == nil + out << "committer #{committer.name} <#{committer.email}> #{committer.date.rfc2822}\n" unless committer == nil + if (message == nil) + out << StreamData.emit_empty_data + else + out << StreamData.emit_inline_data(message) + end + out << "from #{ancestor}\n" unless ancestor == nil + changes.each do |c| + out << c.to_s + end + out << "\n" + end + end + + class StreamMark + + @@mark_counter = 1 + + def initialize(id = (@@mark_counter += 1)) + @id = id + end + + def to_s + ":#{@id}" + end + end + + # This class is used in the filemodify change on the commit stream + # At this time only the inline mode data stream is supported + class StreamFileModify + + attr_accessor :mode, :repository_path, :inline_data + + def initialize(repository_path, data) + @mode = 100644 + @repository_path = repository_path + @inline_data = data + end + + def to_s + "M #{mode} inline #{repository_path}\n#{StreamData.emit_inline_data(inline_data)}" + end + end + + class StreamFileDelete + + attr_accessor :repository_path + + def initialize(repository_path) + @repository_path = repository_path + end + + def to_s + "D #{repository_path}\n" + end + end + + class StreamFileCopy + + attr_accessor :repository_path_from, :repository_path_to + + def initialize(repository_path_from, repository_path_to) + @repository_path_from = repository_path_from + @repository_path_to = repository_path_to + end + + def to_s + "C #{repository_path_from} #{repository_path_to}\n" + end + + end + + class StreamFileRename + + attr_accessor :repository_path_from, :repository_path_to + + def initialize(repository_path_from,repository_path_to) + @repository_path_from = repository_path_from + @repository_path_to = repository_path_to + end + + def to_s + "R #{repository_path_from} #{repository_path_to}\n" + end + + end + + class StreamFileDeleteAll + + def to_s + "deleteall\n" + end + end + + # Represents a stream of data bytes in the git stream + class StreamData + + def self.emit_inline_data(data_string) + "data #{data_string.length}\n#{data_string}\n" + end + + def self.emit_empty_data + "data 0\n\n" + end + end + + #==================== + # Stream Implementation + #==================== + + # This is an initial implementation of git fast-import/export streams + # It is not complete! + class Stream + + + end + +end \ No newline at end of file diff --git a/tests/units/test_stream.rb b/tests/units/test_stream.rb new file mode 100644 index 00000000..553f89de --- /dev/null +++ b/tests/units/test_stream.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' + +class TestStream < Test::Unit::TestCase + def setup + set_file_paths + end + + def test_minimal_commit_stream + t = Time.now + + commit = Git::StreamCommit.new + commit.branch = "master" + commit.committer = Git::Author.new("Arthur Developer","arthur@example.com",t) + + str = "commit refs/heads/master\n" + str << "mark #{commit.mark}\n" + str << "committer Arthur Developer #{t.rfc2822}\n" + str << "data 0\n" + str << "\n\n" + + assert_equal str, commit.to_s + end + + def test_single_file_add + t = Time.now + + commit = Git::StreamCommit.new + commit.branch = "master" + commit.author = Git::Author.new("Arthur Developer","arthur@example.com",t) + commit.committer = Git::Author.new("Jane Developer","jane@example.com",t+10) + commit.message = "Adding a single file." + commit.ancestor = "2e937ac5d5a6e95f4abb9f636273eaa6528f5dae" + commit.changes << Git::StreamFileModify.new("p/e/added-file.txt","This is the contents of the\nadded file.") + + str = "commit refs/heads/master\n" + str << "mark #{commit.mark}\n" + str << "author Arthur Developer #{t.rfc2822}\n" + str << "committer Jane Developer #{(t+10).rfc2822}\n" + str << "data 21\n" + str << "Adding a single file.\n" + str << "from 2e937ac5d5a6e95f4abb9f636273eaa6528f5dae\n" + str << "M 100644 inline p/e/added-file.txt\n" + str << "data 39\n" + str << "This is the contents of the\nadded file.\n" + str << "\n" + + assert_equal str, commit.to_s + end + + def test_single_file_add + t = Time.now + + commit = Git::StreamCommit.new + commit.branch = "master" + commit.author = Git::Author.new("Arthur Developer","arthur@example.com",t) + commit.committer = Git::Author.new("Jane Developer","jane@example.com",t+10) + commit.message = "Adding a single file." + commit.ancestor = "2e937ac5d5a6e95f4abb9f636273eaa6528f5dae" + commit.changes << Git::StreamFileModify.new("p/e/added-file.txt","This is the contents of the\nadded file.") + + str = "commit refs/heads/master\n" + str << "mark #{commit.mark}\n" + str << "author Arthur Developer #{t.rfc2822}\n" + str << "committer Jane Developer #{(t+10).rfc2822}\n" + str << "data 21\n" + str << "Adding a single file.\n" + str << "from 2e937ac5d5a6e95f4abb9f636273eaa6528f5dae\n" + str << "M 100644 inline p/e/added-file.txt\n" + str << "data 39\n" + str << "This is the contents of the\nadded file.\n" + str << "\n" + + assert_equal str, commit.to_s + end + + def test_multiple_changes + t = Time.now + + commit = Git::StreamCommit.new + commit.branch = "master" + commit.author = Git::Author.new("Arthur Developer","arthur@example.com",t) + commit.committer = Git::Author.new("Jane Developer","jane@example.com",t+10) + commit.message = "Add/Delete/Rename/Copy/DeleteAll." + commit.ancestor = "2e937ac5d5a6e95f4abb9f636273eaa6528f5dae" + commit.changes << Git::StreamFileModify.new("p/e/added-file.txt","This is the contents of the\nadded file.") + commit.changes << Git::StreamFileDelete.new("del-file.txt") + commit.changes << Git::StreamFileRename.new("file1.txt","file2.txt") + commit.changes << Git::StreamFileCopy.new("file2.txt","file3.txt") + commit.changes << Git::StreamFileDeleteAll.new + + str = "commit refs/heads/master\n" + str << "mark #{commit.mark}\n" + str << "author Arthur Developer #{t.rfc2822}\n" + str << "committer Jane Developer #{(t+10).rfc2822}\n" + str << "data 33\n" + str << "Add/Delete/Rename/Copy/DeleteAll.\n" + str << "from 2e937ac5d5a6e95f4abb9f636273eaa6528f5dae\n" + str << "M 100644 inline p/e/added-file.txt\n" + str << "data 39\n" + str << "This is the contents of the\nadded file.\n" + str << "D del-file.txt\n" + str << "R file1.txt file2.txt\n" + str << "C file2.txt file3.txt\n" + str << "deleteall\n" + str << "\n" + + assert_equal str, commit.to_s + end + +end From eb89cd5b7a8f816b103f8580145d01fcd80bfcac Mon Sep 17 00:00:00 2001 From: Peter Waldschmidt Date: Thu, 19 Feb 2009 15:46:32 -0500 Subject: [PATCH 3/6] Fix author initialization code. --- lib/git/author.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/git/author.rb b/lib/git/author.rb index 07880533..e9476f79 100644 --- a/lib/git/author.rb +++ b/lib/git/author.rb @@ -2,7 +2,7 @@ module Git class Author attr_accessor :name, :email, :date - def initialize(author_string) + def initialize(author_string = nil) if m = /(.*?) <(.*?)> (\d+) (.*)/.match(author_string) @name = m[1] @email = m[2] @@ -10,10 +10,12 @@ def initialize(author_string) end end - def initialize(name, email, date = Time.now) - @name = name - @email = email - @date = date + def self.from_parts(name, email, date = Time.now) + a = Author.new + a.name = name + a.email = email + a.date = date + return a end end end \ No newline at end of file From 673165a745ef2d8d0ad5d475c7b585dde802e3fb Mon Sep 17 00:00:00 2001 From: Peter Waldschmidt Date: Thu, 19 Feb 2009 15:47:44 -0500 Subject: [PATCH 4/6] Added fast-import commands to the library and tied it in with the git streaming code. --- lib/git/base.rb | 8 +++++ lib/git/lib.rb | 11 ++++++ lib/git/stream.rb | 40 +++++++++++++++++++++ tests/units/test_stream.rb | 74 ++++++++++++++++++++++++++++++++++---- 4 files changed, 126 insertions(+), 7 deletions(-) diff --git a/lib/git/base.rb b/lib/git/base.rb index 342796e2..e7c3fd03 100644 --- a/lib/git/base.rb +++ b/lib/git/base.rb @@ -364,6 +364,14 @@ def archive(treeish, file = nil, opts = {}) self.object(treeish).archive(file, opts) end + # use a stream to import into the repository + def import_stream + stream = Git::Stream.new + yield stream + + self.lib.fast_import(stream) + end + # repacks the repository def repack self.lib.repack diff --git a/lib/git/lib.rb b/lib/git/lib.rb index cffeffbd..437786aa 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -581,6 +581,17 @@ def checkout_index(opts = {}) command('checkout-index', arr_opts) end + def fast_import(stream) + tmp = Tempfile.new("stream-file") + tmp.write(stream.to_s) + tmp.flush + begin + command('fast-import',"--date-format=rfc2822",true,"< #{escape tmp.path}") + ensure + tmp.close(true) + end + end + # creates an archive file # # options diff --git a/lib/git/stream.rb b/lib/git/stream.rb index 2f9ff66a..00f7e106 100644 --- a/lib/git/stream.rb +++ b/lib/git/stream.rb @@ -25,6 +25,28 @@ def initialize() @changes = [] end + def modify_file(repos_path, data, mode = nil) + sfm = StreamFileModify.new(repos_path, data) + sfm.mode = mode unless mode == nil + changes << sfm + end + + def delete_file(repos_path) + changes << StreamFileDelete.new(repos_path) + end + + def rename_file(repos_path_from, repos_path_to) + changes << StreamFileRename.new(repos_path_form, repos_path_to) + end + + def copy_file(repos_path_from, repos_path_to) + changes << StreamFileCopy.new(repos_path_form, repos_path_to) + end + + def delete_all_files() + changes << StreamFileDeleteAll.new + end + def to_s out = "commit refs/heads/#{branch.to_s}\n" out << "mark #{mark}\n" @@ -143,7 +165,25 @@ def self.emit_empty_data # It is not complete! class Stream + attr_reader :commands + + def initialize + @commands = [] + end + + def commit + cmt = Git::StreamCommit.new + yield cmt + commands << cmt + end + def to_s + s = "" + commands.each do |cmd| + s << cmd.to_s + end + return s + end end end \ No newline at end of file diff --git a/tests/units/test_stream.rb b/tests/units/test_stream.rb index 553f89de..d58f178e 100644 --- a/tests/units/test_stream.rb +++ b/tests/units/test_stream.rb @@ -12,7 +12,7 @@ def test_minimal_commit_stream commit = Git::StreamCommit.new commit.branch = "master" - commit.committer = Git::Author.new("Arthur Developer","arthur@example.com",t) + commit.committer = Git::Author.from_parts("Arthur Developer","arthur@example.com",t) str = "commit refs/heads/master\n" str << "mark #{commit.mark}\n" @@ -28,8 +28,8 @@ def test_single_file_add commit = Git::StreamCommit.new commit.branch = "master" - commit.author = Git::Author.new("Arthur Developer","arthur@example.com",t) - commit.committer = Git::Author.new("Jane Developer","jane@example.com",t+10) + commit.author = Git::Author.from_parts("Arthur Developer","arthur@example.com",t) + commit.committer = Git::Author.from_parts("Jane Developer","jane@example.com",t+10) commit.message = "Adding a single file." commit.ancestor = "2e937ac5d5a6e95f4abb9f636273eaa6528f5dae" commit.changes << Git::StreamFileModify.new("p/e/added-file.txt","This is the contents of the\nadded file.") @@ -54,8 +54,8 @@ def test_single_file_add commit = Git::StreamCommit.new commit.branch = "master" - commit.author = Git::Author.new("Arthur Developer","arthur@example.com",t) - commit.committer = Git::Author.new("Jane Developer","jane@example.com",t+10) + commit.author = Git::Author.from_parts("Arthur Developer","arthur@example.com",t) + commit.committer = Git::Author.from_parts("Jane Developer","jane@example.com",t+10) commit.message = "Adding a single file." commit.ancestor = "2e937ac5d5a6e95f4abb9f636273eaa6528f5dae" commit.changes << Git::StreamFileModify.new("p/e/added-file.txt","This is the contents of the\nadded file.") @@ -80,8 +80,8 @@ def test_multiple_changes commit = Git::StreamCommit.new commit.branch = "master" - commit.author = Git::Author.new("Arthur Developer","arthur@example.com",t) - commit.committer = Git::Author.new("Jane Developer","jane@example.com",t+10) + commit.author = Git::Author.from_parts("Arthur Developer","arthur@example.com",t) + commit.committer = Git::Author.from_parts("Jane Developer","jane@example.com",t+10) commit.message = "Add/Delete/Rename/Copy/DeleteAll." commit.ancestor = "2e937ac5d5a6e95f4abb9f636273eaa6528f5dae" commit.changes << Git::StreamFileModify.new("p/e/added-file.txt","This is the contents of the\nadded file.") @@ -109,4 +109,64 @@ def test_multiple_changes assert_equal str, commit.to_s end + def test_temp_repo_baseline + create_temp_repo_with_branched_data do |g| + assert_equal 'blahblahblah3', g.cat_file('other_branch:test-file1') + assert_equal 'blahblahblah2', g.cat_file('other_branch:test-file2') + assert_equal 'blahblahblah1', g.cat_file('new_branch:test-file1') + assert_equal 'blahblahblah2', g.cat_file('new_branch:test-file2') + end + end + + def test_add_file_to_different_branch + create_temp_repo_with_branched_data do |g| + + t = Time.new + + # make sure we are on 'new_branch' + g.branch('new_branch').checkout + + # import a file into the 'other_branch' + g.import_stream do |stream| + stream.commit do |c| + c.branch = g.branch('other_branch') + c.committer = Git::Author.from_parts("Jane Developer","jane@example.com",t) + c.message = "Test adding a single file to a different branch, without switching." + c.modify_file("test-file1","testtesttesttest5") + c.ancestor = g.log.object('other_branch').first + end + end + + assert_equal 'blahblahblah2', g.cat_file('other_branch:test-file2') + assert_equal 'blahblahblah1', g.cat_file('new_branch:test-file1') + assert_equal 'blahblahblah2', g.cat_file('new_branch:test-file2') + assert_equal "testtesttesttest5", g.cat_file('other_branch:test-file1') + end + end + + def create_temp_repo_with_branched_data + in_temp_dir do |path| + g = Git.clone(@wbare, 'branch_test') + Dir.chdir('branch_test') do + + # create a basic repo with two branches and some content + g.branch('new_branch').checkout + + new_file('test-file1', 'blahblahblah1') + new_file('test-file2', 'blahblahblah2') + + g.add(['test-file1', 'test-file2']) + g.commit("Initial commit.") + + g.branch('other_branch').checkout + + new_file('test-file1', 'blahblahblah3') + g.add(['test-file1']) + g.commit("Second commit.") + + yield g + end + end + end + end From c6c23e0e6ac2a9c4bd8909177d79c20ad55e2829 Mon Sep 17 00:00:00 2001 From: peterwald Date: Sun, 22 Feb 2009 15:31:40 -0700 Subject: [PATCH 5/6] Bump Gem Version --- ruby-git.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby-git.gemspec b/ruby-git.gemspec index 30615708..5106056d 100644 --- a/ruby-git.gemspec +++ b/ruby-git.gemspec @@ -1,7 +1,7 @@ spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = "git" - s.version = "1.1.2" + s.version = "1.1.3" s.author = "Scott Chacon" s.email = "schacon@gmail.com" s.summary = "A package for using Git in Ruby code." From 61f577ade0dad7b9a748c2ce96d7c35315ccd571 Mon Sep 17 00:00:00 2001 From: peterwald Date: Sun, 22 Feb 2009 16:37:02 -0700 Subject: [PATCH 6/6] Added stream.rb to the package. --- ruby-git.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby-git.gemspec b/ruby-git.gemspec index 5106056d..ffb4d611 100644 --- a/ruby-git.gemspec +++ b/ruby-git.gemspec @@ -1,11 +1,11 @@ spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = "git" - s.version = "1.1.3" + s.version = "1.1.4" s.author = "Scott Chacon" s.email = "schacon@gmail.com" s.summary = "A package for using Git in Ruby code." - s.files = ["lib/git", "lib/git/author.rb", "lib/git/base.rb", "lib/git/branch.rb", "lib/git/branches.rb", "lib/git/diff.rb", "lib/git/index.rb", "lib/git/lib.rb", "lib/git/log.rb", "lib/git/object.rb", "lib/git/path.rb", "lib/git/remote.rb", "lib/git/repository.rb", "lib/git/stash.rb", "lib/git/stashes.rb", "lib/git/status.rb", "lib/git/working_directory.rb", "lib/git.rb"] + s.files = ["lib/git", "lib/git/author.rb", "lib/git/base.rb", "lib/git/branch.rb", "lib/git/branches.rb", "lib/git/diff.rb", "lib/git/index.rb", "lib/git/lib.rb", "lib/git/log.rb", "lib/git/object.rb", "lib/git/path.rb", "lib/git/remote.rb", "lib/git/repository.rb", "lib/git/stash.rb", "lib/git/stashes.rb", "lib/git/status.rb", "lib/git/stream.rb", "lib/git/working_directory.rb", "lib/git.rb"] s.require_path = "lib" s.autorequire = "git" s.test_files = Dir.glob('tests/*.rb')