Class: Git::Lib

Inherits:
Object
  • Object
show all
Defined in:
lib/git/lib.rb

Constant Summary collapse

CAT_FILE_HEADER_LINE =
/\A(?<key>\w+) (?<value>.*)\z/
BRANCH_LINE_REGEXP =
/
  ^
    # Prefix indicates if this branch is checked out. The prefix is one of:
    (?:
      (?<current>\*[[:blank:]]) |  # Current branch (checked out in the current worktree)
      (?<worktree>\+[[:blank:]]) | # Branch checked out in a different worktree
      [[:blank:]]{2}               # Branch not checked out
    )

    # The branch's full refname
    (?:
      (?<not_a_branch>\(not[[:blank:]]a[[:blank:]]branch\)) |
      (?:\(HEAD[[:blank:]]detached[[:blank:]]at[[:blank:]](?<detached_ref>[^\)]+)\)) |
      (?<refname>[^[[:blank:]]]+)
    )

    # Optional symref
    # If this ref is a symbolic reference, this is the ref referenced
    (?:
      [[:blank:]]->[[:blank:]](?<symref>.*)
    )?
  $
/x

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base = nil, logger = nil) ⇒ Lib

Create a new Git::Lib object

Parameters:

  • base (Git::Base, Hash) (defaults to: nil)

    An object that passes in values for @git_work_dir, @git_dir, and @git_index_file

  • logger (Logger) (defaults to: nil)

Options Hash (base):

  • :working_directory (Pathname)
  • :repository (Pathname)
  • :index (Pathname)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/git/lib.rb', line 50

def initialize(base = nil, logger = nil)
  @git_dir = nil
  @git_index_file = nil
  @git_work_dir = nil
  @path = nil
  @logger = logger || Logger.new(nil)

  if base.is_a?(Git::Base)
    @git_dir = base.repo.path
    @git_index_file = base.index.path if base.index
    @git_work_dir = base.dir.path if base.dir
  elsif base.is_a?(Hash)
    @git_dir = base[:repository]
    @git_index_file = base[:index]
    @git_work_dir = base[:working_directory]
  end
end

Instance Attribute Details

#git_dirPathname (readonly)

The path to the Git repository directory. The default is "#{git_work_dir}/.git".

Returns:

  • (Pathname)

    the Git repository directory.

See Also:

  • repository](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefrepositoryarepository)


28
29
30
# File 'lib/git/lib.rb', line 28

def git_dir
  @git_dir
end

#git_index_filePathname (readonly)

The Git index file used to stage changes (using git add) before they are committed.

Returns:

  • (Pathname)

    the Git index file

See Also:

  • index file](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefindexaindex)


37
38
39
# File 'lib/git/lib.rb', line 37

def git_index_file
  @git_index_file
end

#git_work_dirPathname (readonly)

The path to the Git working copy. The default is '"./.git"'.

Returns:

  • (Pathname)

    the path to the Git working copy.

See Also:

  • working tree](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefworkingtreeaworkingtree)


19
20
21
# File 'lib/git/lib.rb', line 19

def git_work_dir
  @git_work_dir
end

Class Method Details

.warn_if_old_command(lib)



1179
1180
1181
1182
1183
1184
1185
1186
# File 'lib/git/lib.rb', line 1179

def self.warn_if_old_command(lib)
  return true if @version_checked
  @version_checked = true
  unless lib.meets_required_version?
    $stderr.puts "[WARNING] The git gem requires git #{lib.required_command_version.join('.')} or later, but only found #{lib.current_command_version.join('.')}. You should probably upgrade."
  end
  true
end

Instance Method Details

#add(paths = '.', options = {})

updates the repository index using the working directory content

lib.add('path/to/file') lib.add(['path/to/file1','path/to/file2']) lib.add(:all => true)

options: :all => true :force => true

Parameters:

  • paths (String, Array) (defaults to: '.')

    files paths to be added to the repository

  • options (Hash) (defaults to: {})


685
686
687
688
689
690
691
692
693
694
695
696
697
698
# File 'lib/git/lib.rb', line 685

def add(paths='.',options={})
  arr_opts = []

  arr_opts << '--all' if options[:all]
  arr_opts << '--force' if options[:force]

  arr_opts << '--'

  arr_opts << paths

  arr_opts.flatten!

  command('add', *arr_opts)
end

#apply(patch_file)



793
794
795
796
797
# File 'lib/git/lib.rb', line 793

def apply(patch_file)
  arr_opts = []
  arr_opts << '--' << patch_file if patch_file
  command('apply', *arr_opts)
end

#apply_mail(patch_file)



799
800
801
802
803
# File 'lib/git/lib.rb', line 799

def apply_mail(patch_file)
  arr_opts = []
  arr_opts << '--' << patch_file if patch_file
  command('am', *arr_opts)
end

#archive(sha, file = nil, opts = {})

creates an archive file

options :format (zip, tar) :prefix :remote :path



1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
# File 'lib/git/lib.rb', line 1111

