git-db 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
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.
data/README.rdoc ADDED
@@ -0,0 +1,59 @@
1
+ = git-db
2
+
3
+ CouchDB-based git server, avoids the filesystem. (VERY ALPHA)
4
+
5
+ == Installation
6
+
7
+ * Install CouchDB on <tt>localhost</tt>, and start it up.
8
+
9
+ * Install the gem
10
+
11
+ $ gem install ddollar-git-db
12
+
13
+ * Create a <tt>git</tt> user. (Name can be whatever you like)
14
+
15
+ * Set a home directory for the user.
16
+
17
+ * Set up the <tt>git</tt> 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 <your ssh public key>
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.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
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 = "<ddollar@gmail.com>"
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
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
data/bin/git-db ADDED
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
File without changes
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'git-db'
3
+
4
+ require 'spec/expectations'
data/git-db.gemspec ADDED
@@ -0,0 +1,91 @@
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.1.2"
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{<ddollar@gmail.com>}
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
+ "features/git-db.feature",
30
+ "features/step_definitions/git-db_steps.rb",
31
+ "features/support/env.rb",
32
+ "git-db.gemspec",
33
+ "lib/git-db.rb",
34
+ "lib/git-db/commands.rb",
35
+ "lib/git-db/commands/receive-pack.rb",
36
+ "lib/git-db/commands/upload-pack.rb",
37
+ "lib/git-db/database.rb",
38
+ "lib/git-db/objects.rb",
39
+ "lib/git-db/objects/base.rb",
40
+ "lib/git-db/objects/blob.rb",
41
+ "lib/git-db/objects/commit.rb",
42
+ "lib/git-db/objects/entry.rb",
43
+ "lib/git-db/objects/tag.rb",
44
+ "lib/git-db/objects/tree.rb",
45
+ "lib/git-db/pack.rb",
46
+ "lib/git-db/protocol.rb",
47
+ "lib/git-db/utility.rb",
48
+ "lib/git-db/utility/counting_io.rb",
49
+ "spec/git-db/commands_spec.rb",
50
+ "spec/git-db/objects/base_spec.rb",
51
+ "spec/git-db/objects/entry_spec.rb",
52
+ "spec/git-db/objects/tag_spec.rb",
53
+ "spec/git-db/objects/tree_spec.rb",
54
+ "spec/git-db/utility/counting_io_spec.rb",
55
+ "spec/git-db_spec.rb",
56
+ "spec/rcov.opts",
57
+ "spec/spec.opts",
58
+ "spec/spec_helper.rb"
59
+ ]
60
+ s.homepage = %q{http://github.com/ddollar/git-db}
61
+ s.rdoc_options = ["--charset=UTF-8"]
62
+ s.require_paths = ["lib"]
63
+ s.rubygems_version = %q{1.3.5}
64
+ s.summary = %q{Database-based git server}
65
+ s.test_files = [
66
+ "spec/git-db/commands_spec.rb",
67
+ "spec/git-db/objects/base_spec.rb",
68
+ "spec/git-db/objects/entry_spec.rb",
69
+ "spec/git-db/objects/tag_spec.rb",
70
+ "spec/git-db/objects/tree_spec.rb",
71
+ "spec/git-db/utility/counting_io_spec.rb",
72
+ "spec/git-db_spec.rb",
73
+ "spec/spec_helper.rb"
74
+ ]
75
+
76
+ if s.respond_to? :specification_version then
77
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
78
+ s.specification_version = 3
79
+
80
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
81
+ s.add_development_dependency(%q<rspec>, [">= 0"])
82
+ s.add_runtime_dependency(%q<couchrest>, [">= 0"])
83
+ else
84
+ s.add_dependency(%q<rspec>, [">= 0"])
85
+ s.add_dependency(%q<couchrest>, [">= 0"])
86
+ end
87
+ else
88
+ s.add_dependency(%q<rspec>, [">= 0"])
89
+ s.add_dependency(%q<couchrest>, [">= 0"])
90
+ end
91
+ end
@@ -0,0 +1,73 @@
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
+ database.write_objects(entries)
41
+ end
42
+ end
43
+
44
+ io.write_command("unpack ok\n")
45
+ refs.each do |ref|
46
+ io.write_command("ok #{ref}\n")
47
+ end
48
+ io.write_eof
49
+ end
50
+
51
+ private
52
+
53
+ def capabilities
54
+ " report-status delete-refs ofs-delta "
55
+ end
56
+
57
+ def io
58
+ @io ||= GitDB::Protocol.new
59
+ end
60
+
61
+ def write_ref(ref, sha, needs_capabilities=true)
62
+ if needs_capabilities
63
+ header = "%s %s\000%s\n" % [ sha, ref, capabilities ]
64
+ io.write_command(header)
65
+ else
66
+ header = "%s %s\n" % [ sha, ref ]
67
+ io.write_command(header)
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ GitDB::Commands.register 'receive-pack', GitDB::Commands::ReceivePack
@@ -0,0 +1,140 @@
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
@@ -0,0 +1,21 @@
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'
@@ -0,0 +1,134 @@
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
@@ -0,0 +1,34 @@
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
@@ -0,0 +1,11 @@
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