git 1.2.5 → 1.2.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of git might be problematic. Click here for more details.

@@ -16,19 +16,23 @@ module Git
16
16
  # initializes a git repository
17
17
  #
18
18
  # options:
19
+ # :bare
20
+ # :index
19
21
  # :repository
20
- # :index_file
21
22
  #
22
23
  def self.init(working_dir, opts = {})
23
- opts = {
24
- :working_directory => working_dir,
25
- :repository => File.join(working_dir, '.git')
26
- }.merge(opts)
24
+ opts[:working_directory] = working_dir if !opts[:working_directory]
25
+ opts[:repository] = File.join(opts[:working_directory], '.git') if !opts[:repository]
27
26
 
28
27
  FileUtils.mkdir_p(opts[:working_directory]) if opts[:working_directory] && !File.directory?(opts[:working_directory])
29
28
 
30
- # run git_init there
31
- Git::Lib.new(opts).init
29
+ init_opts = {
30
+ :bare => opts[:bare]
31
+ }
32
+
33
+ opts.delete(:working_directory) if opts[:bare]
34
+
35
+ Git::Lib.new(opts).init(init_opts)
32
36
 
33
37
  self.new(opts)
34
38
  end
@@ -115,11 +119,9 @@ module Git
115
119
 
116
120
  # returns the repository size in bytes
117
121
  def repo_size
118
- size = 0
119
122
  Dir.chdir(repo.path) do
120
- (size, dot) = `du -s`.chomp.split
123
+ return `du -s`.chomp.split.first.to_i
121
124
  end
122
- size.to_i
123
125
  end
124
126
 
125
127
  #g.config('user.name', 'Scott Chacon') # sets value
@@ -243,9 +245,23 @@ module Git
243
245
  Git::Diff.new(self, objectish, obj2)
244
246
  end
245
247
 
246
- # adds files from the working directory to the git repository
247
- def add(path = '.')
248
- self.lib.add(path)
248
+ # updates the repository index using the workig dorectory content
249
+ #
250
+ # @git.add('path/to/file')
251
+ # @git.add(['path/to/file1','path/to/file2'])
252
+ # @git.add(:all => true)
253
+ #
254
+ # options:
255
+ # :all => true
256
+ #
257
+ # @param [String,Array] paths files paths to be added (optional, default='.')
258
+ # @param [Hash] options
259
+ def add(*args)
260
+ if args[0].instance_of?(String) || args[0].instance_of?(Array)
261
+ self.lib.add(args[0],args[1]||{})
262
+ else
263
+ self.lib.add('.', args[0]||{})
264
+ end
249
265
  end
250
266
 
251
267
  # removes file(s) from the git repository
@@ -264,12 +280,34 @@ module Git
264
280
  self.lib.reset(commitish, opts)
265
281
  end
266
282
 
283
+ # cleans the working directory
284
+ #
285
+ # options:
286
+ # :force
287
+ # :d
288
+ #
289
+ def clean(opts = {})
290
+ self.lib.clean(opts)
291
+ end
292
+
293
+ # reverts the working directory to the provided commitish.
294
+ # Accepts a range, such as comittish..HEAD
295
+ #
296
+ # options:
297
+ # :no_edit
298
+ #
299
+ def revert(commitish = nil, opts = {})
300
+ self.lib.revert(commitish, opts)
301
+ end
302
+
267
303
  # commits all pending changes in the index file to the git repository
268
304
  #
269
305
  # options:
270
- # :add_all
306
+ # :all
271
307
  # :allow_empty
308
+ # :amend
272
309
  # :author
310
+ #
273
311
  def commit(message, opts = {})
274
312
  self.lib.commit(message, opts)
275
313
  end
@@ -319,10 +357,14 @@ module Git
319
357
  self.lib.conflicts(&block)
320
358
  end
321
359
 
322
- # fetches a branch from a remote and merges it into the current working branch
323
- def pull(remote = 'origin', branch = 'master', message = 'origin pull')
324
- fetch(remote)
325
- merge(branch, message)
360
+ # pulls the given branch from the given remote into the current branch
361
+ #
362
+ # @git.pull # pulls from origin/master
363
+ # @git.pull('upstream') # pulls from upstream/master
364
+ # @git.pull('upstream', 'develope') # pulls from upstream/develop
365
+ #
366
+ def pull(remote='origin', branch='master')
367
+ self.lib.pull(remote, branch)
326
368
  end
327
369
 
328
370
  # returns an array of Git:Remote objects
@@ -337,12 +379,22 @@ module Git
337
379
  # @git.fetch('scotts_git')