def archive(sha, file = nil, opts = {})
  opts[:format] ||= 'zip'

  if opts[:format] == 'tgz'
    opts[:format] = 'tar'
    opts[:add_gzip] = true
  end

  if !file
    tempfile = Tempfile.new('archive')
    file = tempfile.path
    # delete it now, before we write to it, so that Ruby doesn't delete it
    # when it finalizes the Tempfile.
    tempfile.close!
  end

  arr_opts = []
  arr_opts << "--format=#{opts[:format]}" if opts[:format]
  arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
  arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
  arr_opts << sha
  arr_opts << '--' << opts[:path] if opts[:path]

  f = File.open(file, 'wb')
  command('archive', *arr_opts, out: f)
  f.close

  if opts[:add_gzip]
    file_content = File.read(file)
    Zlib::GzipWriter.open(file) do |gz|
      gz.write(file_content)
    end
  end
  return file
end

#branch_contains(commit, branch_name = "")



481
482
483
# File 'lib/git/lib.rb', line 481

def branch_contains(commit, branch_name="")
  command("branch",  branch_name, "--contains", commit)
end

#branch_current



477
478
479
# File 'lib/git/lib.rb', line 477

def branch_current
  branches_all.select { |b| b[1] }.first[0] rescue nil
end

#branch_delete(branch)



844
845
846
# File 'lib/git/lib.rb', line 844

def branch_delete(branch)
  command('branch', '-D', branch)
end

#branch_new(branch)



840
841
842
# File 'lib/git/lib.rb', line 840

def branch_new(branch)
  command('branch', branch)
end

#branches_all



420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/git/lib.rb', line 420

def branches_all
  command_lines('branch', '-a').map do |line|
    match_data = line.match(BRANCH_LINE_REGEXP)
    raise Git::UnexpectedResultError, 'Unexpected branch line format' unless match_data
    next nil if match_data[:not_a_branch] || match_data[:detached_ref]
    [
      match_data[:refname],
      !match_data[:current].nil?,
      !match_data[:worktree].nil?,
      match_data[:symref]
    ]
  end.compact
end

#change_head_branch(branch_name)



392
393
394
# File 'lib/git/lib.rb', line 392

def change_head_branch(branch_name)
  command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
end

#checkout(branch = nil, opts = {})

Runs checkout command to checkout or create branch

accepts options: :new_branch :force :start_point

Parameters:

  • branch (String) (defaults to: nil)
  • opts (Hash) (defaults to: {})


857
858
859
860
861
862
863
864
865
866
867
868
869
870
# File 'lib/git/lib.rb', line 857

def checkout(branch = nil, opts = {})
  if branch.is_a?(Hash) && opts == {}
    opts = branch
    branch = nil
  end

  arr_opts = []
  arr_opts << '-b' if opts[:new_branch] || opts[:b]
  arr_opts << '--force' if opts[:force] || opts[:f]
  arr_opts << branch if branch
  arr_opts << opts[:start_point] if opts[:start_point] && arr_opts.include?('-b')

  command('checkout', *arr_opts)
end

#checkout_file(version, file)



872
873
874
875
876
877
# File 'lib/git/lib.rb', line 872

def checkout_file(version, file)
  arr_opts = []
  arr_opts << version
  arr_opts << file
  command('checkout', *arr_opts)
end

#checkout_index(opts = {})



1094
1095
1096
1097
1098
1099
1100
1101
1102
# File 'lib/git/lib.rb', line 1094

def checkout_index(opts = {})
  arr_opts = []
  arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
  arr_opts << "--force" if opts[:force]
  arr_opts << "--all" if opts[:all]
  arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String

  command('checkout-index', *arr_opts)
end

#clean(opts = {})



772
773
774
775
776
777
778
779
780
# File 'lib/git/lib.rb', line 772

def clean(opts = {})
  arr_opts = []
  arr_opts << '--force' if opts[:force]
  arr_opts << '-ff' if opts[:ff]
  arr_opts << '-d' if opts[:d]
  arr_opts << '-x' if opts[:x]

  command('clean', *arr_opts)
end

#clone(repository_url, directory, opts = {}) ⇒ Hash

TODO:

make this work with SSH password or auth_key

Clones a repository into a newly created directory

Parameters:

  • repository_url (String)

    the URL of the repository to clone

  • directory (String, nil)

    the directory to clone into

    If nil, the repository is cloned into a directory with the same name as the repository.

  • opts (Hash) (defaults to: {})

    the options for this command

Options Hash (opts):

  • :bare (Boolean) — default: false

    if true, clone as a bare repository

  • :branch (String)

    the branch to checkout

  • :config (String, Array)

    one or more configuration options to set

  • :depth (Integer)

    the number of commits back to pull

  • :filter (String)

    specify partial clone

  • :mirror (String)

    set up a mirror of the source repository

  • :origin (String)

    the name of the remote

  • :path (String)

    an optional prefix for the directory parameter

  • :remote (String)

    the name of the remote

  • :recursive (Boolean)

    after the clone is created, initialize all submodules within, using their default settings

  • :timeout (Numeric, nil)

    the number of seconds to wait for the command to complete

    See #command for more information about :timeout

Returns:

  • (Hash)

    the options to pass to Base.new



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/git/lib.rb', line 111

