class Gitlab::Git::Repository

Constants

AUTOCRLF_VALUES
SEARCH_CONTEXT_LINES

Attributes

name[R]

Directory name of repo

path[R]

Full path to repo

rugged[R]

Rugged repo object

Public Class Methods

new(path) click to toggle source

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

# File lib/gitlab_git/repository.rb, line 33
def initialize(path)
  @path = path
  @name = path.split("/").last
end

Public Instance Methods

add_tag(tag_name, ref_target, options = nil) click to toggle source

Add a tag with tag_name+ name to the repository in corresponding ref_target+ supports passing a hash of options to create an annotated tag

Valid annotation options are:

:tagger ::
  same structure as a committer, the user that is creating the tag

:message ::
  the message to include in the tag annotation

Returns a Gitlab::Git::Tag

# File lib/gitlab_git/repository.rb, line 751
def add_tag(tag_name, ref_target, options = nil)
  tag = rugged.tags.create(tag_name, ref_target, options)
  if tag.annotated?
    Tag.new(tag_name, ref_target, tag.annotation.message)
  else
    Tag.new(tag_name, ref_target)
  end
rescue Rugged::TagError
  raise InvalidRef.new("Tag #{tag_name} already exists")
rescue Rugged::ReferenceError
  raise InvalidRef.new("Target #{ref_target} is invalid")
end
archive_file_path(name, storage_path, format = "tar.gz") click to toggle source
# File lib/gitlab_git/repository.rb, line 188
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") click to toggle source
# File lib/gitlab_git/repository.rb, line 172
def archive_metadata(ref, storage_path, format = "tar.gz")
  ref ||= root_ref
  commit = Gitlab::Git::Commit.find(self, ref)
  return {} if commit.nil?

  project_name = self.name.chomp('.git')
  prefix = "#{project_name}-#{ref}-#{commit.id}"

  {
    'RepoPath' => path,
    'ArchivePrefix' => prefix,
    'ArchivePath' => archive_file_path(prefix, storage_path, format),
    'CommitId' => commit.id,
  }
end
autocrlf() click to toggle source
# File lib/gitlab_git/repository.rb, line 858
def autocrlf
  AUTOCRLF_VALUES[rugged.config['core.autocrlf']]
end
autocrlf=(value) click to toggle source
# File lib/gitlab_git/repository.rb, line 862
def autocrlf=(value)
  rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
end
bare?() click to toggle source
# File lib/gitlab_git/repository.rb, line 133
def bare?
  rugged.bare?
end
branch_count() click to toggle source

Returns the number of valid branches

# File lib/gitlab_git/repository.rb, line 72
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_names() click to toggle source

Returns an Array of branch names sorted by name ASC

# File lib/gitlab_git/repository.rb, line 56
def branch_names
  branches.map(&:name)
end
branch_names_contains(commit) click to toggle source

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

Ex.

repo.branch_names_contains('master')
# File lib/gitlab_git/repository.rb, line 449
def branch_names_contains(commit)
  branches_contains(commit).map { |c| c.name }
end
branches() click to toggle source

Returns an Array of Branches

# File lib/gitlab_git/repository.rb, line 61
def branches
  rugged.branches.map do |rugged_ref|
    begin
      Branch.new(rugged_ref.name, rugged_ref.target)
    rescue Rugged::ReferenceError
      # Omit invalid branch
    end
  end.compact.sort_by(&:name)
end
branches_contains(commit) click to toggle source

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

Ex.

repo.branch_names_contains('master')
# File lib/gitlab_git/repository.rb, line 458
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") click to toggle source

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.
# File lib/gitlab_git/repository.rb, line 713
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 = {}) click to toggle source

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
# File lib/gitlab_git/repository.rb, line 566
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) click to toggle source

Return total commits count accessible from passed ref

# File lib/gitlab_git/repository.rb, line 526
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) click to toggle source

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.

# File lib/gitlab_git/repository.rb, line 333
def commits_between(from, to)
  walker = Rugged::Walker.new(rugged)
  walker.sorting(Rugged::SORT_DATE | 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) click to toggle source
