git-ce 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/git/base.rb ADDED
@@ -0,0 +1,555 @@
1
+ require 'git/base/factory'
2
+
3
+ module Git
4
+
5
+ class Base
6
+
7
+ include Git::Base::Factory
8
+
9
+ # opens a bare Git Repository - no working directory options
10
+ def self.bare(git_dir, opts = {})
11
+ self.new({:repository => git_dir}.merge(opts))
12
+ end
13
+
14
+ # clones a git repository locally
15
+ #
16
+ # repository - http://repo.or.cz/w/sinatra.git
17
+ # name - sinatra
18
+ #
19
+ # options:
20
+ # :repository
21
+ #
22
+ # :bare
23
+ # or
24
+ # :working_directory
25
+ # :index_file
26
+ #
27
+ def self.clone(repository, name, opts = {})
28
+ # run git-clone
29
+ self.new(Git::Lib.new.clone(repository, name, opts))
30
+ end
31
+
32
+ # Returns (and initialize if needed) a Git::Config instance
33
+ #
34
+ # @return [Git::Config] the current config instance.
35
+ def self.config
36
+ return @@config ||= Config.new
37
+ end
38
+
39
+ # initializes a git repository
40
+ #
41
+ # options:
42
+ # :bare
43
+ # :index
44
+ # :repository
45
+ #
46
+ def self.init(working_dir, opts = {})
47
+ opts[:working_directory] ||= working_dir
48
+ opts[:repository] ||= File.join(opts[:working_directory], '.git')
49
+
50
+ FileUtils.mkdir_p(opts[:working_directory]) if opts[:working_directory] && !File.directory?(opts[:working_directory])
51
+
52
+ init_opts = {
53
+ :bare => opts[:bare]
54
+ }
55
+
56
+ opts.delete(:working_directory) if opts[:bare]
57
+
58
+ # Submodules have a .git *file* not a .git folder.
59
+ # This file's contents point to the location of
60
+ # where the git refs are held (In the parent repo)
61
+ if File.file?('.git')
62
+ git_file = File.open('.git').read[8..-1].strip
63
+ opts[:repository] = git_file
64
+ opts[:index] = git_file + '/index'
65
+ end
66
+
67
+ Git::Lib.new(opts).init(init_opts)
68
+
69
+ self.new(opts)
70
+ end
71
+
72
+ # opens a new Git Project from a working directory
73
+ # you can specify non-standard git_dir and index file in the options
74
+ def self.open(working_dir, opts={})
75
+ self.new({:working_directory => working_dir}.merge(opts))
76
+ end
77
+
78
+ def initialize(options = {})
79
+ if working_dir = options[:working_directory]
80
+ options[:repository] ||= File.join(working_dir, '.git')
81
+ options[:index] ||= File.join(working_dir, '.git', 'index')
82
+ end
83
+ if options[:log]
84
+ @logger = options[:log]
85
+ @logger.info("Starting Git")
86
+ else
87
+ @logger = nil
88
+ end
89
+
90
+ @working_directory = options[:working_directory] ? Git::WorkingDirectory.new(options[:working_directory]) : nil
91
+ @repository = options[:repository] ? Git::Repository.new(options[:repository]) : nil
92
+ @index = options[:index] ? Git::Index.new(options[:index], false) : nil
93
+ end
94
+
95
+ # changes current working directory for a block
96
+ # to the git working directory
97
+ #
98
+ # example
99
+ # @git.chdir do
100
+ # # write files
101
+ # @git.add
102
+ # @git.commit('message')
103
+ # end
104
+ def chdir # :yields: the Git::Path
105
+ Dir.chdir(dir.path) do
106
+ yield dir.path
107
+ end
108
+ end
109
+
110
+ #g.config('user.name', 'Scott Chacon') # sets value
111
+ #g.config('user.email', 'email@email.com') # sets value
112
+ #g.config('user.name') # returns 'Scott Chacon'
113
+ #g.config # returns whole config hash
114
+ def config(name = nil, value = nil)
115
+ if(name && value)
116
+ # set value
117
+ lib.config_set(name, value)
118
+ elsif (name)
119
+ # return value
120
+ lib.config_get(name)
121
+ else
122
+ # return hash
123
+ lib.config_list
124
+ end
125
+ end
126
+
127
+ # returns a reference to the working directory
128
+ # @git.dir.path
129
+ # @git.dir.writeable?
130
+ def dir
131
+ @working_directory
132
+ end
133
+
134
+ # returns reference to the git index file
135
+ def index
136
+ @index
137
+ end
138
+
139
+ # returns reference to the git repository directory
140
+ # @git.dir.path
141
+ def repo
142
+ @repository
143
+ end
144
+
145
+ # returns the repository size in bytes
146
+ def repo_size
147
+ Dir.chdir(repo.path) do
148
+ return `du -s`.chomp.split.first.to_i
149
+ end
150
+ end
151
+
152
+ def set_index(index_file, check = true)
153
+ @lib = nil
154
+ @index = Git::Index.new(index_file.to_s, check)
155
+ end
156
+
157
+ def set_working(work_dir, check = true)
158
+ @lib = nil
159
+ @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check)
160
+ end
161
+
162
+ # returns +true+ if the branch exists locally
163
+ def is_local_branch?(branch)
164
+ branch_names = self.branches.local.map {|b| b.name}
165
+ branch_names.include?(branch)
166
+ end
167
+
168
+ # returns +true+ if the branch exists remotely
169
+ def is_remote_branch?(branch)
170
+ branch_names = self.branches.remote.map {|b| b.name}
171
+ branch_names.include?(branch)
172
+ end
173
+
174
+ # returns +true+ if the branch exists
175
+ def is_branch?(branch)
176
+ branch_names = self.branches.map {|b| b.name}
177
+ branch_names.include?(branch)
178
+ end
179
+
180
+ # this is a convenience method for accessing the class that wraps all the
181
+ # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
182
+ # class with one that uses native methods or libgit C bindings
183
+ def lib
184
+ @lib ||= Git::Lib.new(self, @logger)
185
+ end
186
+
187
+ # will run a grep for 'string' on the HEAD of the git repository
188
+ #
189
+ # to be more surgical in your grep, you can call grep() off a specific
190
+ # git object. for example:
191
+ #
192
+ # @git.object("v2.3").grep('TODO')
193
+ #
194
+ # in any case, it returns a hash of arrays of the type:
195
+ # hsh[tree-ish] = [[line_no, match], [line_no, match2]]
196
+ # hsh[tree-ish] = [[line_no, match], [line_no, match2]]
197
+ #
198
+ # so you might use it like this:
199
+ #
200
+ # @git.grep("TODO").each do |sha, arr|
201
+ # puts "in blob #{sha}:"
202
+ # arr.each do |match|
203
+ # puts "\t line #{match[0]}: '#{match[1]}'"
204
+ # end
205
+ # end
206
+ def grep(string, path_limiter = nil, opts = {})
207
+ self.object('HEAD').grep(string, path_limiter, opts)
208
+ end
209
+
210
+ # updates the repository index using the working directory content
211
+ #
212
+ # @git.add('path/to/file')
213
+ # @git.add(['path/to/file1','path/to/file2'])
214
+ # @git.add(:all => true)
215
+ #
216
+ # options:
217
+ # :all => true
218
+ #
219
+ # @param [String,Array] paths files paths to be added (optional, default='.')
220
+ # @param [Hash] options
221
+ def add(*args)
222
+ if args[0].instance_of?(String) || args[0].instance_of?(Array)
223
+ self.lib.add(args[0],args[1]||{})
224
+ else
225
+ self.lib.add('.', args[0]||{})
226
+ end
227
+ end
228
+
229
+ # removes file(s) from the git repository
230
+ def remove(path = '.', opts = {})
231
+ self.lib.remove(path, opts)
232
+ end
233
+
234
+ # resets the working directory to the provided commitish
235
+ def reset(commitish = nil, opts = {})
236
+ self.lib.reset(commitish, opts)
237
+ end
238
+
239
+ # resets the working directory to the commitish with '--hard'
240
+ def reset_hard(commitish = nil, opts = {})
241
+ opts = {:hard => true}.merge(opts)
242
+ self.lib.reset(commitish, opts)
243
+ end
244
+
245
+ # cleans the working directory
246
+ #
247
+ # options:
248
+ # :force
249
+ # :d
250
+ #
251
+ def clean(opts = {})
252
+ self.lib.clean(opts)
253
+ end
254
+
255
+ # returns the most recent tag that is reachable from a commit
256
+ #
257
+ # options:
258
+ # :all
259
+ # :tags
260
+ # :contains
261
+ # :debug
262
+ # :exact_match
263
+ # :dirty
264
+ # :abbrev
265
+ # :candidates
266
+ # :long
267
+ # :always
268
+ # :match
269
+ #
270
+ def describe(committish=nil, opts={})
271
+ self.lib.describe(committish, opts)
272
+ end
273
+
274
+ # reverts the working directory to the provided commitish.
275
+ # Accepts a range, such as comittish..HEAD
276
+ #
277
+ # options:
278
+ # :no_edit
279
+ #
280
+ def revert(commitish = nil, opts = {})
281
+ self.lib.revert(commitish, opts)
282
+ end
283
+
284
+ # commits all pending changes in the index file to the git repository
285
+ #
286
+ # options:
287
+ # :all
288
+ # :allow_empty
289
+ # :amend
290
+ # :author
291
+ #
292
+ def commit(message, opts = {})
293
+ self.lib.commit(message, opts)
294
+ end
295
+
296
+ # commits all pending changes in the index file to the git repository,
297
+ # but automatically adds all modified files without having to explicitly
298
+ # calling @git.add() on them.
299
+ def commit_all(message, opts = {})
300
+ opts = {:add_all => true}.merge(opts)
301
+ self.lib.commit(message, opts)
302
+ end
303
+
304
+ # checks out a branch as the new git working directory
305
+ def checkout(branch = 'master', opts = {})
306
+ self.lib.checkout(branch, opts)
307
+ end
308
+
309
+ # checks out an old version of a file
310
+ def checkout_file(version, file)
311
+ self.lib.checkout_file(version,file)
312
+ end
313
+
314
+ # fetches changes from a remote branch - this does not modify the working directory,
315
+ # it just gets the changes from the remote if there are any
316
+ def fetch(remote = 'origin', opts={})
317
+ self.lib.fetch(remote, opts)
318
+ end
319
+
320
+ # pushes changes to a remote repository - easiest if this is a cloned repository,
321
+ # otherwise you may have to run something like this first to setup the push parameters:
322
+ #
323
+ # @git.config('remote.remote-name.push', 'refs/heads/master:refs/heads/master')
324
+ #
325
+ def push(remote = 'origin', branch = 'master', opts = {})
326
+ # Small hack to keep backwards compatibility with the 'push(remote, branch, tags)' method signature.
327
+ opts = {:tags => opts} if [true, false].include?(opts)
328
+
329
+ self.lib.push(remote, branch, opts)
330
+ end
331
+
332
+ # merges one or more branches into the current working branch
333
+ #
334
+ # you can specify more than one branch to merge by passing an array of branches
335
+ def merge(branch, message = 'merge')
336
+ self.lib.merge(branch, message)
337
+ end
338
+
339
+ def rebase(branch)
340
+ self.lib.rebase(branch)
341
+ end
342
+
343
+ # iterates over the files which are unmerged
344
+ def each_conflict(&block) # :yields: file, your_version, their_version
345
+ self.lib.conflicts(&block)
346
+ end
347
+
348
+ # pulls the given branch from the given remote into the current branch
349
+ #
350
+ # @git.pull # pulls from origin/master
351
+ # @git.pull('upstream') # pulls from upstream/master
352
+ # @git.pull('upstream', 'develope') # pulls from upstream/develop
353
+ #
354
+ def pull(remote='origin', branch='master')
355
+ self.lib.pull(remote, branch)
356
+ end
357
+
358
+ # returns an array of Git:Remote objects
359
+ def remotes
360
+ self.lib.remotes.map { |r| Git::Remote.new(self, r) }
361
+ end
362
+
363
+ # adds a new remote to this repository
364
+ # url can be a git url or a Git::Base object if it's a local reference
365
+ #
366
+ # @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
367
+ # @git.fetch('scotts_git')
368
+ # @git.merge('scotts_git/master')
369
+ #
370
+ # Options:
371
+ # :fetch => true
372
+ # :track => <branch_name>
373
+ def add_remote(name, url, opts = {})
374
+ url = url.repo.path if url.is_a?(Git::Base)
375
+ self.lib.remote_add(name, url, opts)
376
+ Git::Remote.new(self, name)
377
+ end
378
+
379
+ # sets the url for a remote
380
+ # url can be a git url or a Git::Base object if it's a local reference
381
+ #
382
+ # @git.set_remote_url('scotts_git', 'git://repo.or.cz/rubygit.git')
383
+ #
384
+ def set_remote_url(name, url)
385
+ url = url.repo.path if url.is_a?(Git::Base)
386
+ self.lib.remote_set_url(name, url)
387
+ Git::Remote.new(self, name)
388
+ end
389
+
390
+ # removes a remote from this repository
391
+ #
392
+ # @git.remove_remote('scott_git')
393
+ def remove_remote(name)
394
+ self.lib.remote_remove(name)
395
+ end
396
+
397
+ # returns an array of all Git::Tag objects for this repository
398
+ def tags
399
+ self.lib.tags.map { |r| tag(r) }
400
+ end
401
+
402
+ # Creates a new git tag (Git::Tag)
403
+ # Usage:
404
+ # repo.add_tag('tag_name', object_reference)
405
+ # repo.add_tag('tag_name', object_reference, {:options => 'here'})
406
+ # repo.add_tag('tag_name', {:options => 'here'})
407
+ #
408
+ # Options:
409
+ # :a | :annotate -> true
410
+ # :d -> true
411
+ # :f -> true
412
+ # :m | :message -> String
413
+ # :s -> true
414
+ #
415
+ def add_tag(name, *opts)
416
+ self.lib.tag(name, *opts)
417
+ self.tag(name)
418
+ end
419
+
420
+ # deletes a tag
421
+ def delete_tag(name)
422
+ self.lib.tag(name, {:d => true})
423
+ end
424
+
425
+ # creates an archive file of the given tree-ish
426
+ def archive(treeish, file = nil, opts = {})
427
+ self.object(treeish).archive(file, opts)
428
+ end
429
+
430
+ # repacks the repository
431
+ def repack
432
+ self.lib.repack
433
+ end
434
+
435
+ def gc
436
+ self.lib.gc
437
+ end
438
+
439
+ def apply(file)
440
+ if File.exist?(file)
441
+ self.lib.apply(file)
442
+ end
443
+ end
444
+
445
+ def apply_mail(file)
446
+ self.lib.apply_mail(file) if File.exist?(file)
447
+ end
448
+
449
+ # Shows objects
450
+ #
451
+ # @param [String|NilClass] objectish the target object reference (nil == HEAD)
452
+ # @param [String|NilClass] path the path of the file to be shown
453
+ # @return [String] the object information
454
+ def show(objectish=nil, path=nil)
455
+ self.lib.show(objectish, path)
456
+ end
457
+
458
+ ## LOWER LEVEL INDEX OPERATIONS ##
459
+
460
+ def with_index(new_index) # :yields: new_index
461
+ old_index = @index
462
+ set_index(new_index, false)
463
+ return_value = yield @index
464
+ set_index(old_index)
465
+ return_value
466
+ end
467
+
468
+ def with_temp_index &blk
469
+ # Workaround for JRUBY, since they handle the TempFile path different.
470
+ # MUST be improved to be safer and OS independent.
471
+ if RUBY_PLATFORM == 'java'
472
+ temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}"
473
+ else
474
+ tempfile = Tempfile.new('temp-index')
475
+ temp_path = tempfile.path
476
+ tempfile.close
477
+ tempfile.unlink
478
+ end
479
+
480
+ with_index(temp_path, &blk)
481
+ end
482
+
483
+ def checkout_index(opts = {})
484
+ self.lib.checkout_index(opts)
485
+ end
486
+
487
+ def read_tree(treeish, opts = {})
488
+ self.lib.read_tree(treeish, opts)
489
+ end
490
+
491
+ def write_tree
492
+ self.lib.write_tree
493
+ end
494
+
495
+ def write_and_commit_tree(opts = {})
496
+ tree = write_tree
497
+ commit_tree(tree, opts)
498
+ end
499
+
500
+ def update_ref(branch, commit)
501
+ branch(branch).update_ref(commit)
502
+ end
503
+
504
+
505
+ def ls_files(location=nil)
506
+ self.lib.ls_files(location)
507
+ end
508
+
509
+ def with_working(work_dir) # :yields: the Git::WorkingDirectory
510
+ return_value = false
511
+ old_working = @working_directory
512
+ set_working(work_dir)
513
+ Dir.chdir work_dir do
514
+ return_value = yield @working_directory
515
+ end
516
+ set_working(old_working)
517
+ return_value
518
+ end
519
+
520
+ def with_temp_working &blk
521
+ tempfile = Tempfile.new("temp-workdir")
522
+ temp_dir = tempfile.path
523
+ tempfile.close
524
+ tempfile.unlink
525
+ Dir.mkdir(temp_dir, 0700)
526
+ with_working(temp_dir, &blk)
527
+ end
528
+
529
+
530
+ # runs git rev-parse to convert the objectish to a full sha
531
+ #
532
+ # @git.revparse("HEAD^^")
533
+ # @git.revparse('v2.4^{tree}')
534
+ # @git.revparse('v2.4:/doc/index.html')
535
+ #
536
+ def revparse(objectish)
537
+ self.lib.revparse(objectish)
538
+ end
539
+
540
+ def ls_tree(objectish)
541
+ self.lib.ls_tree(objectish)
542
+ end
543
+
544
+ def cat_file(objectish)
545
+ self.lib.object_contents(objectish)
546
+ end
547
+
548
+ # returns the name of the branch the working directory is currently on
549
+ def current_branch
550
+ self.lib.branch_current
551
+ end
552
+
553
+ end
554
+
555
+ end
data/lib/git/branch.rb ADDED
@@ -0,0 +1,131 @@
1
+ require 'git/path'
2
+
3
+ module Git
4
+
5
+ class Branch < Path
6
+
7
+ attr_accessor :full, :remote, :name
8
+
9
+ def initialize(base, name)
10
+ @full = name
11
+ @base = base
12
+ @gcommit = nil
13
+ @stashes = nil
14
+ @remote, @name = parse_name(name)
15
+ end
16
+
17
+ def gcommit
18
+ @gcommit ||= @base.gcommit(@full)
19
+ @gcommit
20
+ end
21
+
22
+ def stashes
23
+ @stashes ||= Git::Stashes.new(@base)
24
+ end
25
+
26
+ def checkout
27
+ check_if_create
28
+ @base.checkout(@full)
29
+ end
30
+
31
+ def archive(file, opts = {})
32
+ @base.lib.archive(@full, file, opts)
33
+ end
34
+
35
+ # g.branch('new_branch').in_branch do
36
+ # # create new file
37
+ # # do other stuff
38
+ # return true # auto commits and switches back
39
+ # end
40
+ def in_branch (message = 'in branch work')
41
+ old_current = @base.lib.branch_current
42
+ checkout
43
+ if yield
44
+ @base.commit_all(message)
45
+ else
46
+ @base.reset_hard
47
+ end
48
+ @base.checkout(old_current)
49
+ end
50
+
51
+ def create
52
+ check_if_create
53
+ end
54
+
55
+ def delete
56
+ @base.lib.branch_delete(@name)
57
+ end
58
+
59
+ def current
60
+ determine_current
61
+ end
62
+
63
+ def contains?(commit)
64
+ !@base.lib.branch_contains(commit, self.name).empty?
65
+ end
66
+
67
+ def merge(branch = nil, message = nil)
68
+ if branch
69
+ in_branch do
70
+ @base.merge(branch, message)
71
+ false
72
+ end
73
+ # merge a branch into this one
74
+ else
75
+ # merge this branch into the current one
76
+ @base.merge(@name)
77
+ end
78
+ end
79
+
80
+ def rebase(branch)
81
+ @base.rebase(branch)
82
+ end
83
+
84
+
85
+ def update_ref(commit)
86
+ @base.lib.update_ref(@full, commit)
87
+ end
88
+
89
+ def to_a
90
+ [@full]
91
+ end
92
+
93
+ def to_s
94
+ @full
95
+ end
96
+
97
+ private
98
+
99
+ def check_if_create
100
+ @base.lib.branch_new(@name) rescue nil
101
+ end
102
+
103
+ def determine_current
104
+ @base.lib.branch_current == @name
105
+ end
106
+
107
+ # Given a full branch name return an Array containing the remote and branch names.
108
+ #
109
+ # Removes 'remotes' from the beggining of the name (if present).
110
+ # Takes the second part (splittign by '/') as the remote name.
111
+ # Takes the rest as the repo name (can also hold one or more '/').
112
+ #
113
+ # Example:
114
+ # parse_name('master') #=> [nil, 'master']
115
+ # parse_name('origin/master') #=> ['origin', 'master']
116
+ # parse_name('remotes/origin/master') #=> ['origin', 'master']
117
+ # parse_name('origin/master/v2') #=> ['origin', 'master/v2']
118
+ #
119
+ # param [String] name branch full name.
120
+ # return [<Git::Remote,NilClass,String>] an Array containing the remote and branch names.
121
+ def parse_name(name)
122
+ if name.match(/^(?:remotes)?\/([^\/]+)\/(.+)/)
123
+ return [Git::Remote.new(@base, $1), $2]
124
+ end
125
+
126
+ return [nil, name]
127
+ end
128
+
129
+ end
130
+
131
+ end