def clone(repository_url, directory, opts = {})
  @path = opts[:path] || '.'
  clone_dir = opts[:path] ? File.join(@path, directory) : directory

  arr_opts = []
  arr_opts << '--bare' if opts[:bare]
  arr_opts << '--branch' << opts[:branch] if opts[:branch]
  arr_opts << '--depth' << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
  arr_opts << '--filter' << opts[:filter] if opts[:filter]
  Array(opts[:config]).each { |c| arr_opts << '--config' << c }
  arr_opts << '--origin' << opts[:remote] || opts[:origin] if opts[:remote] || opts[:origin]
  arr_opts << '--recursive' if opts[:recursive]
  arr_opts << '--mirror' if opts[:mirror]

  arr_opts << '--'

  arr_opts << repository_url
  arr_opts << clone_dir

  command('clone', *arr_opts, timeout: opts[:timeout])

  return_base_opts_from_clone(clone_dir, opts)
end

#commit(message, opts = {})

Takes the commit message with the options and executes the commit command

accepts options: :amend :all :allow_empty :author :date :no_verify :allow_empty_message :gpg_sign (accepts true or a gpg key ID as a String) :no_gpg_sign (conflicts with :gpg_sign)

Parameters:

  • message (String)

    the commit message to be used

  • opts (Hash) (defaults to: {})

    the commit options to be used



738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
# File 'lib/git/lib.rb', line 738

def commit(message, opts = {})
  arr_opts = []
  arr_opts << "--message=#{message}" if message
  arr_opts << '--amend' << '--no-edit' if opts[:amend]
  arr_opts << '--all' if opts[:add_all] || opts[:all]
  arr_opts << '--allow-empty' if opts[:allow_empty]
  arr_opts << "--author=#{opts[:author]}" if opts[:author]
  arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
  arr_opts << '--no-verify' if opts[:no_verify]
  arr_opts << '--allow-empty-message' if opts[:allow_empty_message]

  if opts[:gpg_sign] && opts[:no_gpg_sign]
    raise ArgumentError, 'cannot specify :gpg_sign and :no_gpg_sign'
  elsif opts[:gpg_sign]
    arr_opts <<
      if opts[:gpg_sign] == true
        '--gpg-sign'
      else
        "--gpg-sign=#{opts[:gpg_sign]}"
      end
  elsif opts[:no_gpg_sign]
    arr_opts << '--no-gpg-sign'
  end

  command('commit', *arr_opts)
end

#commit_data(sha)

returns useful array of raw commit object data



253
254
255
256
257
# File 'lib/git/lib.rb', line 253

def commit_data(sha)
  sha = sha.to_s
  cdata = command_lines('cat-file', 'commit', sha)
  process_commit_data(cdata, sha)
end

#commit_tree(tree, opts = {})



1080
1081
1082
1083
1084
1085
1086
1087
1088
# File 'lib/git/lib.rb', line 1080

def commit_tree(tree, opts = {})
  opts[:message] ||= "commit tree #{tree}"
  arr_opts = []
  arr_opts << tree
  arr_opts << '-p' << opts[:parent] if opts[:parent]
  Array(opts[:parents]).each { |p| arr_opts << '-p' << p } if opts[:parents]
  arr_opts << '-m' << opts[:message]
  command('commit-tree', *arr_opts)
end

#compare_version_to(*other_version) ⇒ Integer

Returns current_command_version <=> other_version

Examples:

lib.current_command_version #=> [2, 42, 0]

lib.compare_version_to(2, 41, 0) #=> 1
lib.compare_version_to(2, 42, 0) #=> 0
lib.compare_version_to(2, 43, 0) #=> -1

Parameters:

  • other_version (Array<Object>)

    the other version to compare to

Returns:

  • (Integer)

    -1 if this version is less than other_version, 0 if equal, or 1 if greater than



1167
1168
1169
# File 'lib/git/lib.rb', line 1167

def compare_version_to(*other_version)
  current_command_version <=> other_version
end

#config_get(name)



617
618
619
# File 'lib/git/lib.rb', line 617

def config_get(name)
  command('config', '--get', name, chdir: @git_dir)
end

#config_list



625
626
627
# File 'lib/git/lib.rb', line 625

def config_list
  parse_config_list command_lines('config', '--list', chdir: @git_dir)
end

#config_remote(name)



607
608
609
610
611
612
613
614
615
# File 'lib/git/lib.rb', line 607

def config_remote(name)
  hsh = {}
  config_list.each do |key, value|
    if /remote.#{name}/.match(key)
      hsh[key.gsub("remote.#{name}.", '')] = value
    end
  end
  hsh
end

#config_set(name, value, options = {})

WRITE COMMANDS ##



661
662
663
664
665
666
667
# File 'lib/git/lib.rb', line 661

def config_set(name, value, options = {})
  if options[:file].to_s.empty?
    command('config', name, value)
  else
    command('config', '--file', options[:file], name, value)
  end
end

#conflicts

:yields: file, your, their



911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
# File 'lib/git/lib.rb', line 911

def conflicts # :yields: file, your, their
  self.unmerged.each do |f|
    Tempfile.create("YOUR-#{File.basename(f)}") do |your|
      command('show', ":2:#{f}", out: your)
      your.close

      Tempfile.create("THEIR-#{File.basename(f)}") do |their|
        command('show', ":3:#{f}", out: their)
        their.close

        yield(f, your.path, their.path)
      end
    end
  end
end

#current_command_version

returns the current version of git, as an Array of Fixnums.



