schleyfox-grit 2.3.0

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