# File lib/gitlab_git/repository.rb, line 835
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) click to toggle source
# File lib/gitlab_git/repository.rb, line 944
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.exists?(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
  File.write(info_attributes_path, gitattributes_content)
end
count_commits_between(from, to) click to toggle source

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

# File lib/gitlab_git/repository.rb, line 350
def count_commits_between(from, to)
  commits_between(from, to).size
end
create_branch(ref, start_point = "HEAD") click to toggle source

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

Examples:

create_branch("feature")
create_branch("other-feature", "master")
# File lib/gitlab_git/repository.rb, line 732
def create_branch(ref, start_point = "HEAD")
  rugged_ref = rugged.branches.create(ref, start_point)
  Branch.new(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) click to toggle source

Delete the specified branch from the repository

# File lib/gitlab_git/repository.rb, line 723
def delete_branch(branch_name)
  rugged.branches.delete(branch_name)
end
diff(from, to, options = {}, *paths) click to toggle source

Return an array of Diff objects that represent the diff between from and to. See Gitlab::Git::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.

# File lib/gitlab_git/repository.rb, line 363
def diff(from, to, options = {}, *paths)
  DiffCollection.new(diff_patches(from, to, options, *paths), options)
end
diff_text(from, to, options = {}, *paths) click to toggle source

Return the diff between from and to in a single patch string. The options hash has the same allowed keys as diff.

# File lib/gitlab_git/repository.rb, line 369
def diff_text(from, to, options = {}, *paths)
  # NOTE: It would be simpler to use the Rugged::Diff#patch method, but
  # that formats the diff text differently than Rugged::Patch#to_s for
  # changes to binary files.
  diff_patches(from, to, options, *paths).map do |p|
    p.to_s
  end.join("\n")
end
diffable?(blob) click to toggle source

Checks if the blob should be diffable according to its attributes

# File lib/gitlab_git/repository.rb, line 972
def diffable?(blob)
  blob_attributes = attributes(blob.path).to_h
  blob_attributes.fetch('diff', blob.text?)
end
discover_default_branch() click to toggle source

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

# File lib/gitlab_git/repository.rb, line 146
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
empty?() click to toggle source
# File lib/gitlab_git/repository.rb, line 129
def empty?
  rugged.empty?
end
fetch(remote_name) click to toggle source

Fetch the specified remote

# File lib/gitlab_git/repository.rb, line 789
def fetch(remote_name)
  rugged.remotes[remote_name].fetch
end
find_commits(options = {}) click to toggle source

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
# File lib/gitlab_git/repository.rb, line 395
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|
    gitlab_commit = Gitlab::Git::Commit.decorate(commit)
    commits.push(gitlab_commit)
  end

  walker.reset

  commits
rescue Rugged::OdbError
  []
end
format_patch(from, to, options = {}) click to toggle source

Return a String containing the mbox-formatted diff between from and to. See diff for the allowed keys in the options hash.

# File lib/gitlab_git/repository.rb, line 800
def format_patch(from, to, options = {})
  options ||= {}
  break_rewrites = options[:break_rewrites]
  actual_options = Diff.filter_diff_options(options)

  from_sha = rugged.rev_parse_oid(from)
  to_sha = rugged.rev_parse_oid(to)
  commits_between(from_sha, to_sha).map do |commit|
    # Ignore merge commits, which have more than one parent,
    # in creation of patch to mimic the behavior of `git format-patch`
    commit.to_mbox(actual_options) if commit.parents.length <= 1
  end.flatten.join("\n")
end
has_commits?() click to toggle source
# File lib/gitlab_git/repository.rb, line 125
def has_commits?
  !empty?
end
heads() click to toggle source

Deprecated. Will be removed in 5.2

# File lib/gitlab_git/repository.rb, line 119
def heads
  rugged.references.each("refs/heads/*").map do |head|
    Gitlab::Git::Ref.new(head.name, head.target)
  end.sort_by(&:name)
end
log(options) click to toggle source

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)
)
# File lib/gitlab_git/repository.rb, line 248
def log(options)
  default_options = {
    limit: 10,
    offset: 0,
    path: nil,
    ref: root_ref,
    follow: false,
    skip_merges: false,
    disable_walk: false,
    after: nil,
    before: nil
  }

  options = default_options.merge(options)
  options[:limit] ||= 0
  options[:offset] ||= 0
  actual_ref = options[:ref] || root_ref
  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