1148
1149
1150
1151
1152
1153
# File 'lib/git/lib.rb', line 1148

def current_command_version
  output = command('version')
  version = output[/\d+(\.\d+)+/]
  version_parts = version.split('.').collect { |i| i.to_i }
  version_parts.fill(0, version_parts.length...3)
end

#describe(committish = nil, opts = {})

Returns most recent tag that is reachable from a commit

accepts options: :all :tags :contains :debug :exact_match :dirty :abbrev :candidates :long :always :math

@param [String|NilClass] committish target commit sha or object name @param [Symbol=>Object] opts the given options @return [String] the tag name



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/git/lib.rb', line 183

def describe(committish=nil, opts={})
  arr_opts = []

  arr_opts << '--all' if opts[:all]
  arr_opts << '--tags' if opts[:tags]
  arr_opts << '--contains' if opts[:contains]
  arr_opts << '--debug' if opts[:debug]
  arr_opts << '--long' if opts[:long]
  arr_opts << '--always' if opts[:always]
  arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]

  arr_opts << '--dirty' if opts[:dirty] == true
  arr_opts << "--dirty=#{opts[:dirty]}" if opts[:dirty].is_a?(String)

  arr_opts << "--abbrev=#{opts[:abbrev]}" if opts[:abbrev]
  arr_opts << "--candidates=#{opts[:candidates]}" if opts[:candidates]
  arr_opts << "--match=#{opts[:match]}" if opts[:match]

  arr_opts << committish if committish

  return command('describe', *arr_opts)
end

#diff_files

compares the index and the working directory



559
560
561
# File 'lib/git/lib.rb', line 559

def diff_files
  diff_as_hash('diff-files')
end

#diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})



515
516
517
518
519
520
521
522
# File 'lib/git/lib.rb', line 515

def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
  diff_opts = ['-p']
  diff_opts << obj1
  diff_opts << obj2 if obj2.is_a?(String)
  diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String

  command('diff', *diff_opts)
end

#diff_index(treeish)

compares the index and the repository



564
565
566
# File 'lib/git/lib.rb', line 564

def diff_index(treeish)
  diff_as_hash('diff-index', treeish)
end

#diff_name_status(reference1 = nil, reference2 = nil, opts = {})



544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/git/lib.rb', line 544

def diff_name_status(reference1 = nil, reference2 = nil, opts = {})
  opts_arr = ['--name-status']
  opts_arr << reference1 if reference1
  opts_arr << reference2 if reference2

  opts_arr << '--' << opts[:path] if opts[:path]

  command_lines('diff', *opts_arr).inject({}) do |memo, line|
    status, path = line.split("\t")
    memo[path] = status
    memo
  end
end

#diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})



524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
# File 'lib/git/lib.rb', line 524

def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
  diff_opts = ['--numstat']
  diff_opts << obj1
  diff_opts << obj2 if obj2.is_a?(String)
  diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String

  hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}

  command_lines('diff', *diff_opts).each do |file|
    (insertions, deletions, filename) = file.split("\t")
    hsh[:total][:insertions] += insertions.to_i
    hsh[:total][:deletions] += deletions.to_i
    hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
    hsh[:total][:files] += 1
    hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
  end

  hsh
end

#each_cat_file_header(data)



280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/git/lib.rb', line 280

def each_cat_file_header(data)
  while (match = CAT_FILE_HEADER_LINE.match(data.shift))
    key = match[:key]
    value_lines = [match[:value]]

    while data.first.start_with?(' ')
      value_lines << data.shift.lstrip
    end

    yield key, value_lines.join("\n")
  end
end

#empty?Boolean

Returns true if the repository is empty (meaning it has no commits)

Returns:

  • (Boolean)


714
715
716
717
718
719
720
721
# File 'lib/git/lib.rb', line 714

def empty?
  command('rev-parse', '--verify', 'HEAD')
  false
rescue Git::FailedError => e
  raise unless e.result.status.exitstatus == 128 &&
    e.result.stderr == 'fatal: Needed a single revision'
  true
end

#fetch(remote, opts)



983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
# File 'lib/git/lib.rb', line 983

def fetch(remote, opts)
  arr_opts = []
  arr_opts << '--all' if opts[:all]
  arr_opts << '--tags' if opts[:t] || opts[:tags]
  arr_opts << '--prune' if opts[:p] || opts[:prune]
  arr_opts << '--prune-tags' if opts[:P] || opts[:'prune-tags']
  arr_opts << '--force' if opts[:f] || opts[:force]
  arr_opts << '--update-head-ok' if opts[:u] || opts[:'update-head-ok']
  arr_opts << '--unshallow' if opts[:unshallow]
  arr_opts << '--depth' << opts[:depth] if opts[:depth]
  arr_opts << '--' if remote || opts[:ref]
  arr_opts << remote if remote
  arr_opts << opts[:ref] if opts[:ref]

  command('fetch', *arr_opts, merge: true)
end

#full_log_commits(opts = {})



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/git/lib.rb', line 216

def full_log_commits(opts={})
  arr_opts = log_common_options(opts)

  arr_opts << '--pretty=raw'
  arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]

  arr_opts += log_path_options(opts)

  full_log = command_lines('log', *arr_opts)

  process_commit_log_data(full_log)
