ddollar-git-db 0.0.2 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -4,33 +4,54 @@ CouchDB-based git server, avoids the filesystem. (VERY ALPHA)
4
4
 
5
5
  == Installation
6
6
 
7
+ * Install CouchDB on <tt>localhost</tt>, and start it up.
8
+
7
9
  * Install the gem
8
10
 
9
- $ gem install ddollar-git-db
11
+ $ gem install ddollar-git-db
10
12
 
11
13
  * Create a <tt>git</tt> user. (Name can be whatever you like)
14
+
12
15
  * Set a home directory for the user.
16
+
13
17
  * Set up the <tt>git</tt> user's authorized_keys2 file: (modify the command to match your gem particulars)
14
18
 
15
- # ~/.git/.ssh/authorized_keys2
16
- command="/usr/bin/git-db david",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <your ssh public key>
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>
17
21
 
18
22
  * Add your localhost as a remote to an existing project and push
19
23
 
20
- $ git remote add test-git-db git@localhost:my-repo.git
21
- $ git push test-git-db master
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
22
34
 
23
35
  * Please report any problems on the issue tracker.
24
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
+
25
49
  == Note on Patches/Pull Requests
26
50
 
27
51
  * Fork the project.
28
52
  * Make your feature addition or bug fix.
29
- * Add tests for it. This is important so I don't break it in a
30
- future version unintentionally.
53
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
31
54
  * Commit, do not mess with rakefile, version, or history.
32
- (if you want to have your own version, that is fine but
33
- bump version in a commit by itself I can ignore when I pull)
34
55
  * Send me a pull request. Bonus points for topic branches.
35
56
 
36
57
  == Copyright
data/Rakefile CHANGED
@@ -10,10 +10,12 @@ begin
10
10
  gem.email = "<ddollar@gmail.com>"
11
11
  gem.homepage = "http://github.com/ddollar/git-db"
12
12
  gem.authors = ["David Dollar"]
13
+
14
+ # development dependencies
13
15
  gem.add_development_dependency "rspec"
14
- gem.add_development_dependency "yard"
15
- gem.add_development_dependency "cucumber"
16
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+
17
+ # runtime dependencies
18
+ gem.add_dependency "couchrest"
17
19
  end
18
20
  Jeweler::GemcutterTasks.new
19
21
  rescue LoadError
@@ -24,34 +26,18 @@ require 'spec/rake/spectask'
24
26
  Spec::Rake::SpecTask.new(:spec) do |spec|
25
27
  spec.libs << 'lib' << 'spec'
26
28
  spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ spec.spec_opts << '--colour --format specdoc'
27
30
  end
28
31
 
29
32
  Spec::Rake::SpecTask.new(:rcov) do |spec|
30
33
  spec.libs << 'lib' << 'spec'
31
34
  spec.pattern = 'spec/**/*_spec.rb'
32
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
33
39
  end
34
40
 
35
41
  task :spec => :check_dependencies
36
42
 
37
- begin
38
- require 'cucumber/rake/task'
39
- Cucumber::Rake::Task.new(:features)
40
-
41
- task :features => :check_dependencies
42
- rescue LoadError
43
- task :features do
44
- abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
45
- end
46
- end
47
-
48
43
  task :default => :spec
49
-
50
- begin
51
- require 'yard'
52
- YARD::Rake::YardocTask.new
53
- rescue LoadError
54
- task :yardoc do
55
- abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
56
- end
57
- end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.2
data/bin/git-db CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $:.unshift('/Users/david/Code/git-db/lib')
4
3
  require 'git-db'
5
4
  include GitDB
6
5
 
@@ -12,5 +11,7 @@ if git_command
12
11
  command = git_command[1]
13
12
  repository = git_command[2]
14
13
 
15
- Git::Commands.execute(command, [repository])
14
+ repository = repository.gsub(/\.git$/, '')
15
+
16
+ GitDB::Commands.execute(command, [repository])
16
17
  end
data/git-db.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{git-db}
8
- s.version = "0.0.2"
8
+ s.version = "0.1.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["David Dollar"]
12
- s.date = %q{2009-09-13}
12
+ s.date = %q{2009-09-14}
13
13
  s.default_executable = %q{git-db}
14
14
  s.description = %q{Database-based git server}
15
15
  s.email = %q{<ddollar@gmail.com>}
@@ -31,22 +31,30 @@ Gem::Specification.new do |s|
31
31
  "features/support/env.rb",
32
32
  "git-db.gemspec",
33
33
  "lib/git-db.rb",
34
- "lib/git.rb",
35
- "lib/git/commands.rb",
36
- "lib/git/commands/receive-pack.rb",
37
- "lib/git/commands/upload-pack.rb",
38
- "lib/git/objects.rb",
39
- "lib/git/objects/base.rb",
40
- "lib/git/objects/blob.rb",
41
- "lib/git/objects/commit.rb",
42
- "lib/git/objects/entry.rb",
43
- "lib/git/objects/tag.rb",
44
- "lib/git/objects/tree.rb",
45
- "lib/git/pack.rb",
46
- "lib/git/protocol.rb",
47
- "lib/utility.rb",
48
- "lib/utility/counting_io.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",
49
55
  "spec/git-db_spec.rb",
56
+ "spec/rcov.opts",
57
+ "spec/spec.opts",
50
58
  "spec/spec_helper.rb"
51
59
  ]
52
60
  s.homepage = %q{http://github.com/ddollar/git-db}
@@ -55,7 +63,13 @@ Gem::Specification.new do |s|
55
63
  s.rubygems_version = %q{1.3.5}
56
64
  s.summary = %q{Database-based git server}
57
65
  s.test_files = [
58
- "spec/git-db_spec.rb",
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",
59
73
  "spec/spec_helper.rb"
60
74
  ]
61
75
 
@@ -65,16 +79,13 @@ Gem::Specification.new do |s|
65
79
 
66
80
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
67
81
  s.add_development_dependency(%q<rspec>, [">= 0"])
68
- s.add_development_dependency(%q<yard>, [">= 0"])
69
- s.add_development_dependency(%q<cucumber>, [">= 0"])
82
+ s.add_runtime_dependency(%q<couchrest>, [">= 0"])
70
83
  else
71
84
  s.add_dependency(%q<rspec>, [">= 0"])
72
- s.add_dependency(%q<yard>, [">= 0"])
73
- s.add_dependency(%q<cucumber>, [">= 0"])
85
+ s.add_dependency(%q<couchrest>, [">= 0"])
74
86
  end
75
87
  else
76
88
  s.add_dependency(%q<rspec>, [">= 0"])
77
- s.add_dependency(%q<yard>, [">= 0"])
78
- s.add_dependency(%q<cucumber>, [">= 0"])
89
+ s.add_dependency(%q<couchrest>, [">= 0"])
79
90
  end
80
91
  end
data/lib/git-db.rb CHANGED
@@ -1,16 +1,68 @@
1
+ require 'base64'
2
+ require 'couchrest'
1
3
  require 'logger'
2
4
 
3
5
  module GitDB;
4
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
+
5
47
  def self.logger
6
- @logger ||= File.open("/tmp/git-db.log", "w")
7
- #@logger ||= STDERR
48
+ @logger ||= STDERR
8
49
  end
9
50
 
10
51
  def self.log(message)
11
52
  logger.puts message
12
53
  end
54
+
55
+ ## database ##################################################################
56
+
57
+ def self.database(repository)
58
+ GitDB::Database.database(repository)
59
+ end
60
+
13
61
  end
14
62
 
15
- require 'git'
16
- require 'utility'
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'
@@ -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,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