git-db 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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