end

#full_tree(sha)



384
385
386
# File 'lib/git/lib.rb', line 384

def full_tree(sha)
  command_lines('ls-tree', '-r', sha)
end

#gc



1064
1065
1066
# File 'lib/git/lib.rb', line 1064

def gc
  command('gc', '--prune', '--aggressive', '--auto')
end

#global_config_get(name)



621
622
623
# File 'lib/git/lib.rb', line 621

def global_config_get(name)
  command('config', '--global', '--get', name)
end

#global_config_list



629
630
631
# File 'lib/git/lib.rb', line 629

def global_config_list
  parse_config_list command_lines('config', '--global', '--list')
end

#global_config_set(name, value)



669
670
671
# File 'lib/git/lib.rb', line 669

def global_config_set(name, value)
  command('config', '--global', name, value)
end

#grep(string, opts = {})

returns hash [tree-ish] = [[line_no, match], [line_no, match2]] [tree-ish] = [[line_no, match], [line_no, match2]]



488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/git/lib.rb', line 488

def grep(string, opts = {})
  opts[:object] ||= 'HEAD'

  grep_opts = ['-n']
  grep_opts << '-i' if opts[:ignore_case]
  grep_opts << '-v' if opts[:invert_match]
  grep_opts << '-E' if opts[:extended_regexp]
  grep_opts << '-e'
  grep_opts << string
  grep_opts << opts[:object] if opts[:object].is_a?(String)
  grep_opts.push('--', opts[:path_limiter]) if opts[:path_limiter].is_a?(String)
  grep_opts.push('--', *opts[:path_limiter]) if opts[:path_limiter].is_a?(Array)

  hsh = {}
  begin
    command_lines('grep', *grep_opts).each do |line|
      if m = /(.*?)\:(\d+)\:(.*)/.match(line)
        hsh[m[1]] ||= []
        hsh[m[1]] << [m[2].to_i, m[3]]
      end
    end
  rescue Git::FailedError => e
    raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
  end
  hsh
end

#ignored_files



599
600
601
# File 'lib/git/lib.rb', line 599

def ignored_files
  command_lines('ls-files', '--others', '-i', '--exclude-standard')
end

#init(opts = {})

creates or reinitializes the repository

options: :bare :working_directory :initial_branch



75
76
77
78
79
80
81
# File 'lib/git/lib.rb', line 75

def init(opts={})
  arr_opts = []
  arr_opts << '--bare' if opts[:bare]
  arr_opts << "--initial-branch=#{opts[:initial_branch]}" if opts[:initial_branch]

  command('init', *arr_opts)
end

#list_files(ref_dir)



467
468
469
470
471
472
473
474
475
# File 'lib/git/lib.rb', line 467

def list_files(ref_dir)
  dir = File.join(@git_dir, 'refs', ref_dir)
  files = []
  begin
    files = Dir.glob('**/*', base: dir).select { |f| File.file?(File.join(dir, f)) }
  rescue
  end
  files
end

#log_commits(opts = {})



206
207
208
209
210
211
212
213
214
# File 'lib/git/lib.rb', line 206

def log_commits(opts={})
  arr_opts = log_common_options(opts)

  arr_opts << '--pretty=oneline'

  arr_opts += log_path_options(opts)

  command_lines('log', *arr_opts).map { |l| l.split.first }
end

#ls_files(location = nil)



568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'lib/git/lib.rb', line 568

def ls_files(location=nil)
  location ||= '.'
  hsh = {}
  command_lines('ls-files', '--stage', location).each do |line|
    (info, file) = line.split("\t")
    (mode, sha, stage) = info.split
    if file.start_with?('"') && file.end_with?('"')
      file = Git::EscapedPath.new(file[1..-2]).unescape
    end
    hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
  end
  hsh
end

#ls_remote(location = nil, opts = {})



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'lib/git/lib.rb', line 582

def ls_remote(location=nil, opts={})
  arr_opts = []
  arr_opts << '--refs' if opts[:refs]
  arr_opts << (location || '.')

  Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
    command_lines('ls-remote', *arr_opts).each do |line|
      (sha, info) = line.split("\t")
      (ref, type, name) = info.split('/', 3)
      type ||= 'head'
      type = 'branches' if type == 'heads'
      value = {:ref => ref, :sha => sha}
      hsh[type].update( name.nil? ? value : { name => value })
    end
  end
end

#ls_tree(sha)



368
369
370
371
372
373
374
375
376
377
378
# File 'lib/git/lib.rb', line 368

def ls_tree(sha)
  data = { 'blob' => {}, 'tree' => {}, 'commit' => {} }

  command_lines('ls-tree', sha).each do |line|
    (info, filenm) = line.split("\t")
    (mode, type, sha) = info.split
    data[type][filenm] = {:mode => mode, :sha => sha}
  end

  data
end

#meets_required_version?Boolean

Returns:

  • (Boolean)


1175
1176
1177
# File 'lib/git/lib.rb', line 1175

def meets_required_version?
  (self.current_command_version <=>  self.required_command_version) >= 0
end

#merge(branch, message = nil, opts = {})



879
880
881
882
883
884
885
886
# File 'lib/git/lib.rb', line 879

