├── .document ├── .gitignore ├── LICENSE ├── README.rdoc ├── Rakefile ├── VERSION ├── bin └── git-db ├── git-db.gemspec ├── lib ├── git-db.rb └── git-db │ ├── commands.rb │ ├── commands │ ├── receive-pack.rb │ └── upload-pack.rb │ ├── database.rb │ ├── objects.rb │ ├── objects │ ├── base.rb │ ├── blob.rb │ ├── commit.rb │ ├── entry.rb │ ├── tag.rb │ └── tree.rb │ ├── pack.rb │ ├── protocol.rb │ ├── utility.rb │ └── utility │ └── counting_io.rb └── spec ├── git-db ├── commands_spec.rb ├── objects │ ├── base_spec.rb │ ├── blob_spec.rb │ ├── commit_spec.rb │ ├── entry_spec.rb │ ├── tag_spec.rb │ └── tree_spec.rb ├── objects_spec.rb ├── protocol_spec.rb └── utility │ └── counting_io_spec.rb ├── git-db_spec.rb ├── rcov.opts ├── spec.opts └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | rdoc 5 | pkg 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 David Dollar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = git-db 2 | 3 | CouchDB-based git server, avoids the filesystem. (VERY ALPHA) 4 | 5 | == Installation 6 | 7 | * Install CouchDB on localhost, and start it up. 8 | 9 | * Install the gem 10 | 11 | $ gem install ddollar-git-db 12 | 13 | * Create a git user. (Name can be whatever you like) 14 | 15 | * Set a home directory for the user. 16 | 17 | * Set up the git user's authorized_keys2 file: (modify the command to match your gem particulars) 18 | 19 | # $HOME/git/.ssh/authorized_keys2 20 | command="/usr/bin/git-db",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty 21 | 22 | * Add your localhost as a remote to an existing project and push 23 | 24 | $ git remote add test-git-db git@localhost:my-repo.git 25 | $ git push test-git-db master 26 | 27 | * Go look at the data in CouchDB 28 | 29 | http://127.0.0.1:5984/_utils/database.html?gitdb-my-repo 30 | 31 | * Clone your repository somewhere else and examine it 32 | 33 | $ git clone git@localhost:my-repo.git /tmp/my-repo 34 | 35 | * Please report any problems on the issue tracker. 36 | 37 | == Links 38 | 39 | * Continuous Integration - http://runcoderun.com/ddollar/git-db 40 | * Documentation - http://rdoc.info/projects/ddollar/git-db 41 | 42 | == TODO 43 | 44 | * Tests 45 | * Refactor and clean up (experimenting with binary protocols can make things a bit messy) 46 | * Authentication tie-in 47 | * Never look at a raw git pack file again 48 | 49 | == Note on Patches/Pull Requests 50 | 51 | * Fork the project. 52 | * Make your feature addition or bug fix. 53 | * Add tests for it. This is important so I don't break it in a future version unintentionally. 54 | * Commit, do not mess with rakefile, version, or history. 55 | * Send me a pull request. Bonus points for topic branches. 56 | 57 | == Copyright 58 | 59 | Copyright (c) 2009 David Dollar. See LICENSE for details. 60 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | begin 5 | require 'jeweler' 6 | Jeweler::Tasks.new do |gem| 7 | gem.name = "git-db" 8 | gem.summary = %Q{Database-based git server} 9 | gem.description = gem.summary 10 | gem.email = "" 11 | gem.homepage = "http://github.com/ddollar/git-db" 12 | gem.authors = ["David Dollar"] 13 | 14 | # development dependencies 15 | gem.add_development_dependency "rspec" 16 | 17 | # runtime dependencies 18 | gem.add_dependency "couchrest" 19 | end 20 | Jeweler::GemcutterTasks.new 21 | rescue LoadError 22 | puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" 23 | end 24 | 25 | require 'spec/rake/spectask' 26 | Spec::Rake::SpecTask.new(:spec) do |spec| 27 | spec.libs << 'lib' << 'spec' 28 | spec.spec_files = FileList['spec/**/*_spec.rb'] 29 | spec.spec_opts << '--colour --format specdoc' 30 | end 31 | 32 | Spec::Rake::SpecTask.new(:rcov) do |spec| 33 | spec.libs << 'lib' << 'spec' 34 | spec.pattern = 'spec/**/*_spec.rb' 35 | spec.rcov = true 36 | spec.rcov_opts = lambda do 37 | IO.readlines("#{File.dirname(__FILE__)}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten 38 | end 39 | end 40 | 41 | task :spec => :check_dependencies 42 | 43 | task :default => :spec 44 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | -------------------------------------------------------------------------------- /bin/git-db: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'git-db' 4 | include GitDB 5 | 6 | GitDB.log(ENV.inspect) 7 | 8 | git_command = ENV['SSH_ORIGINAL_COMMAND'].match(/git-(.+) '(.+)'/) 9 | 10 | if git_command 11 | command = git_command[1] 12 | repository = git_command[2] 13 | 14 | repository = repository.gsub(/\.git$/, '') 15 | 16 | GitDB::Commands.execute(command, [repository]) 17 | end 18 | -------------------------------------------------------------------------------- /git-db.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec` 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{git-db} 8 | s.version = "0.2.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["David Dollar"] 12 | s.date = %q{2009-09-14} 13 | s.default_executable = %q{git-db} 14 | s.description = %q{Database-based git server} 15 | s.email = %q{} 16 | s.executables = ["git-db"] 17 | s.extra_rdoc_files = [ 18 | "LICENSE", 19 | "README.rdoc" 20 | ] 21 | s.files = [ 22 | ".document", 23 | ".gitignore", 24 | "LICENSE", 25 | "README.rdoc", 26 | "Rakefile", 27 | "VERSION", 28 | "bin/git-db", 29 | "git-db.gemspec", 30 | "lib/git-db.rb", 31 | "lib/git-db/commands.rb", 32 | "lib/git-db/commands/receive-pack.rb", 33 | "lib/git-db/commands/upload-pack.rb", 34 | "lib/git-db/database.rb", 35 | "lib/git-db/objects.rb", 36 | "lib/git-db/objects/base.rb", 37 | "lib/git-db/objects/blob.rb", 38 | "lib/git-db/objects/commit.rb", 39 | "lib/git-db/objects/entry.rb", 40 | "lib/git-db/objects/tag.rb", 41 | "lib/git-db/objects/tree.rb", 42 | "lib/git-db/pack.rb", 43 | "lib/git-db/protocol.rb", 44 | "lib/git-db/utility.rb", 45 | "lib/git-db/utility/counting_io.rb", 46 | "spec/git-db/commands_spec.rb", 47 | "spec/git-db/objects/base_spec.rb", 48 | "spec/git-db/objects/blob_spec.rb", 49 | "spec/git-db/objects/commit_spec.rb", 50 | "spec/git-db/objects/entry_spec.rb", 51 | "spec/git-db/objects/tag_spec.rb", 52 | "spec/git-db/objects/tree_spec.rb", 53 | "spec/git-db/objects_spec.rb", 54 | "spec/git-db/protocol_spec.rb", 55 | "spec/git-db/utility/counting_io_spec.rb", 56 | "spec/git-db_spec.rb", 57 | "spec/rcov.opts", 58 | "spec/spec.opts", 59 | "spec/spec_helper.rb" 60 | ] 61 | s.homepage = %q{http://github.com/ddollar/git-db} 62 | s.rdoc_options = ["--charset=UTF-8"] 63 | s.require_paths = ["lib"] 64 | s.rubygems_version = %q{1.3.5} 65 | s.summary = %q{Database-based git server} 66 | s.test_files = [ 67 | "spec/git-db/commands_spec.rb", 68 | "spec/git-db/objects/base_spec.rb", 69 | "spec/git-db/objects/blob_spec.rb", 70 | "spec/git-db/objects/commit_spec.rb", 71 | "spec/git-db/objects/entry_spec.rb", 72 | "spec/git-db/objects/tag_spec.rb", 73 | "spec/git-db/objects/tree_spec.rb", 74 | "spec/git-db/objects_spec.rb", 75 | "spec/git-db/protocol_spec.rb", 76 | "spec/git-db/utility/counting_io_spec.rb", 77 | "spec/git-db_spec.rb", 78 | "spec/spec_helper.rb" 79 | ] 80 | 81 | if s.respond_to? :specification_version then 82 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 83 | s.specification_version = 3 84 | 85 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 86 | s.add_development_dependency(%q, [">= 0"]) 87 | s.add_runtime_dependency(%q, [">= 0"]) 88 | else 89 | s.add_dependency(%q, [">= 0"]) 90 | s.add_dependency(%q, [">= 0"]) 91 | end 92 | else 93 | s.add_dependency(%q, [">= 0"]) 94 | s.add_dependency(%q, [">= 0"]) 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/git-db.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | require 'couchrest' 3 | require 'logger' 4 | 5 | module GitDB; 6 | 7 | ## git constants ############################################################# 8 | 9 | OBJ_NONE = 0 10 | OBJ_COMMIT = 1 11 | OBJ_TREE = 2 12 | OBJ_BLOB = 3 13 | OBJ_TAG = 4 14 | OBJ_OFS_DELTA = 6 15 | OBJ_REF_DELTA = 7 16 | 17 | ## git utility ############################################################### 18 | 19 | def self.sha1_to_hex(sha) 20 | hex = "" 21 | sha.split('').each do |char| 22 | val = char[0] 23 | hex << (val >> 4).to_s(16) 24 | hex << (val & 0xf).to_s(16) 25 | end 26 | hex 27 | end 28 | 29 | def self.hex_to_sha1(hex) 30 | sha = "" 31 | len = 0 32 | until (len == hex.length) 33 | val = (hex[len, 1].to_i(16) << 4) 34 | val += hex[len+1, 1].to_i(16) 35 | sha << val.chr 36 | len += 2 37 | end 38 | sha 39 | end 40 | 41 | def self.null_sha1 42 | "0000000000000000000000000000000000000000" 43 | end 44 | 45 | ## logging ################################################################### 46 | 47 | def self.logger 48 | @logger ||= STDERR 49 | end 50 | 51 | def self.log(message) 52 | logger.puts message if ENV["DEBUG"] 53 | end 54 | 55 | ## database ################################################################## 56 | 57 | def self.database(repository) 58 | GitDB::Database.database(repository) 59 | end 60 | 61 | end 62 | 63 | require 'git-db/commands' 64 | require 'git-db/database' 65 | require 'git-db/objects' 66 | require 'git-db/pack' 67 | require 'git-db/protocol' 68 | require 'git-db/utility' 69 | -------------------------------------------------------------------------------- /lib/git-db/commands.rb: -------------------------------------------------------------------------------- 1 | module GitDB::Commands 2 | 3 | def self.commands 4 | @commands 5 | end 6 | 7 | def self.execute(command, args=[]) 8 | return unless commands 9 | raise ArgumentError, "Unknown command: #{command}" unless commands[command] 10 | commands[command].execute(args) 11 | end 12 | 13 | def self.register(command, klass) 14 | @commands ||= {} 15 | @commands[command] = klass.new 16 | end 17 | 18 | end 19 | 20 | require 'git-db/commands/receive-pack' 21 | require 'git-db/commands/upload-pack' 22 | -------------------------------------------------------------------------------- /lib/git-db/commands/receive-pack.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'zlib' 3 | 4 | class GitDB::Commands::ReceivePack 5 | 6 | def execute(args) 7 | repository = args.first 8 | raise ArgumentError, "repository required" unless repository 9 | 10 | database = GitDB.database(repository) 11 | 12 | needs_capabilities = true 13 | database.get_refs.each do |ref, sha| 14 | GitDB.log("ISAERF #{ref} #{sha}") 15 | write_ref(ref, sha, needs_capabilities) 16 | needs_capabilities = false 17 | end 18 | write_ref("capabilities^{}", GitDB.null_sha1) if needs_capabilities 19 | io.write_eof 20 | 21 | refs = [] 22 | new_shas = [] 23 | 24 | while (data = io.read_command) 25 | old_sha, new_sha, ref = data.split(' ') 26 | ref, report = ref.split(0.chr) 27 | 28 | refs << ref 29 | new_shas << new_sha 30 | 31 | if new_sha == GitDB.null_sha1 32 | database.delete_ref(ref) 33 | else 34 | database.write_ref(ref, new_sha) 35 | end 36 | end 37 | 38 | unless new_shas.reject { |sha| sha == GitDB.null_sha1 }.length.zero? 39 | while (entries = io.read_pack) 40 | 41 | # bulk in 50 at a time 42 | while (page = entries.shift(50)).length > 0 43 | database.write_objects(page) 44 | end 45 | end 46 | end 47 | 48 | io.write_command("unpack ok\n") 49 | refs.each do |ref| 50 | io.write_command("ok #{ref}\n") 51 | end 52 | io.write_eof 53 | end 54 | 55 | private 56 | 57 | def capabilities 58 | " report-status delete-refs ofs-delta " 59 | end 60 | 61 | def io 62 | @io ||= GitDB::Protocol.new 63 | end 64 | 65 | def write_ref(ref, sha, needs_capabilities=true) 66 | if needs_capabilities 67 | header = "%s %s\000%s\n" % [ sha, ref, capabilities ] 68 | io.write_command(header) 69 | else 70 | header = "%s %s\n" % [ sha, ref ] 71 | io.write_command(header) 72 | end 73 | end 74 | 75 | end 76 | 77 | GitDB::Commands.register 'receive-pack', GitDB::Commands::ReceivePack 78 | -------------------------------------------------------------------------------- /lib/git-db/commands/upload-pack.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'zlib' 3 | 4 | class GitDB::Commands::UploadPack 5 | 6 | def execute(args) 7 | repository = args.first 8 | raise ArgumentError, "repository required" unless repository 9 | 10 | database = GitDB.database(repository) 11 | 12 | #execute_transcript(database) 13 | execute_real(database) 14 | end 15 | 16 | def execute_transcript(database) 17 | cmd = GitDB::Protocol.new(IO.popen("/opt/local/bin/git-upload-pack '/tmp/foo'", 'r+')) 18 | 19 | while (data = cmd.read_command) 20 | GitDB.log("CMD COMMAND: #{data}") 21 | io.write_command(data) 22 | end 23 | io.write_eof 24 | 25 | while (data = io.read_command) 26 | GitDB.log("IO COMMAND: #{data}") 27 | cmd.write_command(data) 28 | end 29 | cmd.write_eof 30 | 31 | while (data = io.read_command) 32 | cmd.write_command(data) 33 | data = data.strip 34 | break if data == 'done' 35 | GitDB.log("READ FROM IO #{data}") 36 | end 37 | 38 | while (data = cmd.read_command) 39 | GitDB.log("GOT COMMAND DATA: #{data.inspect}") 40 | end 41 | end 42 | 43 | def execute_real(database) 44 | write_ref 'HEAD', database.get_ref('refs/heads/master')['sha'] 45 | 46 | database.get_refs.each do |ref, sha| 47 | write_ref(ref, sha) 48 | end 49 | io.write_eof 50 | 51 | shas_to_read = [] 52 | shas_to_ignore = [] 53 | 54 | while (data = io.read_command) 55 | GitDB.log("GOT COMMAND: #{data.inspect}") 56 | command, sha, options = data.split(' ', 3) 57 | shas_to_read << sha 58 | end 59 | 60 | while (data = io.read_command) 61 | data = data.strip 62 | break if data == 'done' 63 | command, sha = data.split(" ", 2) 64 | case command 65 | when 'have' then 66 | shas_to_ignore << sha 67 | else 68 | raise "Unknown SHA command: #{command}" 69 | end 70 | end 71 | 72 | if shas_to_ignore.length.zero? 73 | io.write_command("NAK\n") 74 | else 75 | io.write_command("ACK #{shas_to_ignore.last}\n") 76 | end 77 | 78 | shas_to_ignore, _ = load_entries(database, shas_to_ignore, false) 79 | shas, entries = load_entries(database, shas_to_read, true, shas_to_ignore) 80 | 81 | GitDB.log(entries.map { |e| e.inspect }) 82 | 83 | io.write_pack(entries) 84 | end 85 | 86 | private 87 | 88 | def load_entries(database, shas_to_read, keep_entries, shas_to_ignore=[]) 89 | entries = [] 90 | shas = [] 91 | 92 | while sha = shas_to_read.shift 93 | next if shas_to_ignore.include?(sha) 94 | GitDB.log("SHAS_TO_IGNORE: #{shas_to_ignore.sort.inspect}") 95 | GitDB.log("READING SHA: #{sha}") 96 | shas_to_ignore << sha 97 | 98 | shas << sha 99 | 100 | object = database.get_object(sha) 101 | GitDB.log("OBJECT: #{object.inspect}") 102 | data = object.data 103 | 104 | case object 105 | when GitDB::Objects::Commit then 106 | commit = GitDB::Objects::Commit.new(data) 107 | shas_to_read << commit.tree 108 | shas_to_read += commit.parents if commit.parents 109 | entries << commit if keep_entries 110 | when GitDB::Objects::Tree then 111 | tree = GitDB::Objects::Tree.new(data) 112 | shas_to_read += tree.entries.map { |e| e.sha } 113 | entries << tree if keep_entries 114 | when GitDB::Objects::Blob then 115 | blob = GitDB::Objects::Blob.new(data) 116 | entries << blob if keep_entries 117 | else 118 | raise "UNKNOWN TYPE!! #{object.class}" 119 | end 120 | end 121 | 122 | [shas, entries] 123 | end 124 | 125 | def capabilities 126 | " shallow include-tag" 127 | end 128 | 129 | def io 130 | @io ||= GitDB::Protocol.new 131 | end 132 | 133 | def write_ref(ref, sha, needs_capabilities=true) 134 | header = "%s %s\000%s\n" % [ sha, ref, capabilities ] 135 | io.write_command(header) 136 | end 137 | 138 | end 139 | 140 | GitDB::Commands.register 'upload-pack', GitDB::Commands::UploadPack 141 | -------------------------------------------------------------------------------- /lib/git-db/database.rb: -------------------------------------------------------------------------------- 1 | class GitDB::Database 2 | 3 | def self.couch 4 | @couch ||= CouchRest.new('http://localhost:5984') 5 | end 6 | 7 | def self.database(repository) 8 | @databases ||= {} 9 | @databases[repository] ||= new(repository) 10 | end 11 | 12 | attr_reader :repository, :name, :database 13 | 14 | def initialize(repository) 15 | @repository = repository 16 | @name = "gitdb-#{repository.gsub('/', '-')}" 17 | @database = self.class.couch.database!(name) 18 | update_views 19 | end 20 | 21 | ## refs ###################################################################### 22 | 23 | def get_ref(ref) 24 | doc = database.view('refs/all', :key => ref)['rows'].first 25 | doc ? doc['value'] : nil 26 | end 27 | 28 | def get_refs 29 | database.view('refs/all')['rows'].inject({}) do |hash, row| 30 | hash.update(row['key'] => row['value']['sha']) 31 | end 32 | end 33 | 34 | def write_ref(ref, sha) 35 | if doc = get_ref(ref) 36 | doc['sha'] = sha 37 | database.save_doc(doc) 38 | else 39 | database.save_doc(:doctype => 'ref', :ref => ref, :sha => sha) 40 | end 41 | end 42 | 43 | def delete_ref(ref) 44 | if doc = get_ref(ref) 45 | database.delete_doc(doc) 46 | end 47 | end 48 | 49 | ## objects ################################################################### 50 | 51 | def get_raw_object(sha) 52 | doc = database.view('objects/all', :key => sha)['rows'].first 53 | doc = doc ? decode_object(doc['value']) : nil 54 | end 55 | 56 | def get_object(sha) 57 | raw = get_raw_object(sha) 58 | raw ? GitDB::Objects.new_from_type(raw['type'], raw['data']) : nil 59 | end 60 | 61 | def write_object(object) 62 | doc = object_to_doc(object) 63 | doc = (get_raw_object(object.sha) || {}).merge(doc) 64 | database.save_doc(doc) 65 | end 66 | 67 | def write_objects(objects) 68 | docs = objects.map do |object| 69 | doc = object_to_doc(object) 70 | doc = (get_raw_object(object.sha) || {}).merge(doc) 71 | end 72 | database.bulk_save(docs) 73 | end 74 | 75 | ## utility ################################################################### 76 | 77 | def document_ids 78 | database.documents['rows'].map { |row| row['id']} 79 | end 80 | 81 | def encode_object(doc) 82 | doc['data'] = Base64.encode64(doc['data']) 83 | doc 84 | end 85 | 86 | def decode_object(doc) 87 | doc['data'] = Base64.decode64(doc['data']) 88 | doc 89 | end 90 | 91 | def object_to_doc(object) 92 | properties = object.properties 93 | properties += [:type, :data, :sha] 94 | doc = properties.inject({ :doctype => 'object' }) do |hash, property| 95 | hash.update(property.to_s => object.send(property)) 96 | end 97 | encode_object(doc) 98 | end 99 | 100 | def update_views 101 | if document_ids.include?('_design/refs') 102 | database.delete_doc(database.get('_design/refs')) 103 | end 104 | database.save_doc({ 105 | '_id' => '_design/refs', 106 | :views => { 107 | :all => { 108 | :map => %{ 109 | function(doc) { 110 | if (doc.doctype == 'ref') { emit(doc.ref, doc); } 111 | } 112 | } 113 | }, 114 | } 115 | }) 116 | if document_ids.include?('_design/objects') 117 | database.delete_doc(database.get('_design/objects')) 118 | end 119 | database.save_doc({ 120 | '_id' => '_design/objects', 121 | :views => { 122 | :all => { 123 | :map => %{ 124 | function(doc) { 125 | if (doc.doctype == 'object') { emit(doc.sha, doc); } 126 | } 127 | } 128 | }, 129 | } 130 | }) 131 | end 132 | 133 | 134 | end 135 | -------------------------------------------------------------------------------- /lib/git-db/objects.rb: -------------------------------------------------------------------------------- 1 | module GitDB::Objects; 2 | 3 | def self.new_from_type(type, data) 4 | case type 5 | when GitDB::OBJ_COMMIT then GitDB::Objects::Commit.new(data) 6 | when GitDB::OBJ_TREE then GitDB::Objects::Tree.new(data) 7 | when GitDB::OBJ_BLOB then GitDB::Objects::Blob.new(data) 8 | when GitDB::OBJ_TAG then GitDB::Objects::Tag.new(data) 9 | else raise "Unknown object type: #{type}" 10 | end 11 | end 12 | 13 | end 14 | 15 | require 'git-db/objects/base' 16 | require 'git-db/objects/blob' 17 | require 'git-db/objects/commit' 18 | require 'git-db/objects/entry' 19 | require 'git-db/objects/tag' 20 | require 'git-db/objects/tree' 21 | -------------------------------------------------------------------------------- /lib/git-db/objects/base.rb: -------------------------------------------------------------------------------- 1 | class GitDB::Objects::Base 2 | 3 | attr_reader :data 4 | 5 | def initialize(data) 6 | @data = data 7 | end 8 | 9 | def inspect 10 | %{#<#{self.class} #{inspect_properties}>} 11 | end 12 | 13 | def properties 14 | [:data] 15 | end 16 | 17 | def raw 18 | data 19 | end 20 | 21 | def sha 22 | Digest::SHA1.hexdigest(raw) 23 | end 24 | 25 | private ###################################################################### 26 | 27 | def inspect_properties 28 | inspectors = properties.unshift(:sha).map do |argument| 29 | "#{argument}=#{self.send(argument).inspect}" 30 | end 31 | inspectors.join(' ') 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /lib/git-db/objects/blob.rb: -------------------------------------------------------------------------------- 1 | class GitDB::Objects::Blob < GitDB::Objects::Base 2 | 3 | def raw 4 | "blob #{data.length}\000#{data}" 5 | end 6 | 7 | def type 8 | GitDB::OBJ_BLOB 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /lib/git-db/objects/commit.rb: -------------------------------------------------------------------------------- 1 | class GitDB::Objects::Commit < GitDB::Objects::Base 2 | 3 | def author 4 | attributes['author'].first 5 | end 6 | 7 | def committer 8 | attributes['committer'].first 9 | end 10 | 11 | def message 12 | data.split("\n\n", 2).last 13 | end 14 | 15 | def parents 16 | attributes['parent'] 17 | end 18 | 19 | def properties 20 | [:tree, :parents, :author, :committer, :message] 21 | end 22 | 23 | def raw 24 | "commit #{data.length}\000#{data}" 25 | end 26 | 27 | def type 28 | GitDB::OBJ_COMMIT 29 | end 30 | 31 | def tree 32 | attributes['tree'].first 33 | end 34 | 35 | private ###################################################################### 36 | 37 | def attributes 38 | @attributes ||= begin 39 | attributes = data.split("\n\n", 2).first 40 | attributes.split("\n").inject({}) do |hash, line| 41 | key, value = line.split(' ', 2) 42 | hash[key] ||= [] 43 | hash[key] << value 44 | hash 45 | end 46 | end 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /lib/git-db/objects/entry.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | class GitDB::Objects::Entry < GitDB::Objects::Base 4 | 5 | attr_reader :sha, :permissions, :name 6 | 7 | def initialize(sha, permissions, name) 8 | @sha = sha 9 | @permissions = permissions 10 | @name = name 11 | end 12 | 13 | def properties 14 | [:permissions, :name] 15 | end 16 | 17 | def to_hash 18 | { :sha => sha, :permissions => permissions, :name => name } 19 | end 20 | 21 | def to_json 22 | to_hash.to_json 23 | end 24 | 25 | private ###################################################################### 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lib/git-db/objects/tag.rb: -------------------------------------------------------------------------------- 1 | class GitDB::Objects::Tag < GitDB::Objects::Base 2 | 3 | def type 4 | GitDB::OBJ_TAG 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /lib/git-db/objects/tree.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | class GitDB::Objects::Tree < GitDB::Objects::Base 4 | 5 | def entries 6 | @entries ||= begin 7 | entries = [] 8 | stream = StringIO.new(data) 9 | until stream.eof? 10 | perms = read_until(stream, ' ').to_i 11 | name = read_until(stream, 0.chr) 12 | sha = GitDB.sha1_to_hex(stream.read(20)) 13 | entries << GitDB::Objects::Entry.new(sha, perms, name) 14 | end 15 | entries 16 | end 17 | end 18 | 19 | def properties 20 | [:entries] 21 | end 22 | 23 | def raw 24 | "tree #{data.length}\000#{data}" 25 | end 26 | 27 | def type 28 | GitDB::OBJ_TREE 29 | end 30 | 31 | private ###################################################################### 32 | 33 | def read_until(stream, separator) 34 | data = "" 35 | char = "" 36 | loop do 37 | char = stream.read(1) 38 | break if char.nil? 39 | break if char == separator 40 | data << char 41 | end 42 | data 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /lib/git-db/pack.rb: -------------------------------------------------------------------------------- 1 | require 'digest/sha1' 2 | require 'stringio' 3 | require 'zlib' 4 | 5 | class GitDB::Pack 6 | 7 | PackObject = Struct.new(:type, :offset, :data) 8 | 9 | attr_reader :io 10 | 11 | def initialize(io) 12 | @io = GitDB::Utility::CountingIO.new(io) 13 | end 14 | 15 | def read 16 | header = io.read(12) 17 | return nil unless header 18 | 19 | signature, version, entries = header.unpack("a4NN") 20 | raise 'invalid pack signature' unless signature == 'PACK' 21 | raise 'invalid version' unless version == 2 22 | 23 | objects = {} 24 | 25 | 1.upto(entries) do 26 | object_offset = io.offset 27 | type, size = unpack_pack_header(io) 28 | 29 | object = case type 30 | when 1 then GitDB::Objects::Commit.new(read_compressed(io)) 31 | when 2 then GitDB::Objects::Tree.new(read_compressed(io)) 32 | when 3 then GitDB::Objects::Blob.new(read_compressed(io)) 33 | when 4 then GitDB::Objects::Tag.new(read_compressed(io)) 34 | when 5 then raise 'Invalid Type: 5' 35 | when 6 then 36 | # offset delta, find the referred-to pack and apply as a patch 37 | offset = object_offset - unpack_delta_size(io) 38 | patch = read_compressed(io) 39 | base = objects[offset] 40 | data = apply_patch(base.data, patch) 41 | base.class.new(data) 42 | when 7 then 43 | # TODO: patch against sha 44 | raise "Type 7 unimplemented, please report" 45 | end 46 | 47 | objects[object_offset] = object 48 | end 49 | 50 | # read the checksum, TODO: check the checksum 51 | checksum = io.read(20) 52 | 53 | objects.values.compact 54 | end 55 | 56 | def write(entries) 57 | # build the pack header 58 | buffer = ["PACK", 2, entries.length].pack("a4NN") 59 | 60 | # deflate the entries 61 | entries.each do |entry| 62 | buffer << pack_pack_header(entry.type, entry.data.length) 63 | buffer << Zlib::Deflate.deflate(entry.data) 64 | end 65 | 66 | # calculate a checksum 67 | checksum = GitDB::hex_to_sha1(Digest::SHA1.hexdigest(buffer)) 68 | 69 | # write and flush the buffer 70 | io.write(buffer) 71 | io.write(checksum) 72 | io.flush 73 | end 74 | 75 | private ###################################################################### 76 | 77 | def apply_patch(original, patch) 78 | patch_stream = StringIO.new(patch) 79 | source_size = unpack_size(patch_stream) 80 | destination_size = unpack_size(patch_stream) 81 | 82 | data = "" 83 | 84 | # this pretty much a straight port of the c function in git that does 85 | # this. it could probably be refactored, but is maintained for 86 | # symmetry (and to detect potential future changes to git) 87 | until patch_stream.eof? 88 | offset = size = 0 89 | cmd = patch_stream.read(1)[0] 90 | 91 | if (cmd & 0x80) != 0 92 | offset = (patch_stream.read(1)[0]) if (cmd & 0x01).nonzero? 93 | offset |= (patch_stream.read(1)[0] << 8) if (cmd & 0x02).nonzero? 94 | offset |= (patch_stream.read(1)[0] << 16) if (cmd & 0x04).nonzero? 95 | offset |= (patch_stream.read(1)[0] << 24) if (cmd & 0x08).nonzero? 96 | size = (patch_stream.read(1)[0]) if (cmd & 0x10).nonzero? 97 | size |= (patch_stream.read(1)[0] << 8) if (cmd & 0x20).nonzero? 98 | size |= (patch_stream.read(1)[0] << 16) if (cmd & 0x40).nonzero? 99 | size = 0x10000 if size.zero? 100 | 101 | if ((offset + size) < size) || 102 | ((offset + size) > source_size) || 103 | (size > destination_size) 104 | break 105 | end 106 | data += original[offset,size] 107 | elsif (cmd != 0) 108 | data += patch_stream.read(cmd) 109 | end 110 | end 111 | 112 | data 113 | end 114 | 115 | def read_compressed(stream) 116 | zstream = Zlib::Inflate.new 117 | data = "" 118 | loop do 119 | data += zstream.inflate(stream.read(1)) 120 | break if zstream.finished? 121 | end 122 | data 123 | end 124 | 125 | def pack_pack_header(type, size) 126 | data = "" 127 | c = (type << 4) | (size & 15); 128 | size >>= 4; 129 | while (size > 0) 130 | data << (c | 0x80).chr 131 | c = size & 0x7f; 132 | size >>= 7; 133 | end 134 | data << c.chr 135 | end 136 | 137 | def unpack_delta_size(stream) 138 | c = stream.read(1)[0] 139 | size = (c & 127) 140 | while (c & 128) != 0 141 | size += 1 142 | c = stream.read(1)[0] 143 | size = (size << 7) + (c & 127) 144 | end 145 | size 146 | end 147 | 148 | def unpack_pack_header(stream) 149 | c = stream.read(1)[0] 150 | type = (c >> 4) & 7 151 | size = (c & 15) 152 | shift = 4 153 | while ((c & 0x80) != 0) 154 | c = stream.read(1)[0] 155 | size += ((c & 0x7f) << shift) 156 | shift += 7 157 | end 158 | [type, size] 159 | end 160 | 161 | def unpack_size(stream) 162 | size = shift = 0 163 | loop do 164 | c = stream.read(1)[0] 165 | size += (c & 127) << shift 166 | shift += 7 167 | break if (c & 128).zero? 168 | end 169 | size 170 | end 171 | 172 | end 173 | -------------------------------------------------------------------------------- /lib/git-db/protocol.rb: -------------------------------------------------------------------------------- 1 | class GitDB::Protocol 2 | 3 | attr_reader :reader 4 | attr_reader :writer 5 | 6 | def initialize(io=nil) 7 | if io 8 | @reader = io 9 | @writer = io 10 | else 11 | @reader = STDIN 12 | @writer = STDOUT 13 | end 14 | end 15 | 16 | ## commands ################################################################## 17 | 18 | def flush 19 | writer.flush 20 | end 21 | 22 | def read_command 23 | # length is stored in the first 4 bytes 24 | length = reader.read(4) 25 | return nil unless length 26 | 27 | # length is stored as hex, convert back to decimal and return if it's 0 28 | length = length.to_i(16) 29 | return if length.zero? 30 | 31 | # length includes the 4 bytes of the length itself, subtract for data 32 | length -= 4 33 | 34 | # read and return the data 35 | data = reader.read(length) 36 | GitDB.log("RECEIVED COMMAND: #{data.inspect}") 37 | data 38 | end 39 | 40 | def write_command(command) 41 | # output the length 42 | writer.print length_as_hex(command) 43 | 44 | # output the data 45 | GitDB.log("SENDING COMMAND: #{command.inspect}") 46 | writer.print command 47 | writer.flush 48 | end 49 | 50 | def write(data) 51 | writer.write data 52 | writer.flush 53 | end 54 | 55 | def write_eof 56 | writer.print '0000' 57 | writer.flush 58 | end 59 | 60 | ## packs ##################################################################### 61 | 62 | def read_pack 63 | GitDB::Pack.new(reader).read 64 | end 65 | 66 | def write_pack(entries) 67 | GitDB::Pack.new(writer).write(entries) 68 | end 69 | 70 | private ###################################################################### 71 | 72 | def length_as_hex(command) 73 | hex = (command.length + 4).to_s(16).rjust(4, '0') 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lib/git-db/utility.rb: -------------------------------------------------------------------------------- 1 | module GitDB::Utility; end 2 | 3 | require 'git-db/utility/counting_io' -------------------------------------------------------------------------------- /lib/git-db/utility/counting_io.rb: -------------------------------------------------------------------------------- 1 | class GitDB::Utility::CountingIO 2 | 3 | attr_reader :io, :offset 4 | 5 | def initialize(io) 6 | @io = io 7 | @offset = 0 8 | end 9 | 10 | def flush 11 | io.flush 12 | end 13 | 14 | def read(n) 15 | data = io.read(n) 16 | @offset += n 17 | data 18 | end 19 | 20 | def write(data) 21 | io.write(data) 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /spec/git-db/commands_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe "GitDB::Commands" do 4 | 5 | it "has a receive-pack command" do 6 | GitDB::Commands.commands['receive-pack'].should_not be_nil 7 | end 8 | 9 | it "has an upload-pack command" do 10 | end 11 | 12 | it "executes a command" do 13 | @command = mock 14 | @name = 'command' 15 | @args = ['Test Repository'] 16 | 17 | GitDB::Commands.should_receive(:commands).at_least(:once).and_return({ @name => @command }) 18 | @command.should_receive(:execute).with(@args) 19 | 20 | GitDB::Commands.execute(@name, @args) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/git-db/objects/base_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "GitDB::Objects::Base" do 4 | 5 | before(:each) do 6 | @data = "Test Data" 7 | @base = GitDB::Objects::Base.new(@data) 8 | end 9 | 10 | it "initializes with data" do 11 | @base.data.should == @data 12 | end 13 | 14 | it "inspects its own properties" do 15 | @base.should_receive(:properties).and_return([:data]) 16 | @base.inspect 17 | end 18 | 19 | it "has default properties" do 20 | @base.properties.should == [:data] 21 | end 22 | 23 | it "has default raw" do 24 | @base.raw.should == @data 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /spec/git-db/objects/blob_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "GitDB::Objects::Blob" do 4 | 5 | before(:each) do 6 | @data = "test blob" 7 | @blob = GitDB::Objects::Blob.new(@data) 8 | end 9 | 10 | it "has a raw value" do 11 | @blob.raw.should == "blob #{@data.length}\000#{@data}" 12 | end 13 | 14 | it "has a type" do 15 | @blob.type.should == GitDB::OBJ_BLOB 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /spec/git-db/objects/commit_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "GitDB::Objects::Commit" do 4 | 5 | class CommitFactory 6 | attr_accessor :tree, :parents, :author, :committer, :message 7 | 8 | def to_data 9 | raw = "" 10 | raw << "tree #{tree}\n" 11 | parents.each do |parent| 12 | raw << "parent #{parent}\n" 13 | end 14 | raw << "author #{author}\n" 15 | raw << "committer #{committer}\n" 16 | raw << "\n" 17 | raw << message 18 | end 19 | end 20 | 21 | before(:each) do 22 | @raw_commit = CommitFactory.new 23 | @raw_commit.tree = "12a3b4d6c8d95475e3faf0e4a7c431c9609057f5" 24 | @raw_commit.parents = ["45bef2e266991a468b02a82e7989aca680557f7b"] 25 | @raw_commit.author = "David Dollar 1252946959 -0400" 26 | @raw_commit.committer = "David Dollar 1252946959 -0400" 27 | @raw_commit.message = "test message\n" 28 | @data = @raw_commit.to_data 29 | 30 | @commit = GitDB::Objects::Commit.new(@data) 31 | end 32 | 33 | it "has an author" do 34 | @commit.author.should == @raw_commit.author 35 | end 36 | 37 | it "has a committer" do 38 | @commit.committer.should == @raw_commit.committer 39 | end 40 | 41 | it "has a message" do 42 | @commit.message.should == @raw_commit.message 43 | end 44 | 45 | it "has parents" do 46 | @commit.parents.should == @raw_commit.parents 47 | end 48 | 49 | it "has properties" do 50 | @commit.properties.should == [:tree, :parents, :author, :committer, :message] 51 | end 52 | 53 | it "has a raw value" do 54 | @commit.raw.should == "commit #{@data.length}\000#{@data}" 55 | end 56 | 57 | it "has a tree" do 58 | @commit.tree.should == @raw_commit.tree 59 | end 60 | 61 | it "has a type" do 62 | @commit.type.should == GitDB::OBJ_COMMIT 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /spec/git-db/objects/entry_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "GitDB::Objects::Entry" do 4 | 5 | before(:each) do 6 | @sha = "1111111111111111111111111111111111111111" 7 | @perms = 100644 8 | @name = "test name" 9 | @entry = GitDB::Objects::Entry.new(@sha, @perms, @name) 10 | end 11 | 12 | it "has properties" do 13 | @entry.properties.should == [:permissions, :name] 14 | end 15 | 16 | it "converts to a hash" do 17 | @entry.to_hash[:sha].should == @sha 18 | @entry.to_hash[:permissions].should == @perms 19 | @entry.to_hash[:name].should == @name 20 | end 21 | 22 | it "converts to json" do 23 | @entry.to_json.should == @entry.to_hash.to_json 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /spec/git-db/objects/tag_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "GitDB::Objects::Tag" do 4 | 5 | before(:each) do 6 | # TODO: real tag data 7 | @data = "test-tag" 8 | @tag = GitDB::Objects::Tag.new(@data) 9 | end 10 | 11 | it "has a type" do 12 | @tag.type.should == GitDB::OBJ_TAG 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /spec/git-db/objects/tree_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "GitDB::Objects::Tree" do 4 | 5 | class TreeEntryFactory 6 | attr_reader :perms, :name, :sha 7 | 8 | def initialize(perms, name, sha) 9 | @perms = perms 10 | @name = name 11 | @sha = sha 12 | end 13 | 14 | def to_data 15 | "#{perms} #{name}\000#{GitDB.hex_to_sha1(sha)}" 16 | end 17 | end 18 | 19 | before(:each) do 20 | @entry1 = TreeEntryFactory.new(100644, "entry1", "1111111111111111111111111111111111111111") 21 | @entry2 = TreeEntryFactory.new(100444, "entry2", "2222222222222222222222222222222222222222") 22 | @data = [ @entry1.to_data, @entry2.to_data ].join('') 23 | @tree = GitDB::Objects::Tree.new(@data) 24 | end 25 | 26 | it "has entries" do 27 | @tree.entries.first.name.should == @entry1.name 28 | @tree.entries.last.name.should == @entry2.name 29 | end 30 | 31 | it "has properties" do 32 | @tree.properties.should == [:entries] 33 | end 34 | 35 | it "has a raw value" do 36 | @tree.raw.should == "tree #{@data.length}\000#{@data}" 37 | end 38 | 39 | it "has a type" do 40 | @tree.type.should == GitDB::OBJ_TREE 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /spec/git-db/objects_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe "GitDB::Objects" do 4 | 5 | describe "new_from_type" do 6 | 7 | it "can create a commit" do 8 | GitDB::Objects.new_from_type(GitDB::OBJ_COMMIT, '').should be_a(GitDB::Objects::Commit) 9 | end 10 | 11 | it "can create a tree" do 12 | GitDB::Objects.new_from_type(GitDB::OBJ_TREE, '').should be_a(GitDB::Objects::Tree) 13 | end 14 | 15 | it "can create a blob" do 16 | GitDB::Objects.new_from_type(GitDB::OBJ_BLOB, '').should be_a(GitDB::Objects::Blob) 17 | end 18 | 19 | it "can create a tag" do 20 | GitDB::Objects.new_from_type(GitDB::OBJ_TAG, '').should be_a(GitDB::Objects::Tag) 21 | end 22 | 23 | it "raises on unknown types" do 24 | lambda { GitDB::Objects.new_from_type(-1, '') }.should raise_error 25 | end 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /spec/git-db/protocol_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe "GitDB::Protocol" do 4 | 5 | describe "initialize with defaults" do 6 | before(:each) do 7 | @protocol = GitDB::Protocol.new 8 | end 9 | 10 | it "uses STDIN and STDOUT when no io is specified" do 11 | @protocol.reader.should == STDIN 12 | @protocol.writer.should == STDOUT 13 | end 14 | end 15 | 16 | describe "initialize with io" do 17 | before(:each) do 18 | @io = StringIO.new 19 | @protocol = GitDB::Protocol.new(@io) 20 | end 21 | 22 | it "sets both reader and writer to the io" do 23 | @protocol.reader.should == @io 24 | @protocol.writer.should == @io 25 | end 26 | end 27 | 28 | describe "flush" do 29 | before(:each) do 30 | @io = StringIO.new 31 | @protocol = GitDB::Protocol.new(@io) 32 | end 33 | 34 | it "can flush the writer" do 35 | @io.should_receive(:flush) 36 | @protocol.flush 37 | end 38 | end 39 | 40 | describe "read_command" do 41 | describe "valid command" do 42 | before(:each) do 43 | @io = StringIO.new("000bcommand") 44 | @protocol = GitDB::Protocol.new(@io) 45 | end 46 | 47 | it "reads the command" do 48 | @protocol.read_command.should == 'command' 49 | end 50 | end 51 | 52 | describe "eof" do 53 | before(:each) do 54 | @io = StringIO.new("0000") 55 | @protocol = GitDB::Protocol.new(@io) 56 | end 57 | 58 | it "returns nil" do 59 | @protocol.read_command.should be_nil 60 | end 61 | end 62 | end 63 | 64 | describe "write_command" do 65 | before(:each) do 66 | @io = StringIO.new 67 | @protocol = GitDB::Protocol.new(@io) 68 | end 69 | 70 | it "writes a command" do 71 | @protocol.write_command('command') 72 | @io.string.should == '000bcommand' 73 | end 74 | end 75 | 76 | describe "write" do 77 | before(:each) do 78 | @io = StringIO.new 79 | @protocol = GitDB::Protocol.new(@io) 80 | @data = "test data" 81 | end 82 | 83 | it "can write" do 84 | @io.should_receive(:write).with(@data) 85 | @protocol.write(@data) 86 | end 87 | 88 | it "flushes after write" do 89 | @io.should_receive(:flush) 90 | @protocol.write(@data) 91 | end 92 | end 93 | 94 | describe "write_eof" do 95 | before(:each) do 96 | @io = StringIO.new 97 | @protocol = GitDB::Protocol.new(@io) 98 | end 99 | 100 | it "writes eof" do 101 | @io.should_receive(:write).with("0000") 102 | @protocol.write_eof 103 | end 104 | end 105 | 106 | describe "read_pack" do 107 | before(:each) do 108 | @io = StringIO.new 109 | @protocol = GitDB::Protocol.new(@io) 110 | @pack = mock(GitDB::Pack) 111 | end 112 | 113 | it "uses GitDB::Pack to read a pack object" do 114 | GitDB::Pack.should_receive(:new).with(@io).and_return(@pack) 115 | @pack.should_receive(:read) 116 | @protocol.read_pack 117 | end 118 | end 119 | 120 | describe "write_pack" do 121 | before(:each) do 122 | @io = StringIO.new 123 | @protocol = GitDB::Protocol.new(@io) 124 | @pack = mock(GitDB::Pack) 125 | @entries = [] 126 | end 127 | 128 | it "uses GitDB::Pack to write a pack object" do 129 | GitDB::Pack.should_receive(:new).with(@io).and_return(@pack) 130 | @pack.should_receive(:write).with(@entries) 131 | @protocol.write_pack(@entries) 132 | end 133 | end 134 | 135 | end 136 | -------------------------------------------------------------------------------- /spec/git-db/utility/counting_io_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "GitDB::Utility::CountingIO" do 4 | 5 | before(:each) do 6 | @base = StringIO.new 7 | @io = GitDB::Utility::CountingIO.new(@base) 8 | end 9 | 10 | it "can flush" do 11 | @base.should_receive(:flush) 12 | @io.flush 13 | end 14 | 15 | it "can read" do 16 | @data = "00000" 17 | @bytes = @data.length 18 | 19 | @base.should_receive(:read).with(@bytes).and_return(@data) 20 | @io.read(@bytes).should == @data 21 | end 22 | 23 | it "can write" do 24 | @data = "00000" 25 | 26 | @base.should_receive(:write).with(@data) 27 | @io.write(@data) 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /spec/git-db_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe "GitDB" do 4 | 5 | describe "git utility" do 6 | it "can convert shas to/from hex/binary representation" do 7 | @hex = "6161616161616161616161616161616161616161" 8 | @sha = "aaaaaaaaaaaaaaaaaaaa" 9 | 10 | GitDB.hex_to_sha1(@hex.dup).should == @sha.dup 11 | GitDB.sha1_to_hex(@sha.dup).should == @hex.dup 12 | end 13 | 14 | it "has a null sha" do 15 | GitDB.null_sha1.should == "0000000000000000000000000000000000000000" 16 | end 17 | end 18 | 19 | describe "logging" do 20 | it "has a logger that can respond to puts" do 21 | GitDB.logger.should respond_to(:puts) 22 | end 23 | 24 | describe "with DEBUG" do 25 | before(:each) do 26 | @old_debug = ENV["DEBUG"] 27 | ENV["DEBUG"] = "1" 28 | end 29 | 30 | after(:each) do 31 | ENV["DEBUG"] = @old_debug 32 | end 33 | 34 | it "logs messages sent to log" do 35 | @logger = mock(Logger) 36 | @message = "Log This" 37 | 38 | GitDB.should_receive(:logger).and_return(@logger) 39 | @logger.should_receive(:puts).with(@message) 40 | 41 | GitDB.log(@message) 42 | end 43 | end 44 | 45 | describe "without DEBUG" do 46 | before(:each) do 47 | @old_debug = ENV["DEBUG"] 48 | ENV["DEBUG"] = nil 49 | end 50 | 51 | after(:each) do 52 | ENV["DEBUG"] = @old_debug 53 | end 54 | 55 | it "should not log messages sent to log" do 56 | @logger = mock(Logger) 57 | @message = "Log This" 58 | 59 | GitDB.should_not_receive(:logger) 60 | 61 | GitDB.log(@message) 62 | end 63 | end 64 | end 65 | 66 | describe "database" do 67 | it "returns a database for a repository" do 68 | @repository = "Test Repository" 69 | 70 | GitDB::Database.should_receive(:database).with(@repository) 71 | 72 | GitDB.database(@repository) 73 | end 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec/*,gems/*" 2 | --rails -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format progress 3 | --loadby mtime 4 | --reverse 5 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 3 | 4 | require 'rubygems' 5 | require 'git-db' 6 | require 'spec' 7 | require 'spec/autorun' 8 | 9 | Spec::Runner.configure do |config| 10 | 11 | end 12 | --------------------------------------------------------------------------------