338
380
  # @git.merge('scotts_git/master')
339
381
  #
382
+ # Options:
383
+ # :fetch => true
384
+ # :track => <branch_name>
340
385
  def add_remote(name, url, opts = {})
341
386
  url = url.repo.path if url.is_a?(Git::Base)
342
387
  self.lib.remote_add(name, url, opts)
343
388
  Git::Remote.new(self, name)
344
389
  end
345
390
 
391
+ # removes a remote from this repository
392
+ #
393
+ # @git.remove_remote('scott_git')
394
+ def remove_remote(name)
395
+ self.lib.remote_remove(name)
396
+ end
397
+
346
398
  # returns an array of all Git::Tag objects for this repository
347
399
  def tags
348
400
  self.lib.tags.map { |r| tag(r) }
@@ -394,9 +446,17 @@ module Git
394
446
  end
395
447
 
396
448
  def with_temp_index &blk
397
- tempfile = Tempfile.new('temp-index')
398
- temp_path = tempfile.path
399
- tempfile.unlink
449
+ # Workaround for JRUBY, since they handle the TempFile path different.
450
+ # MUST be improved to be safer and OS independent.
451
+ if RUBY_PLATFORM == 'java'
452
+ temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}"
453
+ else
454
+ tempfile = Tempfile.new('temp-index')
455
+ temp_path = tempfile.path
456
+ tempfile.close
457
+ tempfile.unlink
458
+ end
459
+
400
460
  with_index(temp_path, &blk)
401
461
  end
402
462
 
@@ -444,6 +504,7 @@ module Git
444
504
  def with_temp_working &blk
445
505
  tempfile = Tempfile.new("temp-workdir")
446
506
  temp_dir = tempfile.path
507
+ tempfile.close
447
508
  tempfile.unlink
448
509
  Dir.mkdir(temp_dir, 0700)
449
510
  with_working(temp_dir, &blk)
@@ -1,22 +1,17 @@
1
+ require 'git/path'
2
+
1
3
  module Git
4
+
2
5
  class Branch < Path
3
6
 
4
7
  attr_accessor :full, :remote, :name
5
8
 
6
9
  def initialize(base, name)
7
- @remote = nil
8
10
  @full = name
9
11
  @base = base
10
12
  @gcommit = nil
11
13
  @stashes = nil
12
-
13
- parts = name.split('/')
14
- if parts[1]
15
- @remote = Git::Remote.new(@base, parts[0])
16
- @name = parts[1]
17
- else
18
- @name = parts[0]
19
- end
14
+ @remote, @name = parse_name(name)
20
15
  end
21
16
 
22
17
  def gcommit
@@ -100,5 +95,28 @@ module Git
100
95
  @base.lib.branch_current == @name
101
96
  end
102
97
 
98
+ # Given a full branch name return an Array containing the remote and branch names.
99
+ #
100
+ # Removes 'remotes' from the beggining of the name (if present).
101
+ # Takes the second part (splittign by '/') as the remote name.
102
+ # Takes the rest as the repo name (can also hold one or more '/').
103
+ #
104
+ # Example:
105
+ # parse_name('master') #=> [nil, 'master']
106
+ # parse_name('origin/master') #=> ['origin', 'master']
107
+ # parse_name('remotes/origin/master') #=> ['origin', 'master']
108
+ # parse_name('origin/master/v2') #=> ['origin', 'master/v2']
109
+ #
110
+ # param [String] name branch full name.
111
+ # return [<Git::Remote,NilClass,String>] an Array containing the remote and branch names.
112
+ def parse_name(name)
113
+ if name.match(/^(?:remotes)?\/([^\/]+)\/(.+)/)
114
+ return [Git::Remote.new(@base, $1), $2]
115
+ end
116
+
117
+ return [nil, name]
118
+ end
119
+
103
120
  end
121
+
104
122
  end
@@ -2,6 +2,7 @@ module Git
2
2
 
3
3
  # object that holds all the available branches
4
4
  class Branches
5
+
5
6
  include Enumerable
6
7
 
7
8
  def initialize(base)
@@ -32,8 +33,29 @@ module Git
32
33
  @branches.values.each(&block)
33
34
  end
34
35
 