log_by_shell(sha, options) click to toggle source
# File lib/gitlab_git/repository.rb, line 297
def log_by_shell(sha, options)
  cmd = %W(git --git-dir=#{path} log)
  cmd += %W(-n #{options[:limit].to_i})
  cmd += %W(--format=%H)
  cmd += %W(--skip=#{options[:offset].to_i})
  cmd += %W(--follow) if options[:follow]
  cmd += %W(--no-merges) if options[:skip_merges]
  cmd += %W(--after=#{options[:after].iso8601}) if options[:after]
  cmd += %W(--before=#{options[:before].iso8601}) if options[:before]
  cmd += [sha]
  cmd += %W(-- #{options[:path]}) if options[:path].present?

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

  log = raw_output.lines.map do |c|
    Rugged::Commit.new(rugged, c.strip)
  end

  log.is_a?(Array) ? log : []
end
log_by_walk(sha, options) click to toggle source
# File lib/gitlab_git/repository.rb, line 287
def log_by_walk(sha, options)
  walk_options = {
    show: sha,
    sort: Rugged::SORT_DATE,
    limit: options[:limit],
    offset: options[:offset]
  }
  Rugged::Walker.walk(rugged, walk_options).to_a
end
log_using_shell?(options) click to toggle source
# File lib/gitlab_git/repository.rb, line 279
def log_using_shell?(options)
  options[:path].present? ||
    options[:disable_walk] ||
    options[:skip_merges] ||
    options[:after] ||
    options[:before]
end
lookup(oid_or_ref_name) click to toggle source

Lookup for rugged object by oid or ref name

# File lib/gitlab_git/repository.rb, line 494
def lookup(oid_or_ref_name)
  rugged.rev_parse(oid_or_ref_name)
end
ls_files(ref) click to toggle source

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

Ex.

repo.ls_files('master')
# File lib/gitlab_git/repository.rb, line 918
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 = {}) click to toggle source

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.

# File lib/gitlab_git/repository.rb, line 817
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) click to toggle source

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

# File lib/gitlab_git/repository.rb, line 355
def merge_base_commit(from, to)
  rugged.merge_base(from, to)
end
mkdir(path, options = {}) click to toggle source

Create a new directory with a .gitkeep file. Creates all required nested directories (i.e. mkdir -p behavior)

options should contain next structure:

author: {
  email: 'user@example.com',
  name: 'Test User',
  time: Time.now
},
committer: {
  email: 'user@example.com',
  name: 'Test User',
  time: Time.now
},
commit: {
  message: 'Wow such commit',
  branch: 'master'
}
# File lib/gitlab_git/repository.rb, line 884
def mkdir(path, options = {})
  # Check if this directory exists; if it does, then don't bother
  # adding .gitkeep file.
  ref = options[:commit][:branch]
  path = PathHelper.normalize_path(path).to_s
  rugged_ref = rugged.ref(ref)

  raise InvalidRef.new("Invalid ref") if rugged_ref.nil?
  target_commit = rugged_ref.target
  raise InvalidRef.new("Invalid target commit") if target_commit.nil?

  entry = tree_entry(target_commit, path)
  if entry
    if entry[:type] == :blob
      raise InvalidBlobName.new("Directory already exists as a file")
    else
      raise InvalidBlobName.new("Directory already exists")
    end
  end

  options[:file] = {
    content: '',
    path: "#{path}/.gitkeep",
    update: true
  }

  Blob.commit(self, options)
end
push(remote_name, *refspecs) click to toggle source

Push +*refspecs+ to the remote identified by remote_name.

# File lib/gitlab_git/repository.rb, line 794
def push(remote_name, *refspecs)
  rugged.remotes[remote_name].push(refspecs)
end
raw() click to toggle source

Alias to old method for compatibility

# File lib/gitlab_git/repository.rb, line 44
def raw
  rugged
end
ref_names() click to toggle source

Returns an Array of branch and tag names

# File lib/gitlab_git/repository.rb, line 114
def ref_names
  branch_names + tag_names
end
refs_hash() click to toggle source

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

# File lib/gitlab_git/repository.rb, line 476
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
remote_add(remote_name, url) click to toggle source

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

# File lib/gitlab_git/repository.rb, line 775
def remote_add(remote_name, url)
  rugged.remotes.create(remote_name, url)
end
remote_delete(remote_name) click to toggle source

Delete the specified remote from this repository.

# File lib/gitlab_git/repository.rb, line 770
def remote_delete(remote_name)
  rugged.remotes.delete(remote_name)
end
remote_names() click to toggle source

Return an array of this repository's remote names

# File lib/gitlab_git/repository.rb, line 765
def remote_names
  rugged.remotes.each_name.to_a
end
remote_update(remote_name, options = {}) click to toggle source

Update the specified remote using the values in the options hash

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

# File lib/gitlab_git/repository.rb, line 783
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?() click to toggle source
# File lib/gitlab_git/repository.rb, line 137
def repo_exists?
  !!rugged
end
reset(ref, reset_type) click to toggle source

Sets HEAD to the commit specified by ref; ref can be a branch or tag name or a commit SHA. Valid reset_type values are:

[:soft]
  the head will be moved to the commit.
[:mixed]
  will trigger a +:soft+ reset, plus the index will be replaced
  with the content of the commit tree.
[:hard]
  will trigger a +:mixed+ reset and the working directory will be
  replaced with the content of the index. (Untracked and ignored files
  will be left alone)
# File lib/gitlab_git/repository.rb, line 546
def reset(ref, reset_type)
  rugged.reset(ref, reset_type)
end
rev_parse_target(revspec) click to toggle source

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

# File lib/gitlab_git/repository.rb, line 324
def rev_parse_target(revspec)
  obj = rugged.rev_parse(revspec)
  obj = obj.target while obj.is_a?(Rugged::Tag::Annotation)
  obj
end
root_ref() click to toggle source

Default branch in the repository

# File lib/gitlab_git/repository.rb, line 39
def root_ref
  @root_ref ||= discover_default_branch
end
rugged_head() click to toggle source
# File lib/gitlab_git/repository.rb, line 166
def rugged_head
  rugged.head
rescue Rugged::ReferenceError
  nil
end
search_files(query, ref = nil) click to toggle source

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

# File lib/gitlab_git/repository.rb, line 217
def search_files(query, ref = nil)
  greps = []
  ref ||= root_ref

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

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

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

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

  greps
end
sha_from_ref(ref) click to toggle source
# File lib/gitlab_git/repository.rb, line 318
def sha_from_ref(ref)
  rev_parse_target(ref).oid
end
size() click to toggle source

Return repo size in megabytes

# File lib/gitlab_git/repository.rb, line 210
def size
  size = popen(%W(du -sk), path).first.strip.to_i
  (size.to_f / 1024).round(2)
end
submodules(ref) click to toggle source

Return hash with submodules info for this repository

Ex.

{
  "rack"  => {
    "id" => "c67be4624545b4263184c4a0e8f887efd0a66320",
    "path" => "rack",
    "url" => "git://github.com/chneukirchen/rack.git"
  },
  "encoding" => {
    "id" => ....
  }
}
# File lib/gitlab_git/repository.rb, line 512
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) click to toggle source

Returns true if the given tag exists

name - The name of the tag as a String.

# File lib/gitlab_git/repository.rb, line 109
def tag_exists?(name)
  !!rugged.tags[name]
end
tag_names() click to toggle source

Returns an Array of tag names

# File lib/gitlab_git/repository.rb, line 85
def tag_names
  rugged.tags.map { |t| t.name }
end
tags() click to toggle source

Returns an Array of Tags

# File lib/gitlab_git/repository.rb, line 90
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

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

Private Instance Methods

archive_to_file(treeish = 'master', filename = 'archive.tar.gz', format = nil, compress_cmd = %W(gzip -n)) click to toggle source
# File lib/gitlab_git/repository.rb, line 1113
def archive_to_file(treeish = 'master', filename = 'archive.tar.gz', format = nil, compress_cmd = %W(gzip -n))
  git_archive_cmd = %W(git --git-dir=#{path} archive)

  # Put files into a directory before archiving
  prefix = "#{archive_name(treeish)}/"
  git_archive_cmd << "--prefix=#{prefix}"

  # Format defaults to tar
  git_archive_cmd << "--format=#{format}" if format

  git_archive_cmd += %W(-- #{treeish})

  open(filename, 'w') do |file|
    # Create a pipe to act as the '|' in 'git archive ... | gzip'
    pipe_rd, pipe_wr = IO.pipe

    # Get the compression process ready to accept data from the read end
    # of the pipe
    compress_pid = spawn(*nice(compress_cmd), in: pipe_rd, out: file)
    # The read end belongs to the compression process now; we should
    # close our file descriptor for it.
    pipe_rd.close

    # Start 'git archive' and tell it to write into the write end of the
    # pipe.
    git_archive_pid = spawn(*nice(git_archive_cmd), out: pipe_wr)
    # The write end belongs to 'git archive' now; close it.
    pipe_wr.close

    # When 'git archive' and the compression process are finished, we are
    # done.
    Process.waitpid(git_archive_pid)
    raise "#{git_archive_cmd.join(' ')} failed" unless $?.success?
    Process.waitpid(compress_pid)
    raise "#{compress_cmd.join(' ')} failed" unless $?.success?
  end
end
blob_content(commit, blob_name) click to toggle source

Get the content of a blob for a given commit. If the blob is a commit (for submodules) then return the blob's OID.

# File lib/gitlab_git/repository.rb, line 981
def blob_content(commit, blob_name)
  blob_entry = tree_entry(commit, blob_name)

  unless blob_entry
    raise InvalidBlobName.new("Invalid blob name: #{blob_name}")
  end

  case blob_entry[:type]
  when :commit
    blob_entry[:oid]
  when :tree
    raise InvalidBlobName.new("#{blob_name} is a tree, not a blob")
  when :blob
    rugged.lookup(blob_entry[:oid]).content
  end
end
build_greps(file_contents, query, ref, filename) click to toggle source

Return an array of BlobSnippets for lines in file_contents that match query

# File lib/gitlab_git/repository.rb, line 1179
def build_greps(file_contents, query, ref, filename)
  # The file_contents string is potentially huge so we make sure to loop
  # through it one line at a time. This gives Ruby the chance to GC lines
  # we are not interested in.
  #
  # We need to do a little extra work because we are not looking for just
  # the lines that matches the query, but also for the context
  # (surrounding lines). We will use Enumerable#each_cons to efficiently
  # loop through the lines while keeping surrounding lines on hand.
  #
  # First, we turn "foo\nbar\nbaz" into
  # [
  #  [nil, -3], [nil, -2], [nil, -1],
  #  ['foo', 0], ['bar', 1], ['baz', 3],
  #  [nil, 4], [nil, 5], [nil, 6]
  # ]
  lines_with_index = Enumerator.new do |yielder|
    # Yield fake 'before' lines for the first line of file_contents
    (-SEARCH_CONTEXT_LINES..-1).each do |i|
      yielder.yield [nil, i]
    end

    # Yield the actual file contents
    count = 0
    file_contents.each_line do |line|
      line.chomp!
      yielder.yield [line, count]
      count += 1
    end

    # Yield fake 'after' lines for the last line of file_contents
    (count+1..count+SEARCH_CONTEXT_LINES).each do |i|
      yielder.yield [nil, i]
    end
  end

  greps = []

  # Loop through consecutive blocks of lines with indexes
  lines_with_index.each_cons(2 * SEARCH_CONTEXT_LINES + 1) do |line_block|
    # Get the 'middle' line and index from the block
    line, i = line_block[SEARCH_CONTEXT_LINES]

    next unless line && line.match(/#{Regexp.escape(query)}/i)

    # Yay, 'line' contains a match!
    # Get an array with just the context lines (no indexes)
    match_with_context = line_block.map(&:first)
    # Remove 'nil' lines in case we are close to the first or last line
    match_with_context.compact!

    # Get the line number (1-indexed) of the first context line
    first_context_line_number = line_block[0][1] + 1

    greps << Gitlab::Git::BlobSnippet.new(
      ref,
      match_with_context,
      first_context_line_number,
      filename
    )
  end

  greps
end
commit_touches_path?(commit, path, follow, walker) click to toggle source

Returns true if commit introduced changes to path, using commit trees to make that determination. Uses the history simplification rules that `git log` uses by default, where a commit is omitted if it is TREESAME to any parent.

If the follow option is true and the file specified by path was renamed, then the path value is set to the old path.

# File lib/gitlab_git/repository.rb, line 1035
def commit_touches_path?(commit, path, follow, walker)
  entry = tree_entry(commit, path)

  if commit.parents.empty?
    # This is the root commit, return true if it has +path+ in its tree
    return !entry.nil?
  end

  num_treesame = 0
  commit.parents.each do |parent|
    parent_entry = tree_entry(parent, path)

    # Only follow the first TREESAME parent for merge commits
    if num_treesame > 0
      walker.hide(parent)
      next
    end

    if entry.nil? && parent_entry.nil?
      num_treesame += 1
    elsif entry && parent_entry && entry[:oid] == parent_entry[:oid]
      num_treesame += 1
    end
  end

  case num_treesame
  when 0
    detect_rename(commit, commit.parents.first, path) if follow
    true
  else false
  end
end
detect_rename(commit, parent, path) click to toggle source

Compare commit and parent for path. If path is a file and was renamed in commit, then set path to the old filename.

# File lib/gitlab_git/repository.rb, line 1092
def detect_rename(commit, parent, path)
  diff = parent.diff(commit, paths: [path], disable_pathspec_match: true)

  # If +path+ is a filename, not a directory, then we should only have
  # one delta.  We don't need to follow renames for directories.
  return nil if diff.each_delta.count > 1

  delta = diff.each_delta.first
  if delta.added?
    full_diff = parent.diff(commit)
    full_diff.find_similar!

    full_diff.each_delta do |full_delta|
      if full_delta.renamed? && path == full_delta.new_file[:path]
        # Look for the old path in ancestors
        path.replace(full_delta.old_file[:path])
      end
    end
  end
end
diff_patches(from, to, options = {}, *paths) click to toggle source

Return the Rugged patches for the diff between from and to.

# File lib/gitlab_git/repository.rb, line 1245
def diff_patches(from, to, options = {}, *paths)
  options ||= {}
  break_rewrites = options[:break_rewrites]
  actual_options = Diff.filter_diff_options(options.merge(paths: paths))

  diff = rugged.diff(from, to, actual_options)
  diff.find_similar!(break_rewrites: break_rewrites)
  diff.each_patch
end
nice(cmd) click to toggle source
# File lib/gitlab_git/repository.rb, line 1151
def nice(cmd)
  nice_cmd = %W(nice -n 20)
  unless unsupported_platform?
    nice_cmd += %W(ionice -c 2 -n 7)
  end
  nice_cmd + cmd
end
parse_gitmodules(commit, content) click to toggle source

Parses the contents of a .gitmodules file and returns a hash of submodule information.

# File lib/gitlab_git/repository.rb, line 1000
def parse_gitmodules(commit, content)
  results = {}

  current = ""
  content.split("\n").each do |txt|
    if txt =~ /^\s*\[/
      current = txt.match(/(?<=").*(?=")/)[0]
      results[current] = {}
    else
      next unless results[current]
      match_data = txt.match(/(\w+)\s*=\s*(.*)/)
      next unless match_data
      target = match_data[2].chomp
      results[current][match_data[1]] = target

      if match_data[1] == "path"
        begin
          results[current]["id"] = blob_content(commit, target)
        rescue InvalidBlobName
          results.delete(current)
        end
      end
    end
  end

  results
end
populated_index(ref_name) click to toggle source

Return a Rugged::Index that has read from the tree at ref_name

# File lib/gitlab_git/repository.rb, line 1170
def populated_index(ref_name)
  commit = rev_parse_target(ref_name)
  index = rugged.index
  index.read_tree(commit.tree)
  index
end
submodule?(index_entry) click to toggle source

Returns true if the index entry has the special file mode that denotes a submodule.

# File lib/gitlab_git/repository.rb, line 1165
def submodule?(index_entry)
  index_entry[:mode] == 57344
end
tree_entry(commit, path) click to toggle source

Find the entry for path in the tree for commit

# File lib/gitlab_git/repository.rb, line 1069
def tree_entry(commit, path)
  pathname = Pathname.new(path)
  first = true
  tmp_entry = nil

  pathname.each_filename do |dir|
    if first
      tmp_entry = commit.tree[dir]
      first = false
    elsif tmp_entry.nil?
      return nil
    else
      tmp_entry = rugged.lookup(tmp_entry[:oid])
      return nil unless tmp_entry.type == :tree
      tmp_entry = tmp_entry[dir]
    end
  end

  tmp_entry
end
unsupported_platform?() click to toggle source
# File lib/gitlab_git/repository.rb, line 1159
def unsupported_platform?
  %w( darwin freebsd solaris ).map{ |platform| RUBY_PLATFORM.include?(platform) }.any?
end