def merge(branch, message = nil, opts = {})
  arr_opts = []
  arr_opts << '--no-commit' if opts[:no_commit]
  arr_opts << '--no-ff' if opts[:no_ff]
  arr_opts << '-m' << message if message
  arr_opts += Array(branch)
  command('merge', *arr_opts)
end

#merge_base(*args)



888
889
890
891
892
893
894
895
896
897
898
899
900
901
# File 'lib/git/lib.rb', line 888

def merge_base(*args)
  opts = args.last.is_a?(Hash) ? args.pop : {}

  arg_opts = []

  arg_opts << '--octopus' if opts[:octopus]
  arg_opts << '--independent' if opts[:independent]
  arg_opts << '--fork-point' if opts[:fork_point]
  arg_opts << '--all' if opts[:all]

  arg_opts += args

  command('merge-base', *arg_opts).lines.map(&:strip)
end

#mv(file1, file2)



380
381
382
# File 'lib/git/lib.rb', line 380

def mv(file1, file2)
  command_lines('mv', '--', file1, file2)
end

#namerev(string)



240
241
242
# File 'lib/git/lib.rb', line 240

def namerev(string)
  command('name-rev', string).split[1]
end

#object_contents(sha, &block)



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/git/lib.rb', line 352

def object_contents(sha, &block)
  if block_given?
    Tempfile.create do |file|
      # If a block is given, write the output from the process to a temporary
      # file and then yield the file to the block
      #
      command('cat-file', "-p", sha, out: file, err: file)
      file.rewind
      yield file
    end
  else
    # If a block is not given, return stdout
    command('cat-file', '-p', sha)
  end
end

#object_size(sha)



248
249
250
# File 'lib/git/lib.rb', line 248

def object_size(sha)
  command('cat-file', '-s', sha).to_i
end

#object_type(sha)



244
245
246
# File 'lib/git/lib.rb', line 244

def object_type(sha)
  command('cat-file', '-t', sha)
end

#parse_config(file)



642
643
644
# File 'lib/git/lib.rb', line 642

def parse_config(file)
  parse_config_list command_lines('config', '--list', '--file', file)
end

#parse_config_list(lines)



633
634
635
636
637
638
639
640
# File 'lib/git/lib.rb', line 633

def parse_config_list(lines)
  hsh = {}
  lines.each do |line|
    (key, *values) = line.split('=')
    hsh[key] = values.join('=')
  end
  hsh
end

#process_commit_data(data, sha)



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/git/lib.rb', line 259

def process_commit_data(data, sha)
  hsh = {
    'sha'    => sha,
    'parent' => []
  }

  each_cat_file_header(data) do |key, value|
    if key == 'parent'
      hsh['parent'] << value
    else
      hsh[key] = value
    end
  end

  hsh['message'] = data.join("\n") + "\n"

  return hsh
end

#process_commit_log_data(data)



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/git/lib.rb', line 311

def process_commit_log_data(data)
  in_message = false

  hsh_array = []

  hsh = nil

  data.each do |line|
    line = line.chomp

    if line[0].nil?
      in_message = !in_message
      next
    end

    in_message = false if in_message && line[0..3] != "    "

    if in_message
      hsh['message'] << "#{line[4..-1]}\n"
      next
    end

    key, *value = line.split
    value = value.join(' ')

    case key
      when 'commit'
        hsh_array << hsh if hsh
        hsh = {'sha' => value, 'message' => '', 'parent' => []}
      when 'parent'
        hsh['parent'] << value
      else
        hsh[key] = value
    end
  end

  hsh_array << hsh if hsh

  return hsh_array
end

#process_tag_data(data, name)



299
300
301
302
303
304
305
306
307
308
309
# File 'lib/git/lib.rb', line 299

def process_tag_data(data, name)
  hsh = { 'name' => name }

  each_cat_file_header(data) do |key, value|
    hsh[key] = value
  end

  hsh['message'] = data.join("\n") + "\n"

  return hsh
end

#pull(remote = nil, branch = nil, opts = {})

Raises:

  • (ArgumentError)


1037
1038
1039
1040
1041
1042
1043
1044
1045
# File 'lib/git/lib.rb', line 1037

def pull(remote = nil, branch = nil, opts = {})
  raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?

  arr_opts = []
  arr_opts << '--allow-unrelated-histories' if opts[:allow_unrelated_histories]
  arr_opts << remote if remote
  arr_opts << branch if branch
  command('pull', *arr_opts)
end

#push(remote = nil, branch = nil, opts = nil)

Raises:

  • (ArgumentError)


1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
# File 'lib/git/lib.rb', line 1000

def push(remote = nil, branch = nil, opts = nil)
  if opts.nil? && branch.instance_of?(Hash)
    opts = branch
    branch = nil
  end

  if opts.nil? && remote.instance_of?(Hash)
    opts = remote
    remote = nil
  end

  opts ||= {}

  # Small hack to keep backwards compatibility with the 'push(remote, branch, tags)' method signature.
  opts = {:tags => opts} if [true, false].include?(opts)

  raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?

  arr_opts = []
  arr_opts << '--mirror'  if opts[:mirror]
  arr_opts << '--delete'  if opts[:delete]
  arr_opts << '--force'   if opts[:force] || opts[:f]
  arr_opts << '--all'     if opts[:all] && remote

  Array(opts[:push_option]).each { |o| arr_opts << '--push-option' << o } if opts[:push_option]
  arr_opts << remote if remote
  arr_opts_with_branch = arr_opts.dup
  arr_opts_with_branch << branch if branch

  if opts[:mirror]
      command('push', *arr_opts_with_branch)
  else
      command('push', *arr_opts_with_branch)
      command('push', '--tags', *arr_opts) if opts[:tags]
  end
