schacon-git 1.0.6

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