Class: Bringit::Repository

Inherits:
Object
  • Object
show all
Includes:
Popen
Defined in:
lib/bringit/repository.rb

Constant Summary collapse

SEARCH_CONTEXT_LINES =
3
NoRepository =
Class.new(StandardError)
InvalidBlobName =
Class.new(StandardError)
InvalidRef =
Class.new(StandardError)
AUTOCRLF_VALUES =
{
  "true" => true,
  "false" => false,
  "input" => :input
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Popen

popen

Constructor Details

#initialize(path) ⇒ Repository

‘path’ must be the path to a bare git repository, e.g. /path/to/my-repo.git



27
28
29
30
31
# File 'lib/bringit/repository.rb', line 27

def initialize(path)
  @path = path
  @name = path.split("/").last
  @attributes = Bringit::Attributes.new(path)
end

Instance Attribute Details

#nameObject (readonly)

Directory name of repo



20
21
22
# File 'lib/bringit/repository.rb', line 20

def name
  @name
end

#pathObject (readonly)

Full path to repo



17
18
19
# File 'lib/bringit/repository.rb', line 17

def path
  @path
end

#ruggedObject (readonly)

Rugged repo object



23
24
25
# File 'lib/bringit/repository.rb', line 23

def rugged
  @rugged
end

Instance Method Details

#archive_file_path(name, storage_path, format = "tar.gz") ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/bringit/repository.rb', line 221

def archive_file_path(name, storage_path, format = "tar.gz")
  # Build file path
  return nil unless name

  extension =
    case format
    when "tar.bz2", "tbz", "tbz2", "tb2", "bz2"
      "tar.bz2"
    when "tar"
      "tar"
    when "zip"
      "zip"
    else
      # everything else should fall back to tar.gz
      "tar.gz"
    end

  file_name = "#{name}.#{extension}"
  File.join(storage_path, self.name, file_name)
end

#archive_metadata(ref, storage_path, format = "tar.gz") ⇒ Object



206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/bringit/repository.rb', line 206

def (ref, storage_path, format = "tar.gz")
  ref ||= root_ref
  commit = Bringit::Commit.find(self, ref)
  return {} if commit.nil?

  prefix = archive_prefix(ref, commit.id)

  {
    'RepoPath' => path,
    'ArchivePrefix' => prefix,
    'ArchivePath' => archive_file_path(prefix, storage_path, format),
    'CommitId' => commit.id,
  }
end

#archive_prefix(ref, sha) ⇒ Object



201
202
203
204
# File 'lib/bringit/repository.rb', line 201

def archive_prefix(ref, sha)
  project_name = self.name.chomp('.git')
  "#{project_name}-#{ref.tr('/', '-')}-#{sha}"
end

#attributes(path) ⇒ Object

Returns the Git attributes for the given file path.

See ‘Bringit::Attributes` for more information.



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

def attributes(path)
  @attributes.attributes(path)
end

#autocrlfObject



869
870
871
# File 'lib/bringit/repository.rb', line 869

def autocrlf
  AUTOCRLF_VALUES[rugged.config['core.autocrlf']]
end

#autocrlf=(value) ⇒ Object



873
874
875
# File 'lib/bringit/repository.rb', line 873

def autocrlf=(value)
  rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
end

#branch_countObject

Returns the number of valid branches



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/bringit/repository.rb', line 95

def branch_count
  rugged.branches.count do |ref|
    begin
      ref.name && ref.target # ensures the branch is valid

      true
    rescue Rugged::ReferenceError
      false
    end
  end
end

#branch_exists?(name) ⇒ Boolean

Returns true if the given branch exists

name - The name of the branch as a String.

Returns:

  • (Boolean)


139
140
141
142
143
144
145
146
147
148
# File 'lib/bringit/repository.rb', line 139

def branch_exists?(name)
  rugged.branches.exists?(name)

# If the branch name is invalid (e.g. ".foo") Rugged will raise an error.
# Whatever code calls this method shouldn't have to deal with that so
# instead we just return `false` (which is true since a branch doesn't
# exist when it has an invalid name).
rescue Rugged::ReferenceError
  false
end

#branch_namesObject

Returns an Array of branch names sorted by name ASC



55
56
57
# File 'lib/bringit/repository.rb', line 55

def branch_names
  branches.map(&:name)
end

#branch_names_contains(commit) ⇒ Object

Returns branch names collection that contains the special commit(SHA1 or name)

Ex.

repo.branch_names_contains('master')


502
503
504
# File 'lib/bringit/repository.rb', line 502

def branch_names_contains(commit)
  branches_contains(commit).map { |c| c.name }
end

#branchesObject

Returns an Array of Branches



60
61
62
63
64
65
66
67
68
# File 'lib/bringit/repository.rb', line 60

def branches
  rugged.branches.map do |rugged_ref|
    begin
      Bringit::Branch.new(self, rugged_ref.name, rugged_ref.target)
    rescue Rugged::ReferenceError
      # Omit invalid branch
    end
  end.compact.sort_by(&:name)
end

#branches_contains(commit) ⇒ Object

Returns branch collection that contains the special commit(SHA1 or name)

Ex.

repo.branch_names_contains('master')


511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/bringit/repository.rb', line 511

def branches_contains(commit)
  commit_obj = rugged.rev_parse(commit)
  parent = commit_obj.parents.first unless commit_obj.parents.empty?

  walker = Rugged::Walker.new(rugged)

  rugged.branches.select do |branch|
    walker.push(branch.target_id)
    walker.hide(parent) if parent
    result = walker.any? { |c| c.oid == commit_obj.oid }
    walker.reset

    result
  end
end

#checkout(ref, options = {}, start_point = "HEAD") ⇒ Object

Check out the specified ref. Valid options are:

:b - Create a new branch at +start_point+ and set HEAD to the new
     branch.

* These options are passed to the Rugged::Repository#checkout method:

:progress ::
  A callback that will be executed for checkout progress notifications.
  Up to 3 parameters are passed on each execution:

  - The path to the last updated file (or +nil+ on the very first
    invocation).
  - The number of completed checkout steps.
  - The number of total checkout steps to be performed.

:notify ::
  A callback that will be executed for each checkout notification
  types specified with +:notify_flags+. Up to 5 parameters are passed
  on each execution:

  - An array containing the +:notify_flags+ that caused the callback
    execution.
  - The path of the current file.
  - A hash describing the baseline blob (or +nil+ if it does not
    exist).
  - A hash describing the target blob (or +nil+ if it does not exist).
  - A hash describing the workdir blob (or +nil+ if it does not
    exist).

:strategy ::
  A single symbol or an array of symbols representing the strategies
  to use when performing the checkout. Possible values are:

  :none ::
    Perform a dry run (default).

  :safe ::
    Allow safe updates that cannot overwrite uncommitted data.

  :safe_create ::
    Allow safe updates plus creation of missing files.

  :force ::
    Allow all updates to force working directory to look like index.

  :allow_conflicts ::
    Allow checkout to make safe updates even if conflicts are found.

  :remove_untracked ::
    Remove untracked files not in index (that are not ignored).

  :remove_ignored ::
    Remove ignored files not in index.

  :update_only ::
    Only update existing files, don't create new ones.

  :dont_update_index ::
    Normally checkout updates index entries as it goes; this stops
    that.

  :no_refresh ::
    Don't refresh index/config/etc before doing checkout.

  :disable_pathspec_match ::
    Treat pathspec as simple list of exact match file paths.

  :skip_locked_directories ::
    Ignore directories in use, they will be left empty.

  :skip_unmerged ::
    Allow checkout to skip unmerged files (NOT IMPLEMENTED).

  :use_ours ::
    For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED).

  :use_theirs ::
    For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED).

  :update_submodules ::
    Recursively checkout submodules with same options (NOT
    IMPLEMENTED).

  :update_submodules_if_changed ::
    Recursively checkout submodules if HEAD moved in super repo (NOT
    IMPLEMENTED).

:disable_filters ::
  If +true+, filters like CRLF line conversion will be disabled.

:dir_mode ::
  Mode for newly created directories. Default: +0755+.

:file_mode ::
  Mode for newly created files. Default: +0755+ or +0644+.

:file_open_flags ::
  Mode for opening files. Default:
  <code>IO::CREAT | IO::TRUNC | IO::WRONLY</code>.

:notify_flags ::
  A single symbol or an array of symbols representing the cases in
  which the +:notify+ callback should be invoked. Possible values are:

  :none ::
    Do not invoke the +:notify+ callback (default).

  :conflict ::
    Invoke the callback for conflicting paths.

  :dirty ::
    Invoke the callback for "dirty" files, i.e. those that do not need
    an update but no longer match the baseline.

  :updated ::
    Invoke the callback for any file that was changed.

  :untracked ::
    Invoke the callback for untracked files.

  :ignored ::
    Invoke the callback for ignored files.

  :all ::
    Invoke the callback for all these cases.

:paths ::
  A glob string or an array of glob strings specifying which paths
  should be taken into account for the checkout operation. +nil+ will
  match all files.  Default: +nil+.

:baseline ::
  A Rugged::Tree that represents the current, expected contents of the
  workdir.  Default: +HEAD+.

:target_directory ::
  A path to an alternative workdir directory in which the checkout
  should be performed.


764
765
766
767
768
769
770
771
# File 'lib/bringit/repository.rb', line 764

def checkout(ref, options = {}, start_point = "HEAD")
  if options[:b]
    rugged.branches.create(ref, start_point)
    options.delete(:b)
  end
  default_options = { strategy: [:recreate_missing, :safe] }
  rugged.checkout(ref, default_options.merge(options))
end

#clean(options = {}) ⇒ Object

Mimic the ‘git clean` command and recursively delete untracked files. Valid keys that can be passed in the options hash are:

:d - Remove untracked directories :f - Remove untracked directories that are managed by a different

repository

:x - Remove ignored files

The value in options must evaluate to true for an option to take effect.

Examples:

repo.clean(d: true, f: true) # Enable the -d and -f options

repo.clean(d: false, x: true) # -x is enabled, -d is not


617
618
619
620
621
622
623
# File 'lib/bringit/repository.rb', line 617

def clean(options = {})
  strategies = [:remove_untracked]
  strategies.push(:force) if options[:f]
  strategies.push(:remove_ignored) if options[:x]

  # TODO: implement this method
end

#commit_count(ref) ⇒ Object

Return total commits count accessible from passed ref



579
580
581
582
583
584
585
# File 'lib/bringit/repository.rb', line 579

def commit_count(ref)
  walker = Rugged::Walker.new(rugged)
  walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
  oid = rugged.rev_parse_oid(ref)
  walker.push(oid)
  walker.count
end

#commits_between(from, to) ⇒ Object

Return a collection of Rugged::Commits between the two revspec arguments. See git-scm.com/docs/git-rev-parse.html#_specifying_revisions for a detailed list of valid arguments.



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/bringit/repository.rb', line 398

def commits_between(from, to)
  walker = Rugged::Walker.new(rugged)
  walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)

  sha_from = sha_from_ref(from)
  sha_to = sha_from_ref(to)

  walker.push(sha_to)
  walker.hide(sha_from)

  commits = walker.to_a
  walker.reset

  commits