end

#read_tree(treeish, opts = {})

reads a tree into the current index file



1069
1070
1071
1072
1073
1074
# File 'lib/git/lib.rb', line 1069

def read_tree(treeish, opts = {})
  arr_opts = []
  arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
  arr_opts += [treeish]
  command('read-tree', *arr_opts)
end

#remote_add(name, url, opts = {})



927
928
929
930
931
932
933
934
935
936
# File 'lib/git/lib.rb', line 927

def remote_add(name, url, opts = {})
  arr_opts = ['add']
  arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
  arr_opts << '-t' << opts[:track] if opts[:track]
  arr_opts << '--'
  arr_opts << name
  arr_opts << url

  command('remote', *arr_opts)
end

#remote_remove(name)



946
947
948
# File 'lib/git/lib.rb', line 946

def remote_remove(name)
  command('remote', 'rm', name)
end

#remote_set_url(name, url)



938
939
940
941
942
943
944
# File 'lib/git/lib.rb', line 938

def remote_set_url(name, url)
  arr_opts = ['set-url']
  arr_opts << name
  arr_opts << url

  command('remote', *arr_opts)
end

#remotes



950
951
952
# File 'lib/git/lib.rb', line 950

def remotes
  command_lines('remote')
end

#repack



1060
1061
1062
# File 'lib/git/lib.rb', line 1060

def repack
  command('repack', '-a', '-d')
end

#repository_default_branch(repository) ⇒ String

Returns the name of the default branch of the given repository

Parameters:

  • repository (URI, Pathname, String)

    The (possibly remote) repository to clone from

Returns:

  • (String)

    the name of the default branch

Raises:



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/git/lib.rb', line 149

def repository_default_branch(repository)
  output = command('ls-remote', '--symref', '--', repository, 'HEAD')

  match_data = output.match(%r{^ref: refs/remotes/origin/(?<default_branch>[^\t]+)\trefs/remotes/origin/HEAD$})
  return match_data[:default_branch] if match_data

  match_data = output.match(%r{^ref: refs/heads/(?<default_branch>[^\t]+)\tHEAD$})
  return match_data[:default_branch] if match_data

  raise Git::UnexpectedResultError, 'Unable to determine the default branch'
end

#required_command_version



1171
1172
1173
# File 'lib/git/lib.rb', line 1171

def required_command_version
  [2, 28]
end

#reset(commit, opts = {})



765
766
767
768
769
770
# File 'lib/git/lib.rb', line 765

def reset(commit, opts = {})
  arr_opts = []
  arr_opts << '--hard' if opts[:hard]
  arr_opts << commit if commit
  command('reset', *arr_opts)
end

#return_base_opts_from_clone(clone_dir, opts)



135
136
137
138
139
140
141
# File 'lib/git/lib.rb', line 135

def return_base_opts_from_clone(clone_dir, opts)
  base_opts = {}
  base_opts[:repository] = clone_dir if (opts[:bare] || opts[:mirror])
  base_opts[:working_directory] = clone_dir unless (opts[:bare] || opts[:mirror])
  base_opts[:log] = opts[:log] if opts[:log]
  base_opts
end

#revert(commitish, opts = {})



782
783
784
785
786
787
788
789
790
791
# File 'lib/git/lib.rb', line 782

def revert(commitish, opts = {})
  # Forcing --no-edit as default since it's not an interactive session.
  opts = {:no_edit => true}.merge(opts)

  arr_opts = []
  arr_opts << '--no-edit' if opts[:no_edit]
  arr_opts << commitish

  command('revert', *arr_opts)
end

#revparse(string)



229
230
231
232
233
234
235
236
237
238
# File 'lib/git/lib.rb', line 229

def revparse(string)
  return string if string =~ /^[A-Fa-f0-9]{40}$/  # passing in a sha - just no-op it
  rev = ['head', 'remotes', 'tags'].map do |d|
    File.join(@git_dir, 'refs', d, string)
  end.find do |path|
    File.file?(path)
  end
  return File.read(rev).chomp if rev
  command('rev-parse', string)
end

#rm(path = '.', opts = {})



700
701
702
703
704
705
706
707
708
# File 'lib/git/lib.rb', line 700

def rm(path = '.', opts = {})
  arr_opts = ['-f']  # overrides the up-to-date check by default
  arr_opts << '-r' if opts[:recursive]
  arr_opts << '--cached' if opts[:cached]
  arr_opts << '--'
  arr_opts += Array(path)

  command('rm', *arr_opts)
end

#show(objectish = nil, path = nil) ⇒ String

Shows objects

Parameters:

  • objectish (String|NilClass) (defaults to: nil)

    the target object reference (nil == HEAD)

  • path (String|NilClass) (defaults to: nil)

    the path of the file to be shown

Returns:

  • (String)

    the object information



