multi_git 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/lib/multi_git/backend.rb +100 -0
  2. data/lib/multi_git/backend_set.rb +42 -0
  3. data/lib/multi_git/blob.rb +52 -0
  4. data/lib/multi_git/builder.rb +19 -0
  5. data/lib/multi_git/commit.rb +185 -0
  6. data/lib/multi_git/directory.rb +67 -0
  7. data/lib/multi_git/error.rb +67 -0
  8. data/lib/multi_git/executeable.rb +12 -0
  9. data/lib/multi_git/file.rb +35 -0
  10. data/lib/multi_git/git_backend/blob.rb +11 -0
  11. data/lib/multi_git/git_backend/cmd.rb +117 -0
  12. data/lib/multi_git/git_backend/commit.rb +75 -0
  13. data/lib/multi_git/git_backend/object.rb +34 -0
  14. data/lib/multi_git/git_backend/ref.rb +36 -0
  15. data/lib/multi_git/git_backend/repository.rb +162 -0
  16. data/lib/multi_git/git_backend/tree.rb +22 -0
  17. data/lib/multi_git/git_backend.rb +19 -0
  18. data/lib/multi_git/handle.rb +33 -0
  19. data/lib/multi_git/jgit_backend/blob.rb +10 -0
  20. data/lib/multi_git/jgit_backend/commit.rb +45 -0
  21. data/lib/multi_git/jgit_backend/object.rb +48 -0
  22. data/lib/multi_git/jgit_backend/ref.rb +117 -0
  23. data/lib/multi_git/jgit_backend/repository.rb +223 -0
  24. data/lib/multi_git/jgit_backend/rewindeable_io.rb +33 -0
  25. data/lib/multi_git/jgit_backend/tree.rb +28 -0
  26. data/lib/multi_git/jgit_backend.rb +15 -0
  27. data/lib/multi_git/object.rb +59 -0
  28. data/lib/multi_git/ref.rb +381 -0
  29. data/lib/multi_git/repository.rb +190 -0
  30. data/lib/multi_git/rugged_backend/blob.rb +8 -0
  31. data/lib/multi_git/rugged_backend/commit.rb +38 -0
  32. data/lib/multi_git/rugged_backend/object.rb +38 -0
  33. data/lib/multi_git/rugged_backend/ref.rb +32 -0
  34. data/lib/multi_git/rugged_backend/repository.rb +160 -0
  35. data/lib/multi_git/rugged_backend/tree.rb +18 -0
  36. data/lib/multi_git/rugged_backend.rb +16 -0
  37. data/lib/multi_git/submodule.rb +7 -0
  38. data/lib/multi_git/symlink.rb +42 -0
  39. data/lib/multi_git/tree/builder.rb +184 -0
  40. data/lib/multi_git/tree.rb +144 -0
  41. data/lib/multi_git/tree_entry.rb +86 -0
  42. data/lib/multi_git/utils.rb +57 -0
  43. data/lib/multi_git/version.rb +3 -0
  44. data/lib/multi_git.rb +44 -0
  45. metadata +138 -0