end

#commits_since(from_date) ⇒ Object



846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
# File 'lib/bringit/repository.rb', line 846

def commits_since(from_date)
  walker = Rugged::Walker.new(rugged)
  walker.sorting(Rugged::SORT_DATE | Rugged::SORT_REVERSE)

  rugged.references.each("refs/heads/*") do |ref|
    walker.push(ref.target_id)
  end

  commits = []
  walker.each do |commit|
    break if commit.author[:time].to_date < from_date
    commits.push(commit)
  end

  commits
end

#copy_gitattributes(ref) ⇒ Object



908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
# File 'lib/bringit/repository.rb', line 908

def copy_gitattributes(ref)
  begin
    commit = lookup(ref)
  rescue Rugged::ReferenceError
    raise InvalidRef.new("Ref #{ref} is invalid")
  end

  # Create the paths
  info_dir_path = File.join(path, 'info')
  info_attributes_path = File.join(info_dir_path, 'attributes')

  begin
    # Retrieve the contents of the blob
    gitattributes_content = blob_content(commit, '.gitattributes')
  rescue InvalidBlobName
    # No .gitattributes found. Should now remove any info/attributes and return
    File.delete(info_attributes_path) if File.exist?(info_attributes_path)
    return
  end

  # Create the info directory if needed
  Dir.mkdir(info_dir_path) unless File.directory?(info_dir_path)

  # Write the contents of the .gitattributes file to info/attributes
  # Use binary mode to prevent Rails from converting ASCII-8BIT to UTF-8
  File.open(info_attributes_path, "wb") do |file|
    file.write(gitattributes_content)
  end