35
- def [](symbol)
36
- @branches[symbol.to_s]
36
+ # Returns the target branch
37
+ #
38
+ # Example:
39
+ # Given (git branch -a):
40
+ # master
41
+ # remotes/working/master
42
+ #
43
+ # g.branches['master'].full #=> 'master'
44
+ # g.branches['working/master'].full => 'remotes/working/master'
45
+ # g.branches['remotes/working/master'].full => 'remotes/working/master'
46
+ #
47
+ # @param [#to_s] branch_name the target branch name.
48
+ # @return [Git::Branch] the target branch.
49
+ def [](branch_name)
50
+ @branches.values.inject(@branches) do |branches, branch|
51
+ branches[branch.full] ||= branch
52
+
53
+ # This is how Git (version 1.7.9.5) works.
54
+ # Lets you ignore the 'remotes' if its at the beginning of the branch full name (even if is not a real remote branch).
55
+ branches[branch.full.sub('remotes/', '')] ||= branch if branch.full =~ /^remotes\/.+/
56
+
57
+ branches
58
+ end[branch_name.to_s]
37
59
  end
38
60
 
39
61
  def to_s
@@ -45,4 +67,5 @@ module Git
45
67
  end
46
68
 
47
69
  end
70
+
48
71
  end
@@ -24,9 +24,18 @@ module Git
24
24
  end
25
25
  @logger = logger
26
26
  end
27
-
28
- def init
29
- command('init')
27
+
28
+ # creates or reinitializes the repository
29
+ #
30
+ # options:
31
+ # :bare
32
+ # :working_directory
33
+ #
34
+ def init(opts={})
35
+ arr_opts = []
36
+ arr_opts << '--bare' if opts[:bare]
37
+
38
+ command('init', arr_opts, false)
30
39
  end
31
40
 
32
41
  # tries to clone the given repo
@@ -35,9 +44,10 @@ module Git
35
44
  # {:working_directory} otherwise
36
45
  #
37
46
  # accepts options:
38
- # :remote:: name of remote (rather than 'origin')
39
- # :bare:: no working directory
40
- # :depth:: the number of commits back to pull
47
+ # :remote:: name of remote (rather than 'origin')
48
+ # :bare:: no working directory
49
+ # :recursive:: after the clone is created, initialize all submodules within, using their default settings.
50
+ # :depth:: the number of commits back to pull
41
51
  #
42
52
  # TODO - make this work with SSH password or auth_key
43
53
  #
@@ -47,8 +57,10 @@ module Git
47
57
 
48
58
  arr_opts = []
49
59
  arr_opts << "--bare" if opts[:bare]
60
+ arr_opts << "--recursive" if opts[:recursive]
50
61
  arr_opts << "-o" << opts[:remote] if opts[:remote]
51
62
  arr_opts << "--depth" << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
63
+ arr_opts << "--config" << opts[:config] if opts[:config]
52
64
 
53
65
  arr_opts << '--'
54
66
  arr_opts << repository
@@ -62,36 +74,29 @@ module Git
62
74
 
63
75
  ## READ COMMANDS ##
64
76
 
77
+ def log_commits(opts={})
78
+ arr_opts = log_common_options(opts)
65
79
 
66
- def log_commits(opts = {})
67
- arr_opts = ['--pretty=oneline']
68
- arr_opts << "-#{opts[:count]}" if opts[:count]
69
- arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
70
- arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
71
- arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
72
- arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
73
- arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
74
- arr_opts << opts[:object] if opts[:object].is_a? String
75
- arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
80
+ arr_opts << '--pretty=oneline'
81
+
82
+ arr_opts += log_path_options(opts)
76
83
 
77
84
  command_lines('log', arr_opts, true).map { |l| l.split.first }
78
85
  end
79
86
 
80
- def full_log_commits(opts = {})
81
- arr_opts = ['--pretty=raw']
82
- arr_opts << "-#{opts[:count]}" if opts[:count]
87
+ def full_log_commits(opts={})
88
+ arr_opts = log_common_options(opts)
89
+
90
+ arr_opts << '--pretty=raw'
83
91
  arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
84
- arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
85
- arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
86
- arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
87
- arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
88
- arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
89
- arr_opts << opts[:object] if opts[:object].is_a? String
90
- arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
92
+
93
+ arr_opts += log_path_options(opts)
91
94
 
92
95
  full_log = command_lines('log', arr_opts, true)
93
96
  process_commit_data(full_log)
94
97
  end
98
+
99
+
95
100
 
96
101
  def revparse(string)
97
102
  return string if string =~ /[A-Fa-f0-9]{40}/ # passing in a sha - just no-op it
@@ -271,26 +276,12 @@ module Git
271
276
 
272
277
  # compares the index and the working directory
273
278
  def diff_files