@@ -0,0 +1,100 @@
1
+ require 'multi_git/utils'
2
+ require 'multi_git/error'
3
+ module MultiGit
4
+
5
+ module Backend
6
+
7
+ attr :failed, :exception
8
+
9
+ def check(description, &check)
10
+ @checks ||= []
11
+ @checks << [description, check]
12
+ end
13
+
14
+ def check!
15
+ @checks.each do |description, check|
16
+ begin
17
+ result = check.call
18
+ if result == false
19
+ @failed = description
20
+ return
21
+ end
22
+ rescue Exception => e
23
+ @failed = description
24
+ @exception = e
25
+ end
26
+ end
27
+ end
28
+
29
+ private :check, :check!
30
+
31
+ # @abstract
32
+ #
33
+ # This method implements loading the backend files.
34
+ def load!
35
+ raise NotImplementedError, "Please implement #load! for #{self}"
36
+ end
37
+
38
+ # Opens a git repository.
39
+ #
40
+ # @return [Repository]
41
+ def open(directory, options = {})
42
+ load!
43
+ open(directory, options)
44
+ end
45
+
46
+ # Tests whether this backend is available.
47
+ def available?
48
+ check!
49
+ @failed.nil?
50
+ end
51
+
52
+ end
53
+
54
+ module RuggedBackend
55
+
56
+ extend Backend
57
+
58
+ check "Rugged available" do
59
+ defined?(::Rugged) ||
60
+ Utils.file_loadeable?('rugged.rb')
61
+ end
62
+
63
+ def self.load!
64
+ require 'multi_git/rugged_backend'
65
+ end
66
+
67
+ end
68
+
69
+ module JGitBackend
70
+
71
+ extend Backend
72
+
73
+ check "Using Jruby" do
74
+ RUBY_ENGINE == "jruby"
75
+ end
76
+
77
+ check "Jgit available" do
78
+ defined?(Java::OrgEclipseJgitLib::Repository)
79
+ end
80
+
81
+ def self.load!
82
+ require 'multi_git/jgit_backend'
83
+ end
84
+
85
+ end
86
+
87
+ module GitBackend
88
+
89
+ extend Backend
90
+
91
+ check "Git Executeable available" do
92
+ `git --version`
93
+ end
94
+
95
+ def self.load!
96
+ require 'multi_git/git_backend'
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,42 @@
1
+ require 'multi_git/backend'
2
+ module MultiGit
3
+
4
+ # @visibility private
5
+ # @api private
6
+ class BackendSet
7
+
8
+ def initialize
9
+ @backends = {}
10
+ @priorities = {}
11
+ end
12
+
13
+ def []=(name, options={}, value)
14
+ raise ArgumentError, "Expected a MultiGit::Backend, got #{value.inspect}" unless value.respond_to? :available?
15
+ @priorities[name] = options.fetch(:priority, 0)
16
+ @backends[name] = value
17
+ end
18
+
19
+ def [](name)
20
+ if name.kind_of? Backend
21
+ return name
22
+ elsif name == :best
23
+ return best
24
+ else
25
+ return @backends[name]
26
+ end
27
+ end
28
+
29
+ def priority(name)
30
+ @priorities[name]
31
+ end
32
+
33
+ def best
34
+ @priorities.sort_by{|_,value| -value}.find do |name, _|
35
+ be = self[name]
36
+ return be if be.available?
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,52 @@
1
+ require 'stringio'
2
+ require 'multi_git/object'
3
+ require 'multi_git/builder'
4
+ module MultiGit
5
+ module Blob
6
+
7
+ module Base
8
+ def type
9
+ :blob
10
+ end
11
+ end
12
+
13
+ class Builder < StringIO
14
+ include Base
15
+ include MultiGit::Builder
16
+
17
+ def initialize(content = nil)
18
+ super()
19
+ if content.kind_of? Blob
20
+ self << content.content
21
+ elsif content.kind_of? String
22
+ self << content
23
+ elsif content.kind_of? IO
24
+ IO.copy_stream(content, self)
25
+ elsif content
26
+ raise ArgumentError
27
+ end
28
+ end
29
+
30
+ def content
31
+ string
32
+ end
33
+
34
+ def content=(value)
35
+ self.string = value.dup
36
+ end
37
+
38
+ def >>(repo)
39
+ rewind
40
+ return repo.write(read)
41
+ end
42
+ end
43
+
44
+ include Object
45
+ include Base
46
+
47
+ def to_builder
48
+ Builder.new(self)
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ require 'multi_git/utils'
2
+ # Builders provides simple interfaces to create complex structures like trees and commits.
3
+ module MultiGit::Builder
4
+
5
+ extend MultiGit::Utils::AbstractMethods
6
+
7
+ # @!method >>(repository)
8
+ # @abstract
9
+ # Writes the content of this builder to the given repository.
10
+ # @param [Repository] repository
11
+ # @return [MultiGit::Object] the persisted object
12
+ abstract :>>
13
+
14
+ # @return [self] self
15
+ def to_builder
16
+ self
17
+ end
18
+
19
+ end
@@ -0,0 +1,185 @@
1
+ require 'forwardable'
2
+ require 'multi_git/utils'
3
+ require 'multi_git/handle'
4
+ require 'multi_git/builder'
5
+ module MultiGit
6
+
7
+ module Commit
8
+
9
+ module Base
10
+ extend Utils::AbstractMethods
11
+ extend Forwardable
12
+
13
+ # @return String
14
+ abstract :message
15
+
16
+ # @return Tree
17
+ abstract :tree
18
+
19
+ # @return [Array<Commit>]
20
+ abstract :parents
21
+
22
+ # @return [Time]
23
+ abstract :time
24
+ # @return [Handle]
25
+ abstract :author
26
+
27
+ # @return [Time]
28
+ abstract :commit_time
29
+ # @return [Handle]
30
+ abstract :committer
31
+
32
+ delegate [:[], :/] => :tree
33
+
34
+ def type
35
+ :commit
36
+ end
37
+ end
38
+
39
+ # A commit builder helps creating new commits by
40
+ # providing a simple interface, sane defaults and some
41
+ # validations.
42
+ #
43
+ # You can create a new commit using the commit builder like this:
44
+ #
45
+ # @example Commit an small tree
46
+ # #setup:
47
+ # dir = `mktemp -d`
48
+ # # example:
49
+ # builder = MultiGit::Commit::Builder.new
50
+ # builder.message = "My first commit"
51
+ # builder.by "me@example.com"
52
+ # builder.tree["a_file"] = "some content"
53
+ # # builder is now ready to be inserted
54
+ # repository = MultiGit.open(dir, init: true)
55
+ # commit = repository << builder #=> be_a MultiGit::Commit
56
+ # commit.tree['a_file'].content #=> eql "some content"
57
+ # # teardown:
58
+ # `rm -rf #{dir}`
59
+ class Builder
60
+ include MultiGit::Builder
61
+ include Base
62
+
63
+ # @return (see MultiGit::Commit::Base#message)
64
+ def message(*args)
65
+ if args.any?
66
+ self.message = args.first
67
+ return self
68
+ else
69
+ return @message
70
+ end
71
+ end
72
+
73
+ #
74
+ # @yield allows
75
+ # @return [Tree::Builder]
76
+ def tree(&block)
77
+ @tree.instance_eval(&block) if block
78
+ @tree
79
+ end
80
+ # @return [Array<Commit::Base>]
81
+ attr :parents
82
+
83
+ # @return (see MultiGit::Commit::Base#time)
84
+ attr :time
85
+ # @return (see MultiGit::Commit::Base#commit_time)
86
+ attr :commit_time
87
+
88
+ # @return (see MultiGit::Commit::Base#author)
89
+ attr :author
90
+ # @return (see MultiGit::Commit::Base#committer)
91
+ attr :committer
92
+
93
+ attr_writer :author, :committer
94
+ attr_writer :message
95
+
96
+ # @param time [Time]
97
+ def time=(time)
98
+ raise ArgumentError, "Expected a Time, got #{time.inspect}" unless time.kind_of? Time
99
+ @time = time
100
+ end
101
+
102
+ # @param time [Time]
103
+ def commit_time=(time)
104
+ raise ArgumentError, "Expected a Time, got #{time.inspect}" unless time.kind_of? Time
105
+ @commit_time = time
106
+ end
107
+
108
+ # @param handle [Handle, String]
109
+ def committer=(handle)
110
+ @committer = parse_handle(handle)
111
+ end
112
+
113
+ # @param handle [Handle, String]
114
+ def author=(handle)
115
+ @author = parse_handle(handle)
116
+ end
117
+
118
+ # DSL method to set author and committer in one step
119
+ # @param handle [Handle, String]
120
+ # @return self
121
+ def by(handle)
122
+ self.author = self.committer = handle
123
+ return self
124
+ end
125
+
126
+ def at(time)
127
+ self.time = self.commit_time = time
128
+ return self
129
+ end
130
+
131
+ def initialize(from = nil, &block)
132
+ @parents = []
133
+ if from.kind_of? Tree
134
+ @tree = from.to_builder
135
+ elsif from.kind_of? Tree::Builder
136
+ @tree = from
137
+ elsif from.kind_of? Commit
138
+ @tree = from.tree.to_builder
139
+ @parents << from
140
+ elsif from.nil?
141
+ @tree = Tree::Builder.new
142
+ end
143
+ @author = nil
144
+ @committer = nil
145
+ @time = @commit_time = Time.now
146
+ instance_eval(&block) if block
147
+ end
148
+
149
+ def >>(repo)
150
+ new_tree = repo << tree
151
+ new_parents = parents.map{|p| repo.write(p).oid }
152
+ c = committer || author || Handle::DEFAULT
153
+ a = author || committer || Handle::DEFAULT
154
+ return repo.make_commit(
155
+ :time => time,
156
+ :author => a,
157
+ :commit_time => commit_time,
158
+ :committer => c,
159
+ :parents => new_parents,
160
+ :tree => new_tree.oid,
161
+ :message => message || "Thank you for using multi_git",
162
+ :update_ref => []
163
+ )
164
+ end
165
+
166
+ protected
167
+
168
+ def parse_handle(handle)
169
+ case(handle)
170
+ when Handle then
171
+ return handle
172
+ when String then
173
+ return Handle.parse(handle)
174
+ else
175
+ raise ArgumentError, "Expected a String or a Handle, got #{handle.inspect}"
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ include Base
182
+
183
+ end
184
+ end
185
+
@@ -0,0 +1,67 @@
1
+ require 'multi_git/tree'
2
+ require 'multi_git/tree_entry'
3
+ module MultiGit
4
+
5
+ class Directory < TreeEntry
6
+
7
+ module Base
8
+ include Tree::Base
9
+ def mode
10
+ Utils::MODE_DIRECTORY
11
+ end
12
+
13
+ def parent?
14
+ !@parent.nil?
15
+ end
16
+
17
+ def size
18
+ object.size
19
+ end
20
+
21
+ def entry(key)
22
+ e = object.entry(key)
23
+ e.with_parent(self) if e
24
+ end
25
+
26
+ end
27
+
28
+ class Builder < TreeEntry::Builder
29
+ include Tree::Builder::DSL
30
+ include Base
31
+
32
+ def make_inner(*args)
33
+ if args.any?
34
+ if args[0].kind_of?(Tree::Builder)
35
+ return args[0]
36
+ elsif args[0].kind_of?(Directory)
37
+ return args[0].object.to_builder
38
+ elsif args[0].kind_of?(Tree)
39
+ return args[0].to_builder
40
+ end
41
+ end
42
+ Tree::Builder.new(*args)
43
+ end
44
+
45
+ def entry_set(key, value)
46
+ object.entry_set(key, make_entry(key, value))
47
+ end
48
+
49
+ def entries
50
+ Hash[
51
+ object.map{|entry| [entry.name, entry.with_parent(self) ] }
52
+ ]
53
+ end
54
+
55
+ end
56
+
57
+ include Base
58
+
59
+ def entries
60
+ @entries ||= Hash[
61
+ object.map{|entry| [entry.name, entry.with_parent(self) ] }
62
+ ]
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,67 @@
1
+ module MultiGit
2
+
3
+ module Error
4
+
5
+ class NotARepository < ArgumentError
6
+ include Error
7
+ end
8
+
9
+ class RepositoryNotBare < NotARepository
10
+ include Error
11
+ end
12
+
13
+ class RepositoryBare < NotARepository
14
+ include Error
15
+ end
16
+
17
+ class InvalidObjectType < ArgumentError
18
+ include Error
19
+ end
20
+
21
+ class InvalidReference < ArgumentError
22
+ include Error
23
+ end
24
+
25
+ class InvalidTraversal < ArgumentError
26
+ include Error
27
+ end
28
+
29
+ class CyclicSymlink < Exception
30
+ include Error
31
+ end
32
+
33
+ class AmbiguousReference < InvalidReference
34
+ end
35
+
36
+ class BadRevisionSyntax < InvalidReference
37
+ end
38
+
39
+ class WrongTypeForMode < Exception
40
+ include Error
41
+
42
+ def initialize(expected, actual)
43
+ super("It looks like you expected a #{expected.inspect} but referenced entry is a #{actual.inspect}")
44
+ end
45
+ end
46
+
47
+ class NotYetImplemented < NotImplementedError
48
+ include Error
49
+ end
50
+
51
+ class Internal < Exception
52
+ include Error
53
+ end
54
+
55
+ class ConcurrentRefUpdate < Exception
56
+ include Error
57
+
58
+ def initialize
59
+ super 'Another process has updated the ref you are currently updating.
60
+ This is unlikely to be a problem, no data got lost. You may simply retry the update.
61
+ If this happens frequently, you may have want to run "git gc" to remove clobber.'
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,12 @@
1
+ require 'multi_git/tree_entry'
2
+ require 'multi_git/file'
3
+ module MultiGit
4
+
5
+ class Executeable < File
6
+
7
+ def mode
8
+ Utils::MODE_EXECUTEABLE
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,35 @@
1
+ require 'multi_git/tree_entry'
2
+ require 'multi_git/blob'
3
+ require 'forwardable'
4
+ module MultiGit
5
+
6
+ class File < TreeEntry
7
+
8
+ module Base
9
+ def mode
10
+ Utils::MODE_FILE
11
+ end
12
+ end
13
+
14
+ class Builder < TreeEntry::Builder
15
+ include Base
16
+
17
+ def make_inner(*args)
18
+ if args.any?
19
+ if args[0].kind_of? Blob::Builder
20
+ return args[0]
21
+ elsif args[0].kind_of? Blob
22
+ return args[0].to_builder
23
+ end
24
+ end
25
+ Blob::Builder.new(*args)
26
+ end
27
+ end
28
+
29
+ include Base
30
+ extend Forwardable
31
+
32
+ delegate (Blob.instance_methods - Object.instance_methods) => :object
33
+ end
34
+
35
+ end
@@ -0,0 +1,11 @@
1
+ require 'multi_git/blob'
2
+ require 'multi_git/git_backend/object'
3
+ module MultiGit::GitBackend
4
+
5
+ class Blob < Object
6
+
7
+ include MultiGit::Blob
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,117 @@
1
+ require 'shellwords'
2
+ require 'open3'
3
+ require 'multi_git/error'
4
+ module MultiGit::GitBackend
5
+
6
+ class Cmd
7
+
8
+ class Error < MultiGit::Error::Internal
9
+
10
+ def self.const_missing(name)
11
+ if name =~ /\AExitCode\d+\z/
12
+ self.const_set(name, Class.new(self))
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def self.[](exit_code)
19
+ return const_get("ExitCode#{exit_code}")
20
+ end
21
+ end
22
+
23
+ def initialize(command, options = {})
24
+ @cmd = command
25
+ @opts = options
26
+ end
27
+
28
+ READ_BLOCK = lambda{|io|
29
+ io.read
30
+ }
31
+
32
+ def call_env(env, *args, &block)
33
+ s = cmd(*args)
34
+ block ||= READ_BLOCK
35
+ result = nil
36
+ message = nil
37
+ status = popen_foo(env, s.join(' ')) do | stdin, stdout, stderr |
38
+ if block.arity == 1
39
+ stdin.close
40
+ result = block.call(stdout)
41
+ else
42
+ result = block.call(stdin, stdout)
43
+ end
44
+ message = stderr.read
45
+ end
46
+ if status.exitstatus == 0
47
+ return result
48
+ else
49
+ raise Error[status.exitstatus], message.chomp
50
+ end
51
+ end
52
+
53
+ def call(*args, &block)
54
+ call_env({}, *args, &block)
55
+ end
56
+
57
+ alias [] call
58
+
59
+ private
60
+
61
+ # @api private
62
+ #
63
+ # popen3 is broken in jruby, popen4 is not available in mri :(
64
+ def popen_foo(env={}, cmd)
65
+ if IO.respond_to? :popen4
66
+ # setting the env is broken in jruby
67
+ args = ['env -i', escape_opts(env), cmd]
68
+ IO.popen4(args.join(' ')) do |_pid, *yield_args|
69
+ yield *yield_args
70
+ end
71
+ return $?
72
+ else
73
+ Open3.popen3(env, cmd,:unsetenv_others => true ) do |*yield_args,_thr|
74
+ yield *yield_args
75
+ return _thr.value
76
+ end
77
+ end
78
+ end
79
+
80
+ def cmd(*args)
81
+ [ @cmd, escape_opts(@opts), escape_args(args) ].flatten
82
+ end
83
+
84
+ def escape_args(args)
85
+ args.map{|arg|
86
+ if arg.kind_of? Hash
87
+ escape_opts(arg)
88
+ elsif arg.kind_of? Array
89
+ escape_args(arg)
90
+ else
91
+ Shellwords.escape(opt_to_string(arg))
92
+ end
93
+ }
94
+ end
95
+
96
+ def escape_opts(opts)
97
+ opts.map{|k,v|
98
+ opt_to_string(k)+'='+Shellwords.escape(v)
99
+ }
100
+ end
101
+
102
+ def opt_to_string(opt)
103
+ if opt.kind_of? Symbol
104
+ s = opt.to_s
105
+ if s.size == 1
106
+ return '-'+s
107
+ else
108
+ return '--'+s.gsub('_','-')
109
+ end
110
+ else
111
+ return opt
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ end