├── .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 |
--------------------------------------------------------------------------------