class Gitlab::Git::Blob

Constants

DATA_FRAGMENT_SIZE

This number needs to be large enough to allow reliable content / encoding detection (Linguist) and LFS pointer parsing. All other cases where we need full blob data should use load_all_data!.

Attributes

commit_id[RW]
data[RW]
id[RW]
mode[RW]
name[RW]
path[RW]
size[RW]

Public Class Methods

commit(repository, options, action = :add) click to toggle source

Commit file in repository and return commit sha

options should contain next structure:

file: {
  content: 'Lorem ipsum...',
  path: 'documents/story.txt',
  update: true
},
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/blob.rb, line 120
def commit(repository, options, action = :add)
  file = options[:file]
  update = file[:update].nil? ? true : file[:update]
  author = options[:author]
  committer = options[:committer]
  commit = options[:commit]
  repo = repository.rugged
  ref = commit[:branch]
  parents = []

  unless ref.start_with?('refs/')
    ref = 'refs/heads/' + ref
  end

  path_name = PathHelper.normalize_path(file[:path])
  # Abort if any invalid characters remain (e.g. ../foo)
  raise Repository::InvalidBlobName.new("Invalid path") if path_name.each_filename.to_a.include?('..')

  filename = path_name.to_s
  index = repo.index

  unless repo.empty?
    rugged_ref = repo.references[ref]
    raise Repository::InvalidRef.new("Invalid branch name") unless rugged_ref
    last_commit = rugged_ref.target
    index.read_tree(last_commit.tree)
    parents = [last_commit]
  end

  if action == :remove
    index.remove(filename)
  else
    mode = 0100644
    file_entry = index.get(filename)

    if file_entry
      raise Repository::InvalidBlobName.new("Filename already exists; update not allowed") unless update
      # Preserve the current file mode if one is available
      mode = file_entry[:mode] if file_entry[:mode]
    end

    content = file[:content]
    detect = CharlockHolmes::EncodingDetector.new.detect(content) if content

    unless detect && detect[:type] == :binary
      # When writing to the repo directly as we are doing here,
      # the `core.autocrlf` config isn't taken into account.
      content.gsub!("\r\n", "\n") if repository.autocrlf
    end

    oid = repo.write(content, :blob)
    index.add(path: filename, oid: oid, mode: mode)
  end

  opts = {}
  opts[:tree] = index.write_tree(repo)
  opts[:author] = author
  opts[:committer] = committer
  opts[:message] = commit[:message]
  opts[:parents] = parents
  opts[:update_ref] = ref

  Rugged::Commit.create(repo, opts)
end
find(repository, sha, path) click to toggle source
# File lib/gitlab_git/blob.rb, line 18
def find(repository, sha, path)
  commit = repository.lookup(sha)
  root_tree = commit.tree

  blob_entry = find_entry_by_path(repository, root_tree.oid, path)

  return nil unless blob_entry

  if blob_entry[:type] == :commit
    submodule_blob(blob_entry, path, sha)
  else
    blob = repository.lookup(blob_entry[:oid])

    if blob
      Blob.new(
        id: blob.oid,
        name: blob_entry[:name],
        size: blob.size,
        data: blob.content(DATA_FRAGMENT_SIZE),
        mode: blob_entry[:filemode].to_s(8),
        path: path,
        commit_id: sha,
      )
    end
  end
end
find_entry_by_path(repository, root_id, path) click to toggle source

Recursive search of blob id by path

Ex.

blog/            # oid: 1a
  app/           # oid: 2a
    models/      # oid: 3a
    file.rb      # oid: 4a

::find_entry_by_path(repo, '1a', 'app/file.rb') # => '4a'

# File lib/gitlab_git/blob.rb, line 66
def find_entry_by_path(repository, root_id, path)
  root_tree = repository.lookup(root_id)
  # Strip leading slashes
  path[/^\/*/] = ''
  path_arr = path.split('/')

  entry = root_tree.find do |entry|
    entry[:name] == path_arr[0]
  end

  return nil unless entry

  if path_arr.size > 1
    return nil unless entry[:type] == :tree
    path_arr.shift
    find_entry_by_path(repository, entry[:oid], path_arr.join('/'))
  else
    [:blob, :commit].include?(entry[:type]) ? entry : nil
  end
end
new(options) click to toggle source
# File lib/gitlab_git/blob.rb, line 211
def initialize(options)
  %w(id name path size data mode commit_id).each do |key|
    self.send("#{key}=", options[key.to_sym])
  end
end
raw(repository, sha) click to toggle source
# File lib/gitlab_git/blob.rb, line 45
def raw(repository, sha)
  blob = repository.lookup(sha)

  Blob.new(
    id: blob.oid,
    size: blob.size,
    data: blob.content(DATA_FRAGMENT_SIZE),
  )
end
remove(repository, options) click to toggle source

Remove file from repository and return commit sha

options should contain next structure:

file: {
  path: 'documents/story.txt'
},
author: {
  email: 'user@example.com',
  name: 'Test User',
  time: Time.now
},
committer: {
  email: 'user@example.com',
  name: 'Test User',
  time: Time.now
},
commit: {
  message: 'Remove FILENAME',
  branch: 'master'
}
# File lib/gitlab_git/blob.rb, line 206
def remove(repository, options)
  commit(repository, options, :remove)
end
submodule_blob(blob_entry, path, sha) click to toggle source
# File lib/gitlab_git/blob.rb, line 87
def submodule_blob(blob_entry, path, sha)
  Blob.new(
    id: blob_entry[:oid],
    name: blob_entry[:name],
    data: '',
    path: path,
    commit_id: sha,
  )
end

Public Instance Methods

empty?() click to toggle source
# File lib/gitlab_git/blob.rb, line 217
def empty?
  !data || data == ''
end
lfs_oid() click to toggle source
# File lib/gitlab_git/blob.rb, line 246
def lfs_oid
  if has_lfs_version_key?
    oid = data.match(/(?<=sha256:)([0-9a-f]{64})/)
    return oid[1] if oid
  end

  nil
end
lfs_pointer?() click to toggle source

Valid LFS object pointer is a text file consisting of version oid size see github.com/github/git-lfs/blob/v1.1.0/docs/spec.md#the-pointer

# File lib/gitlab_git/blob.rb, line 242
def lfs_pointer?
  has_lfs_version_key? && lfs_oid.present? && lfs_size.present?
end
lfs_size() click to toggle source
# File lib/gitlab_git/blob.rb, line 255
def lfs_size
  if has_lfs_version_key?
    size = data.match(/(?<=size )([0-9]+)/)
    return size[1] if size
  end

  nil
end
load_all_data!(repository) click to toggle source

Load all blob data (not just the first DATA_FRAGMENT_SIZE bytes) into memory as a Ruby string.

# File lib/gitlab_git/blob.rb, line 227
def load_all_data!(repository)
  return if @data == '' # don't mess with submodule blobs

  @data = repository.lookup(id).content
end

Private Instance Methods

has_lfs_version_key?() click to toggle source
# File lib/gitlab_git/blob.rb, line 266
def has_lfs_version_key?
  !empty? && text? && data.start_with?("version https://git-lfs.github.com/spec")
end