651
652
653
654
655
656
657
# File 'lib/git/lib.rb', line 651

def show(objectish=nil, path=nil)
  arr_opts = []

  arr_opts << (path ? "#{objectish}:#{path}" : objectish)

  command('show', *arr_opts.compact, chomp: false)
end

#stash_apply(id = nil)



824
825
826
827
828
829
830
# File 'lib/git/lib.rb', line 824

def stash_apply(id = nil)
  if id
    command('stash', 'apply', id)
  else
    command('stash', 'apply')
  end
end

#stash_clear



832
833
834
# File 'lib/git/lib.rb', line 832

def stash_clear
  command('stash', 'clear')
end

#stash_list



836
837
838
# File 'lib/git/lib.rb', line 836

def stash_list
  command('stash', 'list')
end

#stash_save(message)



819
820
821
822
# File 'lib/git/lib.rb', line 819

def stash_save(message)
  output = command('stash', 'save', message)
  output =~ /HEAD is now at/
end

#stashes_all



805
806
807
808
809
810
811
812
813
814
815
816
817
# File 'lib/git/lib.rb', line 805

def stashes_all
  arr = []
  filename = File.join(@git_dir, 'logs/refs/stash')
  if File.exist?(filename)
    File.open(filename) do |f|
      f.each_with_index do |line, i|
        m = line.match(/:(.*)$/)
        arr << [i, m[1].strip]
      end
    end
  end
  arr
end

#tag(name, *opts)



958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
# File 'lib/git/lib.rb', line 958

def tag(name, *opts)
  target = opts[0].instance_of?(String) ? opts[0] : nil

  opts = opts.last.instance_of?(Hash) ? opts.last : {}

  if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
    raise  ArgumentError, 'Cannot create an annotated tag without a message.'
  end

  arr_opts = []

  arr_opts << '-f' if opts[:force] || opts[:f]
  arr_opts << '-a' if opts[:a] || opts[:annotate]
  arr_opts << '-s' if opts[:s] || opts[:sign]
  arr_opts << '-d' if opts[:d] || opts[:delete]
  arr_opts << name
  arr_opts << target if target

  if opts[:m] || opts[:message]
    arr_opts << '-m' << (opts[:m] || opts[:message])
  end

  command('tag', *arr_opts)
end

#tag_data(name)



293
294
295
296
297
# File 'lib/git/lib.rb', line 293

def tag_data(name)
  sha = sha.to_s
  tdata = command_lines('cat-file', 'tag', name)
  process_tag_data(tdata, name)
end

#tag_sha(tag_name)



1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
# File 'lib/git/lib.rb', line 1047

def tag_sha(tag_name)
  head = File.join(@git_dir, 'refs', 'tags', tag_name)
  return File.read(head).chomp if File.exist?(head)

  begin
    command('show-ref',  '--tags', '-s', tag_name)
  rescue Git::FailedError => e
    raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''

    ''
  end
end

#tags



954
955
956
# File 'lib/git/lib.rb', line 954

def tags
  command_lines('tag')
end

#tree_depth(sha)



388
389
390
# File 'lib/git/lib.rb', line 388

def tree_depth(sha)
  full_tree(sha).size
end

#unmerged



903
904
905
906
907
908
909
# File 'lib/git/lib.rb', line 903

def unmerged
  unmerged = []
  command_lines('diff', "--cached").each do |line|
    unmerged << $1 if line =~ /^\* Unmerged path (.*)/
  end
  unmerged
end

#untracked_files



603
604
605
# File 'lib/git/lib.rb', line 603

def untracked_files
  command_lines('ls-files', '--others', '--exclude-standard', chdir: @git_work_dir)
end

#update_ref(ref, commit)



1090
1091
1092
# File 'lib/git/lib.rb', line 1090

def update_ref(ref, commit)
  command('update-ref', ref, commit)
end

#worktree_add(dir, commitish = nil)



454
455
456
457
# File 'lib/git/lib.rb', line 454

def worktree_add(dir, commitish = nil)
  return command('worktree', 'add', dir, commitish) if !commitish.nil?
  command('worktree', 'add', dir)
end

#worktree_prune



463
464
465
# File 'lib/git/lib.rb', line 463

def worktree_prune
  command('worktree', 'prune')
end

#worktree_remove(dir)



459
460
461
# File 'lib/git/lib.rb', line 459

def worktree_remove(dir)
  command('worktree', 'remove', dir)
end

#worktrees_all



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/git/lib.rb', line 434

def worktrees_all
  arr = []
  directory = ''
  # Output example for `worktree list --porcelain`:
  # worktree /code/public/ruby-git
  # HEAD 4bef5abbba073c77b4d0ccc1ffcd0ed7d48be5d4
  # branch refs/heads/master
  #
  # worktree /tmp/worktree-1
  # HEAD b8c63206f8d10f57892060375a86ae911fad356e
  # detached
  #
  command_lines('worktree', 'list', '--porcelain').each do |w|
    s = w.split("\s")
    directory = s[1] if s[0] == 'worktree'
    arr << [directory, s[1]] if s[0] == 'HEAD'
  end
  arr
end

#write_tree



1076
1077
1078
# File 'lib/git/lib.rb', line 1076

def write_tree
  command('write-tree')
end