schacon-git 1.0.6

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/README ADDED
@@ -0,0 +1,238 @@
1
+ == Git Library for Ruby
2
+
3
+ Library for using Git in Ruby.
4
+
5
+ = Homepage
6
+
7
+ The Ruby/Git homepage is currently at :
8
+
9
+ http://jointheconversation.org/rubygit
10
+
11
+ Git public hosting of the project source code is at:
12
+
13
+ http://github/schacon/ruby-git
14
+
15
+ = Major Objects
16
+
17
+ Git::Base - this is the object returned from a Git.open or Git.clone.
18
+ Most major actions are called from this object.
19
+
20
+ Git::Object - this is the base object for your tree, blob and commit objects,
21
+ returned from @git.gtree or @git.object calls. the Git::AbstractObject will
22
+ have most of the calls in common for all those objects.
23
+
24
+ Git::Diff - returns from a @git.diff command. It is an Enumerable that returns
25
+ Git::Diff:DiffFile objects from which you can get per file patches and insertion/deletion
26
+ statistics. You can also get total statistics from the Git::Diff object directly.
27
+
28
+ Git::Status - returns from a @git.status command. It is an Enumerable that returns
29
+ Git:Status::StatusFile objects for each object in git, which includes files in the working
30
+ directory, in the index and in the repository. Similar to running 'git status' on the command
31
+ line to determine untracked and changed files.
32
+
33
+ Git::Branches - Enumerable object that holds Git::Branch objects. You can call .local or .remote
34
+ on it to filter to just your local or remote branches.
35
+
36
+ Git::Remote - A reference to a remote repository that is tracked by this repository.
37
+
38
+ Git::Log - An Enumerable object that references all the Git::Object::Commit objects that encompass
39
+ your log query, which can be constructed through methods on the Git::Log object, like:
40
+
41
+ @git.log(20).object("some_file").since("2 weeks ago").between('v2.6', 'v2.7').each { |commit| [block] }
42
+
43
+ = Examples
44
+
45
+ Here are a bunch of examples of how to use the Ruby/Git package.
46
+
47
+ First you have to remember to require rubygems if it's not. Then include the 'git' gem.
48
+
49
+ require 'rubygems'
50
+ require 'git'
51
+
52
+ Here are the operations that need read permission only.
53
+
54
+ g = Git.open (working_dir, :log => Logger.new(STDOUT))
55
+
56
+ g.index
57
+ g.index.readable?
58
+ g.index.writable?
59
+ g.repo
60
+ g.dir
61
+
62
+ g.log # returns array of Git::Commit objects
63
+ g.log.since('2 weeks ago')
64
+ g.log.between('v2.5', 'v2.6')
65
+ g.log.each {|l| puts l.sha }
66
+ g.gblob('v2.5:Makefile').log.since('2 weeks ago')
67
+
68
+ g.object('HEAD^').to_s # git show / git rev-parse
69
+ g.object('HEAD^').contents
70
+ g.object('v2.5:Makefile').size
71
+ g.object('v2.5:Makefile').sha
72
+
73
+ g.gtree(treeish)
74
+ g.gblob(treeish)
75
+ g.gcommit(treeish)
76
+
77
+
78
+ commit = g.gcommit('1cc8667014381')
79
+ commit.gtree
80
+ commit.parent.sha
81
+ commit.parents.size
82
+ commit.author.name
83
+ commit.author.email
84
+ commit.author.date.strftime("%m-%d-%y")
85
+ commit.committer.name
86
+ commit.date.strftime("%m-%d-%y")
87
+ commit.message
88
+
89
+ tree = g.gtree("HEAD^{tree}")
90
+ tree.blobs
91
+ tree.subtrees
92
+ tree.children # blobs and subtrees
93
+
94
+ g.revparse('v2.5:Makefile')
95
+
96
+ g.branches # returns Git::Branch objects
97
+ g.branches.local
98
+ g.branches.remote
99
+ g.branches[:master].gcommit
100
+ g.branches['origin/master'].gcommit
101
+
102
+ g.grep('hello') # implies HEAD
103
+ g.blob('v2.5:Makefile').grep('hello')
104
+ g.tag('v2.5').grep('hello', 'docs/')
105
+
106
+ g.diff(commit1, commit2).size
107
+ g.diff(commit1, commit2).stats
108
+ g.gtree('v2.5').diff('v2.6').insertions
109
+ g.diff('gitsearch1', 'v2.5').path('lib/')
110
+ g.diff('gitsearch1', @git.gtree('v2.5'))
111
+ g.diff('gitsearch1', 'v2.5').path('docs/').patch
112
+ g.gtree('v2.5').diff('v2.6').patch
113
+
114
+ g.gtree('v2.5').diff('v2.6').each do |file_diff|
115
+ puts file_diff.path
116
+ puts file_diff.patch
117
+ puts file_diff.blob(:src).contents
118
+ end
119
+
120
+ g.config('user.name') # returns 'Scott Chacon'
121
+ g.config # returns whole config hash
122
+
123
+ g.tag # returns array of Git::Tag objects
124
+
125
+
126
+
127
+ And here are the operations that will need to write to your git repository.
128
+
129
+
130
+ g = Git.init
131
+ Git.init('project')
132
+ Git.init('/home/schacon/proj',
133
+ { :git_dir => '/opt/git/proj.git',
134
+ :index_file => '/tmp/index'} )
135
+
136
+ g = Git.clone(URI, :name => 'name', :path => '/tmp/checkout')
137
+ g.config('user.name', 'Scott Chacon')
138
+ g.config('user.email', 'email@email.com')
139
+
140
+ g.add('.')
141
+ g.add([file1, file2])
142
+
143
+ g.remove('file.txt')
144
+ g.remove(['file.txt', 'file2.txt'])
145
+
146
+ g.commit('message')
147
+ g.commit_all('message')
148
+
149
+ g = Git.clone(repo, 'myrepo')
150
+ g.chdir do
151
+ new_file('test-file', 'blahblahblah')
152
+ g.status.changed.each do |file|
153
+ puts file.blob(:index).contents
154
+ end
155
+ end
156
+
157
+ g.reset # defaults to HEAD
158
+ g.reset_hard(Git::Commit)
159
+
160
+ g.branch('new_branch') # creates new or fetches existing
161
+ g.branch('new_branch').checkout
162
+ g.branch('new_branch').delete
163
+ g.branch('existing_branch').checkout
164
+
165
+ g.checkout('new_branch')
166
+ g.checkout(g.branch('new_branch'))
167
+
168
+ g.branch(name).merge(branch2)
169
+ g.branch(branch2).merge # merges HEAD with branch2
170
+
171
+ g.branch(name).in_branch(message) { # add files } # auto-commits
172
+ g.merge('new_branch')
173
+ g.merge('origin/remote_branch')
174
+ g.merge(b.branch('master'))
175
+ g.merge([branch1, branch2])
176
+
177
+ r = g.add_remote(name, uri) # Git::Remote
178
+ r = g.add_remote(name, Git::Base) # Git::Remote
179
+
180
+ g.remotes # array of Git::Remotes
181
+ g.remote(name).fetch
182
+ g.remote(name).remove
183
+ g.remote(name).merge
184
+ g.remote(name).merge(branch)
185
+
186
+ g.fetch
187
+ g.fetch(g.remotes.first)
188
+
189
+ g.pull
190
+ g.pull(Git::Repo, Git::Branch) # fetch and a merge
191
+
192
+ g.add_tag('tag_name') # returns Git::Tag
193
+
194
+ g.repack
195
+
196
+ g.push
197
+ g.push(g.remote('name'))
198
+
199
+
200
+ Some examples of more low-level index and tree operations
201
+
202
+ g.with_temp_index do
203
+
204
+ g.read_tree(tree3) # calls self.index.read_tree
205
+ g.read_tree(tree1, :prefix => 'hi/')
206
+
207
+ c = g.commit_tree('message')
208
+ # or #
209
+ t = g.write_tree
210
+ c = g.commit_tree(t, :message => 'message', :parents => [sha1, sha2])
211
+
212
+ g.branch('branch_name').update_ref(c)
213
+ g.update_ref(branch, c)
214
+
215
+ g.with_temp_working do # new blank working directory
216
+ g.checkout
217
+ g.checkout(another_index)
218
+ g.commit # commits to temp_index
219
+ end
220
+ end
221
+
222
+ g.set_index('/path/to/index')
223
+
224
+
225
+ g.with_index(path) do
226
+ # calls set_index, then switches back after
227
+ end
228
+
229
+ g.with_working(dir) do
230
+ # calls set_working, then switches back after
231
+ end
232
+
233
+ g.with_temp_working(dir) do
234
+ g.checkout_index(:prefix => dir, :path_limiter => path)
235
+ # do file work
236
+ g.commit # commits to index
237
+ end
238
+
data/lib/git/author.rb ADDED
@@ -0,0 +1,14 @@
1
+ module Git
2
+ class Author
3
+ attr_accessor :name, :email, :date
4
+
5
+ def initialize(author_string)
6
+ if m = /(.*?) <(.*?)> (\d+) (.*)/.match(author_string)
7
+ @name = m[1]
8
+ @email = m[2]
9
+ @date = Time.at(m[3].to_i)
10
+ end
11
+ end
12
+
13
+ end
14
+ end
data/lib/git/base.rb ADDED
@@ -0,0 +1,464 @@
1
+ module Git
2
+
3
+ class Base
4
+
5
+ @working_directory = nil
6
+ @repository = nil
7
+ @index = nil
8
+
9
+ @lib = nil
10
+ @logger = nil
11
+
12
+ # opens a bare Git Repository - no working directory options
13
+ def self.bare(git_dir, opts = {})
14
+ default = {:repository => git_dir}
15
+ git_options = default.merge(opts)
16
+
17
+ self.new(git_options)
18
+ end
19
+
20
+ # opens a new Git Project from a working directory
21
+ # you can specify non-standard git_dir and index file in the options
22
+ def self.open(working_dir, opts={})
23
+ default = {:working_directory => working_dir}
24
+ git_options = default.merge(opts)
25
+
26
+ self.new(git_options)
27
+ end
28
+
29
+ # initializes a git repository
30
+ #
31
+ # options:
32
+ # :repository
33
+ # :index_file
34
+ #
35
+ def self.init(working_dir, opts = {})
36
+ default = {:working_directory => working_dir,
37
+ :repository => File.join(working_dir, '.git')}
38
+ git_options = default.merge(opts)
39
+
40
+ if git_options[:working_directory]
41
+ # if !working_dir, make it
42
+ FileUtils.mkdir_p(git_options[:working_directory]) if !File.directory?(git_options[:working_directory])
43
+ end
44
+
45
+ # run git_init there
46
+ Git::Lib.new(git_options).init
47
+
48
+ self.new(git_options)
49
+ end
50
+
51
+ # clones a git repository locally
52
+ #
53
+ # repository - http://repo.or.cz/w/sinatra.git
54
+ # name - sinatra
55
+ #
56
+ # options:
57
+ # :repository
58
+ #
59
+ # :bare
60
+ # or
61
+ # :working_directory
62
+ # :index_file
63
+ #
64
+ def self.clone(repository, name, opts = {})
65
+ # run git-clone
66
+ self.new(Git::Lib.new.clone(repository, name, opts))
67
+ end
68
+
69
+ def initialize(options = {})
70
+ if working_dir = options[:working_directory]
71
+ options[:repository] = File.join(working_dir, '.git') if !options[:repository]
72
+ options[:index] = File.join(working_dir, '.git', 'index') if !options[:index]
73
+ end
74
+ if options[:log]
75
+ @logger = options[:log]
76
+ @logger.info("Starting Git")
77
+ end
78
+
79
+ @working_directory = Git::WorkingDirectory.new(options[:working_directory]) if options[:working_directory]
80
+ @repository = Git::Repository.new(options[:repository]) if options[:repository]
81
+ @index = Git::Index.new(options[:index], false) if options[:index]
82
+ end
83
+
84
+
85
+ # returns a reference to the working directory
86
+ # @git.dir.path
87
+ # @git.dir.writeable?
88
+ def dir
89
+ @working_directory
90
+ end
91
+
92
+ # returns reference to the git repository directory
93
+ # @git.dir.path
94
+ def repo
95
+ @repository
96
+ end
97
+
98
+ # returns reference to the git index file
99
+ def index
100
+ @index
101
+ end
102
+
103
+
104
+ def set_working(work_dir, check = true)
105
+ @lib = nil
106
+ @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check)
107
+ end
108
+
109
+ def set_index(index_file, check = true)
110
+ @lib = nil
111
+ @index = Git::Index.new(index_file.to_s, check)
112
+ end
113
+
114
+ # changes current working directory for a block
115
+ # to the git working directory
116
+ #
117
+ # example
118
+ # @git.chdir do
119
+ # # write files
120
+ # @git.add
121
+ # @git.commit('message')
122
+ # end
123
+ def chdir
124
+ Dir.chdir(dir.path) do
125
+ yield dir.path
126
+ end
127
+ end
128
+
129
+ # returns the repository size in bytes
130
+ def repo_size
131
+ size = 0
132
+ Dir.chdir(repo.path) do
133
+ (size, dot) = `du -s`.chomp.split
134
+ end
135
+ size.to_i
136
+ end
137
+
138
+ #g.config('user.name', 'Scott Chacon') # sets value
139
+ #g.config('user.email', 'email@email.com') # sets value
140
+ #g.config('user.name') # returns 'Scott Chacon'
141
+ #g.config # returns whole config hash
142
+ def config(name = nil, value = nil)
143
+ if(name && value)
144
+ # set value
145
+ lib.config_set(name, value)
146
+ elsif (name)
147
+ # return value
148
+ lib.config_get(name)
149
+ else
150
+ # return hash
151
+ lib.config_list
152
+ end
153
+ end
154
+
155
+ # factory methods
156
+
157
+ # returns a Git::Object of the appropriate type
158
+ # you can also call @git.gtree('tree'), but that's
159
+ # just for readability. If you call @git.gtree('HEAD') it will
160
+ # still return a Git::Object::Commit object.
161
+ #
162
+ # @git.object calls a factory method that will run a rev-parse
163
+ # on the objectish and determine the type of the object and return
164
+ # an appropriate object for that type
165
+ def object(objectish)
166
+ Git::Object.new(self, objectish)
167
+ end
168
+
169
+ def gtree(objectish)
170
+ Git::Object.new(self, objectish, 'tree')
171
+ end
172
+
173
+ def gcommit(objectish)
174
+ Git::Object.new(self, objectish, 'commit')
175
+ end
176
+
177
+ def gblob(objectish)
178
+ Git::Object.new(self, objectish, 'blob')
179
+ end
180
+
181
+ # returns a Git::Log object with count commits
182
+ def log(count = 30)
183
+ Git::Log.new(self, count)
184
+ end
185
+
186
+ # returns a Git::Status object
187
+ def status
188
+ Git::Status.new(self)
189
+ end
190
+
191
+ # returns a Git::Branches object of all the Git::Branch objects for this repo
192
+ def branches
193
+ Git::Branches.new(self)
194
+ end
195
+
196
+ # returns a Git::Branch object for branch_name
197
+ def branch(branch_name = 'master')
198
+ Git::Branch.new(self, branch_name)
199
+ end
200
+
201
+ # returns a Git::Remote object
202
+ def remote(remote_name = 'origin')
203
+ Git::Remote.new(self, remote_name)
204
+ end
205
+
206
+ # this is a convenience method for accessing the class that wraps all the
207
+ # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
208
+ # class with one that uses native methods or libgit C bindings
209
+ def lib
210
+ @lib ||= Git::Lib.new(self, @logger)
211
+ end
212
+
213
+ # will run a grep for 'string' on the HEAD of the git repository
214
+ #
215
+ # to be more surgical in your grep, you can call grep() off a specific
216
+ # git object. for example:
217
+ #
218
+ # @git.object("v2.3").grep('TODO')
219
+ #
220
+ # in any case, it returns a hash of arrays of the type:
221
+ # hsh[tree-ish] = [[line_no, match], [line_no, match2]]
222
+ # hsh[tree-ish] = [[line_no, match], [line_no, match2]]
223
+ #
224
+ # so you might use it like this:
225
+ #
226
+ # @git.grep("TODO").each do |sha, arr|
227
+ # puts "in blob #{sha}:"
228
+ # arr.each do |match|
229
+ # puts "\t line #{match[0]}: '#{match[1]}'"
230
+ # end
231
+ # end
232
+ def grep(string)
233
+ self.object('HEAD').grep(string)
234
+ end
235
+
236
+ # returns a Git::Diff object
237
+ def diff(objectish = 'HEAD', obj2 = nil)
238
+ Git::Diff.new(self, objectish, obj2)
239
+ end
240
+
241
+ # adds files from the working directory to the git repository
242
+ def add(path = '.')
243
+ self.lib.add(path)
244
+ end
245
+
246
+ # removes file(s) from the git repository
247
+ def remove(path = '.', opts = {})
248
+ self.lib.remove(path, opts)
249
+ end
250
+
251
+ # resets the working directory to the provided commitish
252
+ def reset(commitish = nil, opts = {})
253
+ self.lib.reset(commitish, opts)
254
+ end
255
+
256
+ # resets the working directory to the commitish with '--hard'
257
+ def reset_hard(commitish = nil, opts = {})
258
+ opts = {:hard => true}.merge(opts)
259
+ self.lib.reset(commitish, opts)
260
+ end
261
+
262
+ # commits all pending changes in the index file to the git repository
263
+ def commit(message, opts = {})
264
+ self.lib.commit(message, opts)
265
+ end
266
+
267
+ # commits all pending changes in the index file to the git repository,
268
+ # but automatically adds all modified files without having to explicitly
269
+ # calling @git.add() on them.
270
+ def commit_all(message, opts = {})
271
+ opts = {:add_all => true}.merge(opts)
272
+ self.lib.commit(message, opts)
273
+ end
274
+
275
+ # checks out a branch as the new git working directory
276
+ def checkout(branch = 'master', opts = {})
277
+ self.lib.checkout(branch, opts)
278
+ end
279
+
280
+ # checks out an old version of a file
281
+ def checkout_file(version, file)
282
+ self.lib.checkout_file(version,file)
283
+ end
284
+
285
+ # fetches changes from a remote branch - this does not modify the working directory,
286
+ # it just gets the changes from the remote if there are any
287
+ def fetch(remote = 'origin')
288
+ self.lib.fetch(remote)
289
+ end
290
+
291
+ # pushes changes to a remote repository - easiest if this is a cloned repository,
292
+ # otherwise you may have to run something like this first to setup the push parameters:
293
+ #
294
+ # @git.config('remote.remote-name.push', 'refs/heads/master:refs/heads/master')
295
+ #
296
+ def push(remote = 'origin', branch = 'master')
297
+ self.lib.push(remote, branch)
298
+ end
299
+
300
+ # merges one or more branches into the current working branch
301
+ #
302
+ # you can specify more than one branch to merge by passing an array of branches
303
+ def merge(branch, message = 'merge')
304
+ self.lib.merge(branch, message)
305
+ end
306
+
307
+ # iterates over the files which are unmerged
308
+ #
309
+ # yields file, your_version, their_version
310
+ def each_conflict(&block)
311
+ self.lib.conflicts(&block)
312
+ end
313
+
314
+ # fetches a branch from a remote and merges it into the current working branch
315
+ def pull(remote = 'origin', branch = 'master', message = 'origin pull')
316
+ fetch(remote)
317
+ merge(branch, message)
318
+ end
319
+
320
+ # returns an array of Git:Remote objects
321
+ def remotes
322
+ self.lib.remotes.map { |r| Git::Remote.new(self, r) }
323
+ end
324
+
325
+ # adds a new remote to this repository
326
+ # url can be a git url or a Git::Base object if it's a local reference
327
+ #
328
+ # @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
329
+ # @git.fetch('scotts_git')
330
+ # @git.merge('scotts_git/master')
331
+ #
332
+ def add_remote(name, url, opts = {})
333
+ if url.is_a?(Git::Base)
334
+ url = url.repo.path
335
+ end
336
+ self.lib.remote_add(name, url, opts)
337
+ Git::Remote.new(self, name)
338
+ end
339
+
340
+ # returns an array of all Git::Tag objects for this repository
341
+ def tags
342
+ self.lib.tags.map { |r| tag(r) }
343
+ end
344
+
345
+ # returns a Git::Tag object
346
+ def tag(tag_name)
347
+ Git::Object.new(self, tag_name, 'tag', true)
348
+ end
349
+
350
+ # creates a new git tag (Git::Tag)
351
+ def add_tag(tag_name)
352
+ self.lib.tag(tag_name)
353
+ tag(tag_name)
354
+ end
355
+
356
+ # creates an archive file of the given tree-ish
357
+ def archive(treeish, file = nil, opts = {})
358
+ self.object(treeish).archive(file, opts)
359
+ end
360
+
361
+ # repacks the repository
362
+ def repack
363
+ self.lib.repack
364
+ end
365
+
366
+ def gc
367
+ self.lib.gc
368
+ end
369
+
370
+
371
+ ## LOWER LEVEL INDEX OPERATIONS ##
372
+
373
+ def with_index(new_index)
374
+ old_index = @index
375
+ set_index(new_index, false)
376
+ return_value = yield @index
377
+ set_index(old_index)
378
+ return_value
379
+ end
380
+
381
+ def with_temp_index &blk
382
+ tempfile = Tempfile.new('temp-index')
383
+ temp_path = tempfile.path
384
+ tempfile.unlink
385
+ with_index(temp_path, &blk)
386
+ end
387
+
388
+ def checkout_index(opts = {})
389
+ self.lib.checkout_index(opts)
390
+ end
391
+
392
+ def read_tree(treeish, opts = {})
393
+ self.lib.read_tree(treeish, opts)
394
+ end
395
+
396
+ def write_tree
397
+ self.lib.write_tree
398
+ end
399
+
400
+ def commit_tree(tree = nil, opts = {})
401
+ Git::Object::Commit.new(self, self.lib.commit_tree(tree, opts))
402
+ end
403
+
404
+ def write_and_commit_tree(opts = {})
405
+ tree = write_tree
406
+ commit_tree(tree, opts)
407
+ end
408
+
409
+ def update_ref(branch, commit)
410
+ branch(branch).update_ref(commit)
411
+ end
412
+
413
+
414
+ def ls_files
415
+ self.lib.ls_files
416
+ end
417
+
418
+ def with_working(work_dir)
419
+ return_value = false
420
+ old_working = @working_directory
421
+ set_working(work_dir)
422
+ Dir.chdir work_dir do
423
+ return_value = yield @working_directory
424
+ end
425
+ set_working(old_working)
426
+ return_value
427
+ end
428
+
429
+ def with_temp_working &blk
430
+ tempfile = Tempfile.new("temp-workdir")
431
+ temp_dir = tempfile.path
432
+ tempfile.unlink
433
+ Dir.mkdir(temp_dir, 0700)
434
+ with_working(temp_dir, &blk)
435
+ end
436
+
437
+
438
+ # runs git rev-parse to convert the objectish to a full sha
439
+ #
440
+ # @git.revparse("HEAD^^")
441
+ # @git.revparse('v2.4^{tree}')
442
+ # @git.revparse('v2.4:/doc/index.html')
443
+ #
444
+ def revparse(objectish)
445
+ self.lib.revparse(objectish)
446
+ end
447
+
448
+ def ls_tree(objectish)
449
+ self.lib.ls_tree(objectish)
450
+ end
451
+
452
+ def cat_file(objectish)
453
+ self.lib.object_contents(objectish)
454
+ end
455
+
456
+ # returns the name of the branch the working directory is currently on
457
+ def current_branch
458
+ self.lib.branch_current
459
+ end
460
+
461
+
462
+ end
463
+
464
+ end