274
- hsh = {}
275
- command_lines('diff-files').each do |line|
276
- (info, file) = line.split("\t")
277
- (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
278
- hsh[file] = {:path => file, :mode_file => mode_src.to_s[1, 7], :mode_index => mode_dest,
279
- :sha_file => sha_src, :sha_index => sha_dest, :type => type}
280
- end
281
- hsh
279
+ diff_as_hash('diff-files')
282
280
  end
283
281
 
284
282
  # compares the index and the repository
285
283
  def diff_index(treeish)
286
- hsh = {}
287
- command_lines('diff-index', treeish).each do |line|
288
- (info, file) = line.split("\t")
289
- (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
290
- hsh[file] = {:path => file, :mode_repo => mode_src.to_s[1, 7], :mode_index => mode_dest,
291
- :sha_repo => sha_src, :sha_index => sha_dest, :type => type}
292
- end
293
- hsh
284
+ diff_as_hash('diff-index', treeish)
294
285
  end
295
286
 
296
287
  def ls_files(location=nil)
@@ -321,7 +312,7 @@ module Git
321
312
  end
322
313
 
323
314
  def config_get(name)
324
- do_get = lambda do
315
+ do_get = lambda do |path|
325
316
  command('config', ['--get', name])
326
317
  end
327
318
 
@@ -362,24 +353,7 @@ module Git
362
353
  end
363
354
 
364
355
  def parse_config(file)
365
- hsh = {}
366
356
  parse_config_list command_lines('config', ['--list', '--file', file], false)
367
- #hsh = {}
368
- #file = File.expand_path(file)
369
- #if File.file?(file)
370
- # current_section = nil
371
- # File.readlines(file).each do |line|
372
- # if m = /\[(\w+)\]/.match(line)
373
- # current_section = m[1]
374
- # elsif m = /\[(\w+?) "(.*?)"\]/.match(line)
375
- # current_section = "#{m[1]}.#{m[2]}"
376
- # elsif m = /(\w+?) = (.*)/.match(line)
377
- # key = "#{current_section}.#{m[1]}"
378
- # hsh[key] = m[2]
379
- # end
380
- # end
381
- #end
382
- #hsh
383
357
  end
384
358
 
385
359
  ## WRITE COMMANDS ##
@@ -391,14 +365,31 @@ module Git
391
365
  def global_config_set(name, value)
392
366
  command('config', ['--global', name, value], false)
393
367
  end
394
-
395
- def add(path = '.')
396
- arr_opts = ['--']
397
- if path.is_a?(Array)
398
- arr_opts += path
399
- else
400
- arr_opts << path
401
- end
368
+
369
+ # updates the repository index using the workig dorectory content
370
+ #
371
+ # lib.add('path/to/file')
372
+ # lib.add(['path/to/file1','path/to/file2'])
373
+ # lib.add(:all => true)
374
+ #
375
+ # options:
376
+ # :all => true
377
+ # :force => true
378
+ #
379
+ # @param [String,Array] paths files paths to be added to the repository
380
+ # @param [Hash] options
381
+ def add(paths='.',options={})
382
+ arr_opts = []
383
+
384
+ arr_opts << '--all' if options[:all]
385
+ arr_opts << '--force' if options[:force]
386
+
387
+ arr_opts << '--'
388
+
389
+ arr_opts << paths
390
+
391
+ arr_opts.flatten!
392
+
402
393
  command('add', arr_opts)
403
394
  end
404
395
 
@@ -416,10 +407,13 @@ module Git
416
407
  end
417
408
 
418
409
  def commit(message, opts = {})
419
- arr_opts = ['-m', message]
420
- arr_opts << '-a' if opts[:add_all]
410
+ arr_opts = []
411
+ arr_opts << "--message=#{message}" if message
412
+ arr_opts << '--amend' << '--no-edit' if opts[:amend]
413
+ arr_opts << '--all' if opts[:add_all] || opts[:all]
421
414
  arr_opts << '--allow-empty' if opts[:allow_empty]
422
- arr_opts << "--author" << opts[:author] if opts[:author]
415
+ arr_opts << "--author=#{opts[:author]}" if opts[:author]
416
+
423
417
  command('commit', arr_opts)
424
418
  end
425
419
 
@@ -429,7 +423,26 @@ module Git
429
423
  arr_opts << commit if commit
430
424
  command('reset', arr_opts)
431
425
  end
426
+
427
+ def clean(opts = {})
428
+ arr_opts = []
429
+ arr_opts << '--force' if opts[:force]
430
+ arr_opts << '-d' if opts[:d]
431
+
432
+ command('clean', arr_opts)
433
+ end
432
434
 
435
+ def revert(commitish, opts = {})
436
+ # Forcing --no-edit as default since it's not an interactive session.
437
+ opts = {:no_edit => true}.merge(opts)
438
+
439
+ arr_opts = []
440
+ arr_opts << '--no-edit' if opts[:no_edit]
441
+ arr_opts << commitish
442
+
443
+ command('revert', arr_opts)
444
+ end
445
+
433
446
  def apply(patch_file)
434
447
  arr_opts = []
435
448
  arr_opts << '--' << patch_file if patch_file
@@ -527,7 +540,8 @@ module Git
527
540
 
528
541
  def remote_add(name, url, opts = {})
529
542
  arr_opts = ['add']
530
- arr_opts << '-f' if opts[:with_fetch]
543
+ arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
544
+ arr_opts << '-t' << opts[:track] if opts[:track]
531
545
  arr_opts << '--'
532
546
  arr_opts << name
533
547
  arr_opts << url
@@ -535,10 +549,8 @@ module Git
535
549
  command('remote', arr_opts)
536
550
  end
537
551
 
538
- # this is documented as such, but seems broken for some reason
539
- # i'll try to get around it some other way later
540
552
  def remote_remove(name)
541
- command('remote', ['rm', '--', name])
553
+ command('remote', ['rm', name])
542
554
  end
543
555
 
544
556
  def remotes
@@ -562,7 +574,11 @@ module Git
562
574
  command('push', [remote, branch])
563
575
  command('push', ['--tags', remote]) if tags
564
576
  end
565
-
577
+
578
+ def pull(remote='origin', branch='master')
579
+ command('pull', [remote, branch])
580
+ end
581
+
566
582
  def tag_sha(tag_name)
567
583
  head = File.join(@git_dir, 'refs', 'tags', tag_name)
568
584
  return File.read(head).chomp if File.exists?(head)
@@ -674,11 +690,13 @@ module Git
674
690
 
675
691
  def command(cmd, opts = [], chdir = true, redirect = '', &block)
676
692
  ENV['GIT_DIR'] = @git_dir
677
- ENV['GIT_INDEX_FILE'] = @git_index_file
678
693
  ENV['GIT_WORK_TREE'] = @git_work_dir
694
+ ENV['GIT_INDEX_FILE'] = @git_index_file
695
+
679
696
  path = @git_work_dir || @git_dir || @path
680
697
 
681
698
  opts = [opts].flatten.map {|s| escape(s) }.join(' ')
699
+
682
700
  git_cmd = "git #{cmd} #{opts} #{redirect} 2>&1"
683
701
 
684
702
  out = nil
@@ -701,6 +719,60 @@ module Git
701
719
  end
702
720
  out
703
721
  end
722
+
723
+ # Takes the diff command line output (as Array) and parse it into a Hash
724
+ #
725
+ # @param [String] diff_command the diff commadn to be used
726
+ # @param [Array] opts the diff options to be used
727
+ # @return [Hash] the diff as Hash
728
+ def diff_as_hash(diff_command, opts=[])
729
+ command_lines(diff_command, opts).inject({}) do |memo, line|
730
+ info, file = line.split("\t")
731
+ mode_src, mode_dest, sha_src, sha_dest, type = info.split
732
+
733
+ memo[file] = {
734
+ :mode_index => mode_dest,
735
+ :mode_repo => mode_src.to_s[1, 7],
736
+ :path => file,
737
+ :sha_repo => sha_src,
738
+ :sha_index => sha_dest,
739
+ :type => type
740
+ }
741
+
742
+ memo
743
+ end
744
+ end
745
+
746
+ # Returns an array holding the common options for the log commands
747
+ #
748
+ # @param [Hash] opts the given options
749
+ # @return [Array] the set of common options that the log command will use
750
+ def log_common_options(opts)
751
+ arr_opts = []
752
+
753
+ arr_opts << "-#{opts[:count]}" if opts[:count]
754
+ arr_opts << "--no-color"
755
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
756
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
757
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
758
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
759
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
760
+
761
+ arr_opts
762
+ end
763
+
764
+ # Retrurns an array holding path options for the log commands
765
+ #
766
+ # @param [Hash] opts the given options
767
+ # @return [Array] the set of path options that the log command will use
768
+ def log_path_options(opts)
769
+ arr_opts = []
770
+
771
+ arr_opts << opts[:object] if opts[:object].is_a? String
772
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter]
773
+
774
+ arr_opts
775
+ end
704
776
 
705
777
  def run_command(git_cmd, &block)
706
778
  if block_given?