311 lines
8.8 KiB
Ruby
311 lines
8.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'cgi'
|
|
require 'uri'
|
|
require 'fileutils'
|
|
require 'tmpdir'
|
|
require 'securerandom'
|
|
|
|
module QA
|
|
module Git
|
|
class Repository
|
|
include Scenario::Actable
|
|
include Support::Repeater
|
|
include Support::Run
|
|
|
|
attr_writer :use_lfs, :gpg_key_id
|
|
attr_accessor :env_vars, :default_branch
|
|
|
|
InvalidCredentialsError = Class.new(RuntimeError)
|
|
|
|
def initialize
|
|
# We set HOME to the current working directory (which is a
|
|
# temporary directory created in .perform()) so the temporarily dropped
|
|
# .netrc can be utilised
|
|
self.env_vars = [%Q{HOME="#{tmp_home_dir}"}]
|
|
@use_lfs = false
|
|
@gpg_key_id = nil
|
|
@default_branch = Runtime::Env.default_branch
|
|
end
|
|
|
|
def self.perform(*args)
|
|
Dir.mktmpdir do |dir|
|
|
Dir.chdir(dir) { super }
|
|
end
|
|
end
|
|
|
|
def password=(password)
|
|
@password = password
|
|
|
|
raise InvalidCredentialsError, "Please provide a username when setting a password" unless username
|
|
|
|
try_add_credentials_to_netrc
|
|
end
|
|
|
|
def uri=(address)
|
|
@uri = URI(address)
|
|
end
|
|
|
|
def username=(username)
|
|
@username = username
|
|
# Only include the user in the URI if we're using HTTP as this breaks
|
|
# SSH authentication.
|
|
@uri.user = username unless ssh_key_set?
|
|
end
|
|
|
|
def use_default_credentials
|
|
self.username, self.password = default_credentials
|
|
end
|
|
|
|
def use_default_identity
|
|
configure_identity('GitLab QA', 'root@gitlab.com')
|
|
end
|
|
|
|
def clone(opts = '')
|
|
clone_result = run_git("git clone #{opts} #{uri} ./", max_attempts: 3)
|
|
return clone_result.response unless clone_result.success?
|
|
|
|
enable_lfs_result = enable_lfs if use_lfs?
|
|
|
|
clone_result.to_s + enable_lfs_result.to_s
|
|
end
|
|
|
|
def checkout(branch_name, new_branch: false)
|
|
opts = new_branch ? '-b' : ''
|
|
run_git(%Q{git checkout #{opts} "#{branch_name}"}).to_s
|
|
end
|
|
|
|
def shallow_clone
|
|
clone('--depth 1')
|
|
end
|
|
|
|
def configure_identity(name, email)
|
|
run_git(%Q{git config user.name "#{name}"})
|
|
run_git(%Q{git config user.email #{email}})
|
|
end
|
|
|
|
def commit_file(name, contents, message)
|
|
add_file(name, contents)
|
|
commit(message)
|
|
end
|
|
|
|
def add_file(name, contents)
|
|
FileUtils.mkdir_p(::File.dirname(name))
|
|
|
|
::File.write(name, contents)
|
|
|
|
if use_lfs?
|
|
git_lfs_track_result = run_git(%Q{git lfs track #{name} --lockable})
|
|
return git_lfs_track_result.response unless git_lfs_track_result.success?
|
|
end
|
|
|
|
git_add_result = run_git(%Q{git add #{name}})
|
|
|
|
git_lfs_track_result.to_s + git_add_result.to_s
|
|
end
|
|
|
|
def add_tag(tag_name)
|
|
run_git("git tag #{tag_name}").to_s
|
|
end
|
|
|
|
def delete_tag(tag_name)
|
|
run_git(%Q{git push origin --delete #{tag_name}}, max_attempts: 3).to_s
|
|
end
|
|
|
|
def commit(message)
|
|
run_git(%Q{git commit -m "#{message}"}, max_attempts: 3).to_s
|
|
end
|
|
|
|
def commit_with_gpg(message)
|
|
run_git(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s
|
|
end
|
|
|
|
def current_branch
|
|
run_git("git rev-parse --abbrev-ref HEAD").to_s
|
|
end
|
|
|
|
def push_changes(branch = @default_branch, push_options: nil)
|
|
cmd = ['git push']
|
|
cmd << push_options_hash_to_string(push_options)
|
|
cmd << uri
|
|
cmd << branch
|
|
run_git(cmd.compact.join(' '), max_attempts: 3).to_s
|
|
end
|
|
|
|
def push_all_branches
|
|
run_git("git push --all").to_s
|
|
end
|
|
|
|
def push_tags_and_branches(branches)
|
|
run_git("git push --tags origin #{branches.join(' ')}").to_s
|
|
end
|
|
|
|
def merge(branch)
|
|
run_git("git merge #{branch}")
|
|
end
|
|
|
|
def init_repository
|
|
run_git("git init")
|
|
end
|
|
|
|
def pull(repository = nil, branch = nil)
|
|
run_git(['git', 'pull', repository, branch].compact.join(' '))
|
|
end
|
|
|
|
def commits
|
|
run_git('git log --oneline').to_s.split("\n")
|
|
end
|
|
|
|
def use_ssh_key(key)
|
|
@ssh = Support::SSH.perform do |ssh|
|
|
ssh.key = key
|
|
ssh.uri = uri
|
|
ssh.setup(env: self.env_vars)
|
|
ssh
|
|
end
|
|
|
|
self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{ssh.private_key_file.path} -o UserKnownHostsFile=#{ssh.known_hosts_file.path}"}
|
|
end
|
|
|
|
def delete_ssh_key
|
|
return unless ssh_key_set?
|
|
|
|
ssh.delete
|
|
end
|
|
|
|
def push_with_git_protocol(version, file_name, file_content, commit_message = 'Initial commit')
|
|
self.git_protocol = version
|
|
add_file(file_name, file_content)
|
|
commit(commit_message)
|
|
push_changes
|
|
|
|
fetch_supported_git_protocol
|
|
end
|
|
|
|
def git_protocol=(value)
|
|
raise ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2" unless %w[0 1 2].include?(value.to_s)
|
|
|
|
run_git("git config protocol.version #{value}")
|
|
end
|
|
|
|
def fetch_supported_git_protocol
|
|
# ls-remote is one command known to respond to Git protocol v2 so we use
|
|
# it to get output including the version reported via Git tracing
|
|
result = run_git("git ls-remote #{uri}", max_attempts: 3, env: [*self.env_vars, "GIT_TRACE_PACKET=1"])
|
|
result.response[/git< version (\d+)/, 1] || 'unknown'
|
|
end
|
|
|
|
def try_add_credentials_to_netrc
|
|
return unless add_credentials?
|
|
return if netrc_already_contains_content?
|
|
|
|
save_netrc_content
|
|
end
|
|
|
|
def file_content(file)
|
|
run("cat #{file}").to_s
|
|
end
|
|
|
|
def delete_netrc
|
|
File.delete(netrc_file_path) if File.exist?(netrc_file_path)
|
|
end
|
|
|
|
def remote_branches
|
|
# This gets the remote branch names
|
|
# When executed on a fresh repo it returns the default branch name
|
|
|
|
run_git('git --no-pager branch --list --remotes --format="%(refname:lstrip=3)"').to_s.split("\n")
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :uri, :username, :password, :ssh, :use_lfs
|
|
|
|
alias_method :use_lfs?, :use_lfs
|
|
|
|
def add_credentials?
|
|
return false if !username || !password
|
|
return true unless ssh_key_set?
|
|
|
|
false
|
|
end
|
|
|
|
def ssh_key_set?
|
|
ssh && !ssh.private_key_file.nil?
|
|
end
|
|
|
|
def enable_lfs
|
|
# git lfs install *needs* a .gitconfig defined at ${HOME}/.gitconfig
|
|
FileUtils.mkdir_p(tmp_home_dir)
|
|
touch_gitconfig_result = run("touch #{tmp_home_dir}/.gitconfig")
|
|
return touch_gitconfig_result.response unless touch_gitconfig_result.success?
|
|
|
|
git_lfs_install_result = run_git('git lfs install')
|
|
|
|
touch_gitconfig_result.to_s + git_lfs_install_result.to_s
|
|
end
|
|
|
|
def default_credentials
|
|
if ::QA::Runtime::User.ldap_user?
|
|
[Runtime::User.ldap_username, Runtime::User.ldap_password]
|
|
else
|
|
[Runtime::User.username, Runtime::User.password]
|
|
end
|
|
end
|
|
|
|
def read_netrc_content
|
|
File.exist?(netrc_file_path) ? File.readlines(netrc_file_path) : []
|
|
end
|
|
|
|
def save_netrc_content
|
|
# Despite libcurl supporting a custom .netrc location through the
|
|
# CURLOPT_NETRC_FILE environment variable, git does not support it :(
|
|
# Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
|
|
#
|
|
# This will create a .netrc in the correct working directory, which is
|
|
# a temporary directory created in .perform()
|
|
#
|
|
FileUtils.mkdir_p(tmp_home_dir)
|
|
File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
|
|
File.chmod(0600, netrc_file_path)
|
|
end
|
|
|
|
def tmp_home_dir
|
|
@tmp_home_dir ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
|
|
end
|
|
|
|
def push_options_hash_to_string(opts)
|
|
return if opts.nil?
|
|
|
|
prefix = "-o merge_request"
|
|
opts.each_with_object([]) do |(key, value), options|
|
|
if value.is_a?(Array)
|
|
value.each do |item|
|
|
options << "#{prefix}.#{key}=\"#{item}\""
|
|
end
|
|
elsif value == true
|
|
options << "#{prefix}.#{key}"
|
|
else
|
|
options << "#{prefix}.#{key}=\"#{value}\""
|
|
end
|
|
end.join(' ')
|
|
end
|
|
|
|
def netrc_file_path
|
|
@netrc_file_path ||= File.join(tmp_home_dir, '.netrc')
|
|
end
|
|
|
|
def netrc_content
|
|
"machine #{uri.host} login #{username} password #{password}"
|
|
end
|
|
|
|
def netrc_already_contains_content?
|
|
read_netrc_content.grep(/^#{Regexp.escape(netrc_content)}$/).any?
|
|
end
|
|
|
|
def run_git(command_str, env: self.env_vars, max_attempts: 1)
|
|
run(command_str, env: env, max_attempts: max_attempts, log_prefix: 'Git: ')
|
|
end
|
|
end
|
|
end
|
|
end
|