end

#count_commits(options) ⇒ Object



372
373
374
375
376
377
378
379
380
381
382
# File 'lib/bringit/repository.rb', line 372

def count_commits(options)
  cmd = %W[git --git-dir=#{path} rev-list]
  cmd << "--after=#{options[:after].iso8601}" if options[:after]
  cmd << "--before=#{options[:before].iso8601}" if options[:before]
  cmd += %W[--count #{options[:ref]}]
  cmd += %W[-- #{options[:path]}] if options[:path].present?

  raw_output = IO.popen(cmd) { |io| io.read }

  raw_output.to_i
end

#count_commits_between(from, to) ⇒ Object

Counts the amount of commits between ‘from` and `to`.



415
416
417
# File 'lib/bringit/repository.rb', line 415

def count_commits_between(from, to)
  commits_between(from, to).size
end

#create_branch(ref, start_point = "HEAD") ⇒ Object

Create a new branch named **ref+ based on **stat_point+, HEAD by default

Examples:

create_branch("feature")
create_branch("other-feature", "master")


783
784
785
786
787
788
789
# File 'lib/bringit/repository.rb', line 783

def create_branch(ref, start_point = "HEAD")
  rugged_ref = rugged.branches.create(ref, start_point)
  Bringit::Branch.new(self, rugged_ref.name, rugged_ref.target)
rescue Rugged::ReferenceError => e
  raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/
  raise InvalidRef.new("Invalid reference #{start_point}")
end

#delete_branch(branch_name) ⇒ Object

Delete the specified branch from the repository



774
775
776
# File 'lib/bringit/repository.rb', line 774

def delete_branch(branch_name)
  rugged.branches.delete(branch_name)
end

#diff(from, to, options = {}, *paths) ⇒ Object

Return an array of Diff objects that represent the diff between from and to. See Diff::filter_diff_options for the allowed diff options. The options hash can also include :break_rewrites to split larger rewrites into delete/add pairs.



428
429
430
# File 'lib/bringit/repository.rb', line 428

def diff(from, to, options = {}, *paths)
  Bringit::DiffCollection.new(diff_patches(from, to, options, *paths), options)
end

#diffable?(blob) ⇒ Boolean

Checks if the blob should be diffable according to its attributes

Returns:

  • (Boolean)


939
940
941
# File 'lib/bringit/repository.rb', line 939

def diffable?(blob)
  attributes(blob.path).fetch('diff') { blob.text? }
end

#discover_default_branchObject

Discovers the default branch based on the repository’s available branches

  • If no branches are present, returns nil

  • If one branch is present, returns its name

  • If two or more branches are present, returns current HEAD or master or first branch



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/bringit/repository.rb', line 175

def discover_default_branch
  names = branch_names

  return if names.empty?

  return names[0] if names.length == 1

  if rugged_head
    extracted_name = Ref.extract_branch_name(rugged_head.name)

    return extracted_name if names.include?(extracted_name)
  end

  if names.include?('master')
    'master'
  else
    names[0]
  end
end

#fetch(remote_name) ⇒ Object

Fetch the specified remote



816
817
818
# File 'lib/bringit/repository.rb', line 816

def fetch(remote_name)
  rugged.remotes[remote_name].fetch
end

#find_branch(name, force_reload = false) ⇒ Object

Directly find a branch with a simple name (e.g. master)

force_reload causes a new Rugged repository to be instantiated

This is to work around a bug in libgit2 that causes in-memory refs to be stale/invalid when packed-refs is changed. See gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333



81
82
83
84
85
86
# File 'lib/bringit/repository.rb', line 81

def find_branch(name, force_reload = false)
  reload_rugged if force_reload

  rugged_ref = rugged.branches[name]
  Bringit::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref
end

#find_commits(options = {}) ⇒ Object

Returns commits collection

Ex.

repo.find_commits(
  ref: 'master',
  max_count: 10,
  skip: 5,
  order: :date
)

+options+ is a Hash of optional arguments to git
  :ref is the ref from which to begin (SHA1 or name)
  :contains is the commit contained by the refs from which to begin (SHA1 or name)
  :max_count is the maximum number of commits to fetch
  :skip is the number of commits to skip
  :order is the commits order and allowed value is :date(default) or :topo


449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/bringit/repository.rb', line 449

def find_commits(options = {})
  actual_options = options.dup

  allowed_options = [:ref, :max_count, :skip, :contains, :order]

  actual_options.keep_if do |key|
    allowed_options.include?(key)
  end

  default_options = { skip: 0 }
  actual_options = default_options.merge(actual_options)

  walker = Rugged::Walker.new(rugged)

  if actual_options[:ref]
    walker.push(rugged.rev_parse_oid(actual_options[:ref]))
  elsif actual_options[:contains]
    branches_contains(actual_options[:contains]).each do |branch|
      walker.push(branch.target_id)
    end
  else
    rugged.references.each("refs/heads/*") do |ref|
      walker.push(ref.target_id)
    end
  end

  if actual_options[:order] == :topo
    walker.sorting(Rugged::SORT_TOPO)
  else
    walker.sorting(Rugged::SORT_DATE)
  end

  commits = []
  offset = actual_options[:skip]
  limit = actual_options[:max_count]
  walker.each(offset: offset, limit: limit) do |commit|
    bringit_commit = Bringit::Commit.decorate(commit, self)
    commits.push(bringit_commit)
  end

  walker.reset

  commits
rescue Rugged::OdbError
  []
end

#has_commits?Boolean

Returns:

  • (Boolean)


162
163
164
# File 'lib/bringit/repository.rb', line 162

def has_commits?
  !empty?
end

#headsObject

Deprecated. Will be removed in 5.2



156
157
158
159
160
# File 'lib/bringit/repository.rb', line 156

def heads
  rugged.references.each("refs/heads/*").map do |head|
    Bringit::Ref.new(self, head.name, head.target)
  end.sort_by(&:name)
end

#local_branchesObject



88
89
90
91
92
# File 'lib/bringit/repository.rb', line 88

def local_branches
  rugged.branches.each(:local).map do |branch|
    Bringit::Branch.new(self, branch.name, branch.target)
  end
end

#log(options) ⇒ Object

Use the Rugged Walker API to build an array of commits.

Usage.

repo.log(
  ref: 'master',
  path: 'app/models',
  limit: 10,
  offset: 5,
  after: Time.new(2016, 4, 21, 14, 32, 10)
)


281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/bringit/repository.rb', line 281

def log(options)
  default_options = {
    limit: 10,
    offset: 0,
    path: nil,
    follow: false,
    skip_merges: false,
    disable_walk: false,
    after: nil,
    before: nil,
    unsafe_range: false,
  }

  options = default_options.merge(options)
  options[:limit] ||= 0
  options[:offset] ||= 0
  actual_ref = options[:ref] || root_ref

  if options[:unsafe_range]
    log_by_shell(actual_ref, options)
  else
    begin
      sha = sha_from_ref(actual_ref)
    rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
      # Return an empty array if the ref wasn't found
      return []
    end
    if log_using_shell?(options)
      log_by_shell(sha, options)
    else
      log_by_walk(sha, options)
    end
  end
end

#log_by_shell(sha, options) ⇒ Object



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/bringit/repository.rb', line 339

def log_by_shell(sha, options)
  limit = options[:limit].to_i
  offset = options[:offset].to_i
  use_follow_flag = options[:follow] && options[:path].present?

  # We will perform the offset in Ruby because --follow doesn't play well with --skip.
  # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
  offset_in_ruby = use_follow_flag && options[:offset].present?
  limit += offset if offset_in_ruby

  cmd = %W[git --git-dir=#{path} log]
  cmd << "--max-count=#{limit}" if limit > 0
  cmd << '--format=%H'
  cmd << "--skip=#{offset}" unless offset_in_ruby
  cmd << '--follow' if use_follow_flag
  cmd << '--no-merges' if options[:skip_merges]
  cmd << "--after=#{options[:after].iso8601}" if options[:after]
  cmd << "--before=#{options[:before].iso8601}" if options[:before]
  cmd << sha
  if options[:path].present?
    cmd += %W[-- #{options[:path].sub(%r{\A/*}, './')}]
  end

  raw_output = IO.popen(cmd) { |io| io.read }
  lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines

  if options[:only_commit_sha]
    lines.map(&:strip)
  else
    lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
  end
end

#log_by_walk(sha, options) ⇒ Object



324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/bringit/repository.rb', line 324

def log_by_walk(sha, options)
  walk_options = {
    show: sha,
    sort: Rugged::SORT_TOPO,
    limit: options[:limit],
    offset: options[:offset]
  }
  commits = Rugged::Walker.walk(rugged, walk_options).to_a
  if options[:only_commit_sha]
    commits.map(&:oid)
  else
    commits
  end
end

#log_using_shell?(options) ⇒ Boolean

Returns:

  • (Boolean)


316
317
318
319
320
321
322
# File 'lib/bringit/repository.rb', line 316

def log_using_shell?(options)
  options[:path].present? ||
    options[:disable_walk] ||
    options[:skip_merges] ||
    options[:after] ||
    options[:before]
end

#lookup(oid_or_ref_name) ⇒ Object

Lookup for rugged object by oid or ref name



547
548
549
# File 'lib/bringit/repository.rb', line 547

def lookup(oid_or_ref_name)
  rugged.rev_parse(oid_or_ref_name)
end

#ls_files(ref) ⇒ Object

Returns result like “git ls-files” , recursive and full file path

Ex.

repo.ls_files('master')


882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
# File 'lib/bringit/repository.rb', line 882

def ls_files(ref)
  actual_ref = ref || root_ref

  begin
    sha_from_ref(actual_ref)
  rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
    # Return an empty array if the ref wasn't found
    return []
  end

  cmd = %W(git --git-dir=#{path} ls-tree)
  cmd += %w(-r)
  cmd += %w(--full-tree)
  cmd += %w(--full-name)
  cmd += %W(-- #{actual_ref})

  raw_output = IO.popen(cmd, &:read).split("\n").map do |f|
    stuff, path = f.split("\t")
    _mode, type, _sha = stuff.split(" ")
    path if type == "blob"
    # Contain only blob type
  end

  raw_output.compact
end

#merge(source_name, target_name, options = {}) ⇒ Object

Merge the source_name branch into the target_name branch. This is equivalent to ‘git merge –no_ff source_name`, since a merge commit is always created.



828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/bringit/repository.rb', line 828

def merge(source_name, target_name, options = {})
  our_commit = rugged.branches[target_name].target
  their_commit = rugged.branches[source_name].target

  raise "Invalid merge target" if our_commit.nil?
  raise "Invalid merge source" if their_commit.nil?

  merge_index = rugged.merge_commits(our_commit, their_commit)
  return false if merge_index.conflicts?

  actual_options = options.merge(
    parents: [our_commit, their_commit],
    tree: merge_index.write_tree(rugged),
    update_ref: "refs/heads/#{target_name}"
  )
  Rugged::Commit.create(rugged, actual_options)
end

#merge_base_commit(from, to) ⇒ Object

Returns the SHA of the most recent common ancestor of from and to



420
421
422
# File 'lib/bringit/repository.rb', line 420

def merge_base_commit(from, to)
  rugged.merge_base(from, to)
end

#push(remote_name, *refspecs) ⇒ Object

Push *refspecs to the remote identified by remote_name.



821
822
823
# File 'lib/bringit/repository.rb', line 821

def push(remote_name, *refspecs)
  rugged.remotes[remote_name].push(refspecs)
end

#rawObject

Alias to old method for compatibility



43
44
45
# File 'lib/bringit/repository.rb', line 43

def raw
  rugged
end

#ref_namesObject

Returns an Array of branch and tag names



151
152
153
# File 'lib/bringit/repository.rb', line 151

def ref_names
  branch_names + tag_names
end

#refs_hashObject

Get refs hash which key is SHA1 and value is a Rugged::Reference



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/bringit/repository.rb', line 529

def refs_hash
  # Initialize only when first call
  if @refs_hash.nil?
    @refs_hash = Hash.new { |h, k| h[k] = [] }

    rugged.references.each do |r|
      # Symbolic/remote references may not have an OID; skip over them
      target_oid = r.target.try(:oid)
      if target_oid
        sha = rev_parse_target(target_oid).oid
        @refs_hash[sha] << r
      end
    end
  end
  @refs_hash
end

#reload_ruggedObject



70
71
72
# File 'lib/bringit/repository.rb', line 70

def reload_rugged
  @rugged = nil
end

#remote_add(remote_name, url) ⇒ Object

Add a new remote to this repository. Returns a Rugged::Remote object



802
803
804
# File 'lib/bringit/repository.rb', line 802

def remote_add(remote_name, url)
  rugged.remotes.create(remote_name, url)
end

#remote_delete(remote_name) ⇒ Object

Delete the specified remote from this repository.



797
798
799
# File 'lib/bringit/repository.rb', line 797

def remote_delete(remote_name)
  rugged.remotes.delete(remote_name)
end

#remote_namesObject

Return an array of this repository’s remote names



792
793
794
# File 'lib/bringit/repository.rb', line 792

def remote_names
  rugged.remotes.each_name.to_a
end

#remote_update(remote_name, options = {}) ⇒ Object

Update the specified remote using the values in the options hash

Example repo.update_remote(“origin”, url: “path/to/repo”)



810
811
812
813
# File 'lib/bringit/repository.rb', line 810

def remote_update(remote_name, options = {})
  # TODO: Implement other remote options
  rugged.remotes.set_url(remote_name, options[:url]) if options[:url]
end

#repo_exists?Boolean

Returns:

  • (Boolean)


166
167
168
# File 'lib/bringit/repository.rb', line 166

def repo_exists?
  !!rugged
end

#rev_parse_target(revspec) ⇒ Object

Return the object that revspec points to. If revspec is an annotated tag, then return the tag’s target instead.



390
391
392
393
# File 'lib/bringit/repository.rb', line 390

def rev_parse_target(revspec)
  obj = rugged.rev_parse(revspec)
  Ref.dereference_object(obj)
end

#root_refObject

Default branch in the repository



38
39
40
# File 'lib/bringit/repository.rb', line 38

def root_ref
  @root_ref ||= discover_default_branch
end

#rugged_headObject



195
196
197
198
199
# File 'lib/bringit/repository.rb', line 195

def rugged_head
  rugged.head
rescue Rugged::ReferenceError
  nil
end

#search_files(query, ref = nil) ⇒ Object

Returns an array of BlobSnippets for files at the specified ref that contain the query string.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/bringit/repository.rb', line 250

def search_files(query, ref = nil)
  greps = []
  ref ||= root_ref

  populated_index(ref).each do |entry|
    # Discard submodules
    next if submodule?(entry)

    blob = Bringit::Blob.raw(self, entry[:oid])

    # Skip binary files
    next if blob.data.encoding == Encoding::ASCII_8BIT

    blob.load_all_data!
    greps += build_greps(blob.data, query, ref, entry[:path])
  end

  greps
end

#sha_from_ref(ref) ⇒ Object



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

def sha_from_ref(ref)
  rev_parse_target(ref).oid
end

#sizeObject

Return repo size in megabytes



243
244
245
246
# File 'lib/bringit/repository.rb', line 243

def size
  size = Bringit::Popen.popen(%w(du -sk), path).first.strip.to_i
  (size.to_f / 1024).round(2)
end

#submodules(ref) ⇒ Object

Return hash with submodules info for this repository

Ex.

{
  "rack"  => {
    "id" => "c67be4624545b4263184c4a0e8f887efd0a66320",
    "path" => "rack",
    "url" => "git://github.com/chneukirchen/rack.git"
  },
  "encoding" => {
    "id" => ....
  }
}


565
566
567
568
569
570
571
572
573
574
575
576
# File 'lib/bringit/repository.rb', line 565

def submodules(ref)
  commit = rev_parse_target(ref)
  return {} unless commit

  begin
    content = blob_content(commit, ".gitmodules")
  rescue InvalidBlobName
    return {}
  end

  parse_gitmodules(commit, content)
end

#tag_exists?(name) ⇒ Boolean

Returns true if the given tag exists

name - The name of the tag as a String.

Returns:

  • (Boolean)


132
133
134
# File 'lib/bringit/repository.rb', line 132

def tag_exists?(name)
  !!rugged.tags[name]
end

#tag_namesObject

Returns an Array of tag names



108
109
110
# File 'lib/bringit/repository.rb', line 108

def tag_names
  rugged.tags.map { |t| t.name }
end

#tagsObject

Returns an Array of Tags



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/bringit/repository.rb', line 113

def tags
  rugged.references.each("refs/tags/*").map do |ref|
    message = nil

    if ref.target.is_a?(Rugged::Tag::Annotation)
      tag_message = ref.target.message

      if tag_message.respond_to?(:chomp)
        message = tag_message.chomp
      end
    end

    Bringit::Tag.new(self, ref.name, ref.target, message)
  end.sort_by(&:name)
end