Commit a00578ce authored by Robert Speicher's avatar Robert Speicher

Absorb gitlab_git

parent aec04a47
......@@ -16,6 +16,8 @@ gem 'default_value_for', '~> 3.0.0'
gem 'mysql2', '~> 0.3.16', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.24.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
......@@ -49,10 +51,6 @@ gem 'u2f', '~> 0.2.1'
# Browser detection
gem 'browser', '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem 'gitlab_git', '~> 10.7.0'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
......
......@@ -255,11 +255,6 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-markup (1.5.0)
gitlab_git (10.7.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
rugged (~> 0.24.0)
gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9)
omniauth (~> 1.0)
......@@ -857,7 +852,6 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.0)
gitlab_git (~> 10.7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2)
......@@ -942,6 +936,7 @@ DEPENDENCIES
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
rugged (~> 0.24.0)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0)
......@@ -988,4 +983,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.13.6
1.13.7
class MergeRequestDiff < ActiveRecord::Base
include Sortable
include Importable
include EncodingHelper
include Gitlab::Git::EncodingHelper
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100
......
module Gitlab
module Git
# Class for parsing Git attribute files and extracting the attributes for
# file patterns.
#
# Unlike Rugged this parser only needs a single IO call (a call to `open`),
# vastly reducing the time spent in extracting attributes.
#
# This class _only_ supports parsing the attributes file located at
# `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
# (`.gitattributes` is copied to this particular path).
#
# Basic usage:
#
# attributes = Gitlab::Git::Attributes.new(some_repo.path)
#
# attributes.attributes('README.md') # => { "eol" => "lf }
class Attributes
# path - The path to the Git repository.
def initialize(path)
@path = File.expand_path(path)
@patterns = nil
end
# Returns all the Git attributes for the given path.
#
# path - A path to a file for which to get the attributes.
#
# Returns a Hash.
def attributes(path)
full_path = File.join(@path, path)
patterns.each do |pattern, attrs|
return attrs if File.fnmatch?(pattern, full_path)
end
{}
end
# Returns a Hash containing the file patterns and their attributes.
def patterns
@patterns ||= parse_file
end
# Parses an attribute string.
#
# These strings can be in the following formats:
#
# text # => { "text" => true }
# -text # => { "text" => false }
# key=value # => { "key" => "value" }
#
# string - The string to parse.
#
# Returns a Hash containing the attributes and their values.
def parse_attributes(string)
values = {}
dash = '-'
equal = '='
binary = 'binary'
string.split(/\s+/).each do |chunk|
# Data such as "foo = bar" should be treated as "foo" and "bar" being
# separate boolean attributes.
next if chunk == equal
key = chunk
# Input: "-foo"
if chunk.start_with?(dash)
key = chunk.byteslice(1, chunk.length - 1)
value = false
# Input: "foo=bar"
elsif chunk.include?(equal)
key, value = chunk.split(equal, 2)
# Input: "foo"
else
value = true
end
values[key] = value
# When the "binary" option is set the "diff" option should be set to
# the inverse. If "diff" is later set it should overwrite the
# automatically set value.
values['diff'] = false if key == binary && value
end
values
end
# Iterates over every line in the attributes file.
def each_line
full_path = File.join(@path, 'info/attributes')
return unless File.exist?(full_path)
File.open(full_path, 'r') do |handle|
handle.each_line do |line|
break unless line.valid_encoding?
yield line.strip
end
end
end
private
# Parses the Git attributes file.
def parse_file
pairs = []
comment = '#'
each_line do |line|
next if line.start_with?(comment) || line.empty?
pattern, attrs = line.split(/\s+/, 2)
parsed = attrs ? parse_attributes(attrs) : {}
pairs << [File.join(@path, pattern), parsed]
end
# Newer entries take precedence over older entries.
pairs.reverse.to_h
end
end
end
end
require_relative 'encoding_helper'
module Gitlab
module Git
class Blame
include Gitlab::Git::EncodingHelper
attr_reader :lines, :blames
def initialize(repository, sha, path)
@repo = repository
@sha = sha
@path = path
@lines = []
@blames = load_blame
end
def each
@blames.each do |blame|
yield(
Gitlab::Git::Commit.new(blame.commit),
blame.line
)
end
end
private
def load_blame
cmd = %W(git --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path})
# Read in binary mode to ensure ASCII-8BIT
raw_output = IO.popen(cmd, 'rb') {|io| io.read }
output = encode_utf8(raw_output)
process_raw_blame output
end
def process_raw_blame(output)
lines, final = [], []
info, commits = {}, {}
# process the output
output.split("\n").each do |line|
if line[0, 1] == "\t"
lines << line[1, line.size]
elsif m = /^(\w{40}) (\d+) (\d+)/.match(line)
commit_id, old_lineno, lineno = m[1], m[2].to_i, m[3].to_i
commits[commit_id] = nil unless commits.key?(commit_id)
info[lineno] = [commit_id, old_lineno]
end
end
# load all commits in single call
commits.keys.each do |key|
commits[key] = @repo.lookup(key)
end
# get it together
info.sort.each do |lineno, (commit_id, old_lineno)|
commit = commits[commit_id]
final << BlameLine.new(lineno, old_lineno, commit, lines[lineno - 1])
end
@lines = final
end
end
class BlameLine
attr_accessor :lineno, :oldlineno, :commit, :line
def initialize(lineno, oldlineno, commit, line)
@lineno = lineno
@oldlineno = oldlineno
@commit = commit
@line = line
end
end
end
end
This diff is collapsed.
module Gitlab
module Git
class BlobSnippet
include Linguist::BlobHelper
attr_accessor :ref
attr_accessor :lines
attr_accessor :filename
attr_accessor :startline
def initialize(ref, lines, startline, filename)
@ref, @lines, @startline, @filename = ref, lines, startline, filename
end
def data
lines.join("\n") if lines
end
def name
filename
end
def size
data.length
end
def mode
nil
end
end
end
end
module Gitlab
module Git
class Branch < Ref
end
end
end
# Gitlab::Git::Commit is a wrapper around native Rugged::Commit object
module Gitlab
module Git
class Commit
include Gitlab::Git::EncodingHelper
attr_accessor :raw_commit, :head, :refs
SERIALIZE_KEYS = [
:id, :message, :parent_ids,
:authored_date, :author_name, :author_email,
:committed_date, :committer_name, :committer_email
].freeze
attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator
def ==(other)
return false unless other.is_a?(Gitlab::Git::Commit)
methods = [:message, :parent_ids, :authored_date, :author_name,
:author_email, :committed_date, :committer_name,
:committer_email]
methods.all? do |method|
send(method) == other.send(method)
end
end
class << self
# Get commits collection
#
# Ex.
# Commit.where(
# repo: repo,
# ref: 'master',
# path: 'app/models',
# limit: 10,
# offset: 5,
# )
#
def where(options)
repo = options.delete(:repo)
raise 'Gitlab::Git::Repository is required' unless repo.respond_to?(:log)
repo.log(options).map { |c| decorate(c) }
end
# Get single commit
#
# Ex.
# Commit.find(repo, '29eda46b')
#
# Commit.find(repo, 'master')
#
def find(repo, commit_id = "HEAD")
return decorate(commit_id) if commit_id.is_a?(Rugged::Commit)
obj = if commit_id.is_a?(String)
repo.rev_parse_target(commit_id)
else
Ref.dereference_object(commit_id)
end
return nil unless obj.is_a?(Rugged::Commit)
decorate(obj)
rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, Gitlab::Git::Repository::NoRepository
nil
end
# Get last commit for HEAD
#
# Ex.
# Commit.last(repo)
#
def last(repo)
find(repo)
end
# Get last commit for specified path and ref
#
# Ex.
# Commit.last_for_path(repo, '29eda46b', 'app/models')
#
# Commit.last_for_path(repo, 'master', 'Gemfile')
#
def last_for_path(repo, ref, path = nil)
where(
repo: repo,
ref: ref,
path: path,
limit: 1
).first
end
# Get commits between two revspecs
# See also #repository.commits_between
#
# Ex.
# Commit.between(repo, '29eda46b', 'master')
#
def between(repo, base, head)
repo.commits_between(base, head).map do |commit|
decorate(commit)
end
rescue Rugged::ReferenceError
[]
end
# Delegate Repository#find_commits
def find_all(repo, options = {})
repo.find_commits(options)
end
def decorate(commit, ref = nil)
Gitlab::Git::Commit.new(commit, ref)
end
# Returns a diff object for the changes introduced by +rugged_commit+.
# If +rugged_commit+ doesn't have a parent, then the diff is between
# this commit and an empty repo. See Repository#diff for the keys
# allowed in the +options+ hash.
def diff_from_parent(rugged_commit, options = {})
options ||= {}
break_rewrites = options[:break_rewrites]
actual_options = Diff.filter_diff_options(options)
diff = if rugged_commit.parents.empty?
rugged_commit.diff(actual_options.merge(reverse: true))
else
rugged_commit.parents[0].diff(rugged_commit, actual_options)
end
diff.find_similar!(break_rewrites: break_rewrites)
diff
end
end
def initialize(raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit
if raw_commit.is_a?(Hash)
init_from_hash(raw_commit)
elsif raw_commit.is_a?(Rugged::Commit)
init_from_rugged(raw_commit)
else
raise "Invalid raw commit type: #{raw_commit.class}"
end
@head = head
end
def sha
id
end
def short_id(length = 10)
id.to_s[0..length]
end
def safe_message
@safe_message ||= message
end
def created_at
committed_date
end
# Was this commit committed by a different person than the original author?
def different_committer?
author_name != committer_name || author_email != committer_email
end
def parent_id
parent_ids.first
end
# Shows the diff between the commit's parent and the commit.
#
# Cuts out the header and stats from #to_patch and returns only the diff.
def to_diff(options = {})
diff_from_parent(options).patch
end
# Returns a diff object for the changes from this commit's first parent.
# If there is no parent, then the diff is between this commit and an
# empty repo. See Repository#diff for keys allowed in the +options+
# hash.
def diff_from_parent(options = {})
Commit.diff_from_parent(raw_commit, options)
end
def has_zero_stats?
stats.total.zero?
rescue
true
end
def no_commit_message
"--no commit message"
end
def to_hash
serialize_keys.map.with_object({}) do |key, hash|
hash[key] = send(key)
end
end
def date
committed_date
end
def diffs(options = {})
DiffCollection.new(diff_from_parent(options), options)
end
def parents
raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
end
def tree
raw_commit.tree
end
def stats
Gitlab::Git::CommitStats.new(self)
end
def to_patch(options = {})
begin
raw_commit.to_mbox(options)
rescue Rugged::InvalidError => ex
if ex.message =~ /Commit \w+ is a merge commit/
'Patch format is not currently supported for merge commits.'
end
end
end
# Get a collection of Rugged::Reference objects for this commit.
#
# Ex.
# commit.ref(repo)
#
def refs(repo)
repo.refs_hash[id]
end
# Get ref names collection
#
# Ex.
# commit.ref_names(repo)
#
def ref_names(repo)
refs(repo).map do |ref|
ref.name.sub(%r{^refs/(heads|remotes|tags)/}, "")
end
end
def message
encode! @message
end
def author_name
encode! @author_name
end
def author_email
encode! @author_email
end
def committer_name
encode! @committer_name
end
def committer_email
encode! @committer_email
end
private
def init_from_hash(hash)
raw_commit = hash.symbolize_keys
serialize_keys.each do |key|
send("#{key}=", raw_commit[key])
end
end
def init_from_rugged(commit)
author = commit.author
committer = commit.committer
@raw_commit = commit
@id = commit.oid
@message = commit.message
@authored_date = author[:time]
@committed_date = committer[:time]
@author_name = author[:name]
@author_email = author[:email]
@committer_name = committer[:name]
@committer_email = committer[:email]
@parent_ids = commit.parents.map(&:oid)
end
def serialize_keys
SERIALIZE_KEYS
end
end
end
end
# Gitlab::Git::CommitStats counts the additions, deletions, and total changes
# in a commit.
module Gitlab
module Git
class CommitStats
attr_reader :id, :additions, :deletions, :total
# Instantiate a CommitStats object
def initialize(commit)
@id = commit.id
@additions = 0
@deletions = 0
@total = 0
diff = commit.diff_from_parent
diff.each_patch do |p|
# TODO: Use the new Rugged convenience methods when they're released
@additions += p.stat[0]
@deletions += p.stat[1]
@total += p.changes
end
end
end
end
end
module Gitlab
module Git
class Compare
attr_reader :head, :base, :straight
def initialize(repository, base, head, straight = false)
@repository = repository
@straight = straight
unless base && head
@commits = []
return
end
@base = Gitlab::Git::Commit.find(repository, base.try(:strip))
@head = Gitlab::Git::Commit.find(repository, head.try(:strip))
@commits = [] unless @base && @head
@commits = [] if same
end
def same
@base && @head && @base.id == @head.id
end
def commits
return @commits if defined?(@commits)
@commits = Gitlab::Git::Commit.between(@repository, @base.id, @head.id)
end
def diffs(options = {})
unless @head && @base
return Gitlab::Git::DiffCollection.new([])
end
paths = options.delete(:paths) || []
options[:straight] = @straight
Gitlab::Git::Diff.between(@repository, @head.id, @base.id, options, *paths)
end
end
end
end
This diff is collapsed.
module Gitlab
module Git
class DiffCollection
include Enumerable
DEFAULT_LIMITS = { max_files: 100, max_lines: 5000 }.freeze
def initialize(iterator, options = {})
@iterator = iterator
@max_files = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
@max_lines = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
@max_bytes = @max_files * 5120 # Average 5 KB per file
@safe_max_files = [@max_files, DEFAULT_LIMITS[:max_files]].min
@safe_max_lines = [@max_lines, DEFAULT_LIMITS[:max_lines]].min
@safe_max_bytes = @safe_max_files * 5120 # Average 5 KB per file
@all_diffs = !!options.fetch(:all_diffs, false)
@no_collapse = !!options.fetch(:no_collapse, true)
@deltas_only = !!options.fetch(:deltas_only, false)
@line_count = 0
@byte_count = 0
@overflow = false
@array = Array.new
end
def each(&block)
if @populated
# @iterator.each is slower than just iterating the array in place
@array.each(&block)
elsif @deltas_only
each_delta(&block)
else
each_patch(&block)
end
end
def empty?
!@iterator.any?
end