schleyfox-grit 2.3.0

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/lib/grit/git.rb ADDED
@@ -0,0 +1,324 @@
1
+ require 'tempfile'
2
+ module Grit
3
+
4
+ class Git
5
+ class GitTimeout < RuntimeError
6
+ attr_reader :command, :bytes_read
7
+
8
+ def initialize(command = nil, bytes_read = nil)
9
+ @command = command
10
+ @bytes_read = bytes_read
11
+ end
12
+ end
13
+
14
+ undef_method :clone
15
+
16
+ include GitRuby
17
+
18
+ def exist?
19
+ File.exist?(self.git_dir)
20
+ end
21
+
22
+ def put_raw_object(content, type)
23
+ ruby_git.put_raw_object(content, type)
24
+ end
25
+
26
+ def object_exists?(object_id)
27
+ ruby_git.object_exists?(object_id)
28
+ end
29
+
30
+ def select_existing_objects(object_ids)
31
+ object_ids.select do |object_id|
32
+ object_exists?(object_id)
33
+ end
34
+ end
35
+
36
+ class << self
37
+ attr_accessor :git_binary, :git_timeout, :git_max_size
38
+ end
39
+
40
+ if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
41
+ self.git_binary = "git" # using search path
42
+ else
43
+ self.git_binary = "/usr/bin/env git"
44
+ end
45
+ self.git_timeout = 10
46
+ self.git_max_size = 5242880 # 5.megabytes
47
+
48
+ def self.with_timeout(timeout = 10.seconds)
49
+ old_timeout = Grit::Git.git_timeout
50
+ Grit::Git.git_timeout = timeout
51
+ yield
52
+ Grit::Git.git_timeout = old_timeout
53
+ end
54
+
55
+ attr_accessor :git_dir, :bytes_read, :work_tree
56
+
57
+ def initialize(git_dir)
58
+ self.git_dir = git_dir
59
+ self.work_tree = git_dir.gsub(/\/\.git$/,'')
60
+ self.bytes_read = 0
61
+ end
62
+
63
+ def shell_escape(str)
64
+ str.to_s.gsub("'", "\\\\'").gsub(";", '\\;')
65
+ end
66
+ alias_method :e, :shell_escape
67
+
68
+ # Check if a normal file exists on the filesystem
69
+ # +file+ is the relative path from the Git dir
70
+ #
71
+ # Returns Boolean
72
+ def fs_exist?(file)
73
+ File.exist?(File.join(self.git_dir, file))
74
+ end
75
+
76
+ # Read a normal file from the filesystem.
77
+ # +file+ is the relative path from the Git dir
78
+ #
79
+ # Returns the String contents of the file
80
+ def fs_read(file)
81
+ File.read(File.join(self.git_dir, file))
82
+ end
83
+
84
+ # Write a normal file to the filesystem.
85
+ # +file+ is the relative path from the Git dir
86
+ # +contents+ is the String content to be written
87
+ #
88
+ # Returns nothing
89
+ def fs_write(file, contents)
90
+ path = File.join(self.git_dir, file)
91
+ FileUtils.mkdir_p(File.dirname(path))
92
+ File.open(path, 'w') do |f|
93
+ f.write(contents)
94
+ end
95
+ end
96
+
97
+ # Delete a normal file from the filesystem
98
+ # +file+ is the relative path from the Git dir
99
+ #
100
+ # Returns nothing
101
+ def fs_delete(file)
102
+ FileUtils.rm_rf(File.join(self.git_dir, file))
103
+ end
104
+
105
+ # Move a normal file
106
+ # +from+ is the relative path to the current file
107
+ # +to+ is the relative path to the destination file
108
+ #
109
+ # Returns nothing
110
+ def fs_move(from, to)
111
+ FileUtils.mv(File.join(self.git_dir, from), File.join(self.git_dir, to))
112
+ end
113
+
114
+ # Make a directory
115
+ # +dir+ is the relative path to the directory to create
116
+ #
117
+ # Returns nothing
118
+ def fs_mkdir(dir)
119
+ FileUtils.mkdir_p(File.join(self.git_dir, dir))
120
+ end
121
+
122
+ # Chmod the the file or dir and everything beneath
123
+ # +file+ is the relative path from the Git dir
124
+ #
125
+ # Returns nothing
126
+ def fs_chmod(mode, file = '/')
127
+ FileUtils.chmod_R(mode, File.join(self.git_dir, file))
128
+ end
129
+
130
+ def list_remotes
131
+ remotes = []
132
+ Dir.chdir(File.join(self.git_dir, 'refs/remotes')) do
133
+ remotes = Dir.glob('*')
134
+ end
135
+ remotes
136
+ rescue
137
+ []
138
+ end
139
+
140
+ def create_tempfile(seed, unlink = false)
141
+ path = Tempfile.new(seed).path
142
+ File.unlink(path) if unlink
143
+ return path
144
+ end
145
+
146
+ def commit_from_sha(id)
147
+ git_ruby_repo = GitRuby::Repository.new(self.git_dir)
148
+ object = git_ruby_repo.get_object_by_sha1(id)
149
+
150
+ if object.type == :commit
151
+ id
152
+ elsif object.type == :tag
153
+ object.object
154
+ else
155
+ ''
156
+ end
157
+ end
158
+
159
+ def check_applies(head_sha, applies_sha)
160
+ git_index = create_tempfile('index', true)
161
+ (o1, exit1) = raw_git("git read-tree #{head_sha} 2>/dev/null", git_index)
162
+ (o2, exit2) = raw_git("git diff #{applies_sha}^ #{applies_sha} | git apply --check --cached >/dev/null 2>/dev/null", git_index)
163
+ return (exit1 + exit2)
164
+ end
165
+
166
+ def get_patch(applies_sha)
167
+ git_index = create_tempfile('index', true)
168
+ (patch, exit2) = raw_git("git diff #{applies_sha}^ #{applies_sha}", git_index)
169
+ patch
170
+ end
171
+
172
+ def apply_patch(head_sha, patch)
173
+ git_index = create_tempfile('index', true)
174
+
175
+ git_patch = create_tempfile('patch')
176
+ File.open(git_patch, 'w+') { |f| f.print patch }
177
+
178
+ raw_git("git read-tree #{head_sha} 2>/dev/null", git_index)
179
+ (op, exit) = raw_git("git apply --cached < #{git_patch}", git_index)
180
+ if exit == 0
181
+ return raw_git("git write-tree", git_index).first.chomp
182
+ end
183
+ false
184
+ end
185
+
186
+ # RAW CALLS WITH ENV SETTINGS
187
+ def raw_git_call(command, index)
188
+ tmp = ENV['GIT_INDEX_FILE']
189
+ ENV['GIT_INDEX_FILE'] = index
190
+ out = `#{command}`
191
+ after = ENV['GIT_INDEX_FILE'] # someone fucking with us ??
192
+ ENV['GIT_INDEX_FILE'] = tmp
193
+ if after != index
194
+ raise 'environment was changed for the git call'
195
+ end
196
+ [out, $?.exitstatus]
197
+ end
198
+
199
+ def raw_git(command, index)
200
+ output = nil
201
+ Dir.chdir(self.git_dir) do
202
+ output = raw_git_call(command, index)
203
+ end
204
+ output
205
+ end
206
+ # RAW CALLS WITH ENV SETTINGS END
207
+
208
+
209
+
210
+ # Run the given git command with the specified arguments and return
211
+ # the result as a String
212
+ # +cmd+ is the command
213
+ # +options+ is a hash of Ruby style options
214
+ # +args+ is the list of arguments (to be joined by spaces)
215
+ #
216
+ # Examples
217
+ # git.rev_list({:max_count => 10, :header => true}, "master")
218
+ #
219
+ # Returns String
220
+ def method_missing(cmd, options = {}, *args, &block)
221
+ run('', cmd, '', options, args, &block)
222
+ end
223
+
224
+ # Bypass any pure Ruby implementations and go straight to the native Git command
225
+ #
226
+ # Returns String
227
+ def native(cmd, options = {}, *args, &block)
228
+ method_missing(cmd, options, *args, &block)
229
+ end
230
+
231
+ def run(prefix, cmd, postfix, options, args, &block)
232
+ timeout = options.delete(:timeout) rescue nil
233
+ timeout = true if timeout.nil?
234
+
235
+ base = options.delete(:base) rescue nil
236
+ base = true if base.nil?
237
+
238
+ opt_args = transform_options(options)
239
+
240
+ if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
241
+ ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|' || Grit.no_quote) ? a : "\"#{e(a)}\"" }
242
+ gitdir = base ? "--git-dir=\"#{self.git_dir}\"" : ""
243
+ call = "#{prefix}#{Git.git_binary} #{gitdir} #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
244
+ else
245
+ ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|' || Grit.no_quote) ? a : "'#{e(a)}'" }
246
+ gitdir = base ? "--git-dir='#{self.git_dir}'" : ""
247
+ call = "#{prefix}#{Git.git_binary} #{gitdir} #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
248
+ end
249
+ Grit.log(call) if Grit.debug
250
+ response, err = timeout ? sh(call, &block) : wild_sh(call, &block)
251
+ Grit.log(response) if Grit.debug
252
+ Grit.log(err) if Grit.debug
253
+ response
254
+ end
255
+
256
+ def sh(command, &block)
257
+ ret, err = '', ''
258
+ max = self.class.git_max_size
259
+ Open3.popen3(command) do |stdin, stdout, stderr|
260
+ block.call(stdin) if block
261
+ Timeout.timeout(self.class.git_timeout) do
262
+ while tmp = stdout.read(8192)
263
+ ret << tmp
264
+ raise GitTimeout.new(command, ret.size) if ret.size > max
265
+ end
266
+ end
267
+
268
+ while tmp = stderr.read(8192)
269
+ err << tmp
270
+ end
271
+ end
272
+ [ret, err]
273
+ rescue Timeout::Error, Grit::Git::GitTimeout
274
+ raise GitTimeout.new(command, ret.size)
275
+ end
276
+
277
+ def wild_sh(command, &block)
278
+ ret, err = '', ''
279
+ Open3.popen3(command) do |stdin, stdout, stderr|
280
+ block.call(stdin) if block
281
+ while tmp = stdout.read(8192)
282
+ ret << tmp
283
+ end
284
+
285
+ while tmp = stderr.read(8192)
286
+ err << tmp
287
+ end
288
+ end
289
+ [ret, err]
290
+ end
291
+
292
+ # Transform Ruby style options into git command line options
293
+ # +options+ is a hash of Ruby style options
294
+ #
295
+ # Returns String[]
296
+ # e.g. ["--max-count=10", "--header"]
297
+ def transform_options(options)
298
+ args = []
299
+ options.keys.each do |opt|
300
+ if opt.to_s.size == 1
301
+ if options[opt] == true
302
+ args << "-#{opt}"
303
+ elsif options[opt] == false
304
+ # ignore
305
+ else
306
+ val = options.delete(opt)
307
+ args << "-#{opt.to_s} '#{e(val)}'"
308
+ end
309
+ else
310
+ if options[opt] == true
311
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
312
+ elsif options[opt] == false
313
+ # ignore
314
+ else
315
+ val = options.delete(opt)
316
+ args << "--#{opt.to_s.gsub(/_/, '-')}='#{e(val)}'"
317
+ end
318
+ end
319
+ end
320
+ args
321
+ end
322
+ end # Git
323
+
324
+ end # Grit
data/lib/grit/index.rb ADDED
@@ -0,0 +1,197 @@
1
+ module Grit
2
+
3
+ class Index
4
+ # Public: Gets/Sets the Grit::Repo to which this index belongs.
5
+ attr_accessor :repo
6
+
7
+ # Public: Gets/Sets the Hash tree map that holds the changes to be made
8
+ # in the next commit.
9
+ attr_accessor :tree
10
+
11
+ # Public: Gets/Sets the Grit::Tree object representing the tree upon
12
+ # which the next commit will be based.
13
+ attr_accessor :current_tree
14
+
15
+ # Initialize a new Index object.
16
+ #
17
+ # repo - The Grit::Repo to which the index belongs.
18
+ #
19
+ # Returns the newly initialized Grit::Index.
20
+ def initialize(repo)
21
+ self.repo = repo
22
+ self.tree = {}
23
+ self.current_tree = nil
24
+ end
25
+
26
+ # Public: Add a file to the index.
27
+ #
28
+ # path - The String file path including filename (no slash prefix).
29
+ # data - The String binary contents of the file.
30
+ #
31
+ # Returns nothing.
32
+ def add(path, data)
33
+ path = path.split('/')
34
+ filename = path.pop
35
+
36
+ current = self.tree
37
+
38
+ path.each do |dir|
39
+ current[dir] ||= {}
40
+ node = current[dir]
41
+ current = node
42
+ end
43
+
44
+ current[filename] = data
45
+ end
46
+
47
+ # Public: Delete the given file from the index.
48
+ #
49
+ # path - The String file path including filename (no slash prefix).
50
+ #
51
+ # Returns nothing.
52
+ def delete(path)
53
+ add(path, false)
54
+ end
55
+
56
+ # Public: Read the contents of the given Tree into the index to use as a
57
+ # starting point for the index.
58
+ #
59
+ # tree - The String branch/tag/sha of the Git tree object.
60
+ #
61
+ # Returns nothing.
62
+ def read_tree(tree)
63
+ self.current_tree = self.repo.tree(tree)
64
+ end
65
+
66
+ # Public: Commit the contents of the index. This method supports two
67
+ # formats for arguments:
68
+ #
69
+ # message - The String commit message.
70
+ # options - An optional Hash of index options.
71
+ # :parents - Array of String commit SHA1s or Grit::Commit
72
+ # objects to attach this commit to to form a
73
+ # new head (default: nil).
74
+ # :actor - The Grit::Actor details of the user making
75
+ # the commit (default: nil).
76
+ # :last_tree - The String SHA1 of a tree to compare with
77
+ # in order to avoid making empty commits
78
+ # (default: nil).
79
+ # :head - The String branch name to write this head to
80
+ # (default: "master").
81
+ # :committed_date - The Time that the commit was made.
82
+ # (Default: Time.now)
83
+ # :authored_date - The Time that the commit was authored.
84
+ # (Default: committed_date)
85
+ #
86
+ # The legacy argument style looks like:
87
+ #
88
+ # message - The String commit message.
89
+ # parents - Array of String commit SHA1s or Grit::Commit objects to
90
+ # attach this commit to to form a new head (default: nil).
91
+ # actor - The Grit::Actor details of the user making the commit
92
+ # (default: nil).
93
+ # last_tree - The String SHA1 of a tree to compare with in order to avoid
94
+ # making empty commits (default: nil).
95
+ # head - The String branch name to write this head to
96
+ # (default: "master").
97
+ #
98
+ # Returns a String of the SHA1 of the new commit.
99
+ def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
100
+ if parents.is_a?(Hash)
101
+ actor = parents[:actor]
102
+ committer = parents[:committer]
103
+ author = parents[:author]
104
+ last_tree = parents[:last_tree]
105
+ head = parents[:head]
106
+ committed_date = parents[:committed_date]
107
+ authored_date = parents[:authored_date]
108
+ parents = parents[:parents]
109
+ end
110
+
111
+ committer ||= actor
112
+ author ||= committer
113
+
114
+ tree_sha1 = write_tree(self.tree, self.current_tree)
115
+
116
+ # don't write identical commits
117
+ return false if tree_sha1 == last_tree
118
+
119
+ contents = []
120
+ contents << ['tree', tree_sha1].join(' ')
121
+ parents.each do |p|
122
+ contents << ['parent', p].join(' ')
123
+ end if parents
124
+
125
+ committer ||= begin
126
+ config = Config.new(self.repo)
127
+ Actor.new(config['user.name'], config['user.email'])
128
+ end
129
+ author ||= committer
130
+ committed_date ||= Time.now
131
+ authored_date ||= committed_date
132
+
133
+ contents << ['author', author.output(authored_date)].join(' ')
134
+ contents << ['committer', committer.output(committed_date)].join(' ')
135
+ contents << ''
136
+ contents << message
137
+
138
+ commit_sha1 = self.repo.git.put_raw_object(contents.join("\n"), 'commit')
139
+
140
+ self.repo.update_ref(head, commit_sha1)
141
+ end
142
+
143
+ # Recursively write a tree to the index.
144
+ #
145
+ # tree - The Hash tree map:
146
+ # key - The String directory or filename.
147
+ # val - The Hash submap or the String contents of the file.
148
+ # now_tree - The Grit::Tree representing the a previous tree upon which
149
+ # this tree will be based (default: nil).
150
+ #
151
+ # Returns the String SHA1 String of the tree.
152
+ def write_tree(tree, now_tree = nil)
153
+ tree_contents = {}
154
+
155
+ # fill in original tree
156
+ now_tree.contents.each do |obj|
157
+ sha = [obj.id].pack("H*")
158
+ k = obj.name
159
+ k += '/' if (obj.class == Grit::Tree)
160
+ tmode = obj.mode.to_i.to_s ## remove zero-padding
161
+ tree_contents[k] = "%s %s\0%s" % [tmode, obj.name, sha]
162
+ end if now_tree
163
+
164
+ # overwrite with new tree contents
165
+ tree.each do |k, v|
166
+ case v
167
+ when String
168
+ sha = write_blob(v)
169
+ sha = [sha].pack("H*")
170
+ str = "%s %s\0%s" % ['100644', k, sha]
171
+ tree_contents[k] = str
172
+ when Hash
173
+ ctree = now_tree/k if now_tree
174
+ sha = write_tree(v, ctree)
175
+ sha = [sha].pack("H*")
176
+ str = "%s %s\0%s" % ['40000', k, sha]
177
+ tree_contents[k + '/'] = str
178
+ when false
179
+ tree_contents.delete(k)
180
+ end
181
+ end
182
+
183
+ tr = tree_contents.sort.map { |k, v| v }.join('')
184
+ self.repo.git.put_raw_object(tr, 'tree')
185
+ end
186
+
187
+ # Write a blob to the index.
188
+ #
189
+ # data - The String data to write.
190
+ #
191
+ # Returns the String SHA1 of the new blob.
192
+ def write_blob(data)
193
+ self.repo.git.put_raw_object(data, 'blob')
194
+ end
195
+ end # Index
196
+
197
+ end # Grit