1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
require_relative 'push_results'
module Gitlab
module Git
module RepositoryMirroring
GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout
RemoteError = Class.new(StandardError)
REFMAPS = {
# With `:all_refs`, the repository is equivalent to the result of `git clone --mirror`
all_refs: '+refs/*:refs/*',
heads: '+refs/heads/*:refs/heads/*',
tags: '+refs/tags/*:refs/tags/*'
}.freeze
def remote_branches(remote_name)
branches = []
rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref|
name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '')
begin
target_commit = Gitlab::Git::Commit.find(self, ref.target.oid)
branches << Gitlab::Git::Branch.new(self, name, ref.target, target_commit)
rescue Rugged::ReferenceError
# Omit invalid branch
end
end
branches
end
def push_remote_branches(remote_name, branch_names, forced: true, env: {})
success = @gitlab_projects.push_branches(
remote_name,
GITLAB_PROJECTS_TIMEOUT,
forced,
branch_names,
env: env
)
begin
success || gitlab_projects_error
rescue CommandError => ex
results = PushResults.new(ex.message)
accepted_branches = results.accepted_branches
rejected_branches = results.rejected_branches
@gitlab_projects.logger.info(
"Failed to push to remote #{remote_name}. " \
"Accepted: #{accepted_branches.join(', ')} / " \
"Rejected: #{rejected_branches.join(', ')}"
)
raise ex unless accepted_branches.any? &&
@gitlab_projects.features.enabled?(:push_mirror_retry)
@gitlab_projects.logger.info("Retrying failed push to #{remote_name} with limited branches: #{accepted_branches}")
# Try one more push without branches that failed
success = @gitlab_projects.push_branches(
remote_name,
GITLAB_PROJECTS_TIMEOUT,
forced,
accepted_branches,
env: env
)
success || gitlab_projects_error
end
end
def delete_remote_branches(remote_name, branch_names, env: {})
success = @gitlab_projects.delete_remote_branches(remote_name, branch_names, env: env)
success || gitlab_projects_error
end
def set_remote_as_mirror(remote_name, refmap: :all_refs)
set_remote_refmap(remote_name, refmap)
rugged.config["remote.#{remote_name}.mirror"] = true
rugged.config["remote.#{remote_name}.prune"] = true
end
def remote_tags(remote, env: {})
# Each line has this format: "dc872e9fa6963f8f03da6c8f6f264d0845d6b092\trefs/tags/v1.10.0\n"
# We want to convert it to: [{ 'v1.10.0' => 'dc872e9fa6963f8f03da6c8f6f264d0845d6b092' }, ...]
list_remote_tags(remote, env: env).map do |line|
target, path = line.strip.split("\t")
# When the remote repo does not have tags.
if target.nil? || path.nil?
Rails.logger.info "Empty or invalid list of tags for remote: #{remote}"
break []
end
name = path.split('/', 3).last
# We're only interested in tag references
# See: http://stackoverflow.com/questions/15472107/when-listing-git-ls-remote-why-theres-after-the-tag-name
next if name =~ /\^\{\}\Z/
target_commit = Gitlab::Git::Commit.find(self, target)
Gitlab::Git::Tag.new(self,
name: name,
target: target,
target_commit: target_commit)
end.compact
end
private
def set_remote_refmap(remote_name, refmap)
Array(refmap).each_with_index do |refspec, i|
refspec = REFMAPS[refspec] || refspec
# We need multiple `fetch` entries, but Rugged only allows replacing a config, not adding to it.
# To make sure we start from scratch, we set the first using rugged, and use `git` for any others
if i == 0
rugged.config["remote.#{remote_name}.fetch"] = refspec
else
run_git(%W[config --add remote.#{remote_name}.fetch #{refspec}])
end
end
end
def list_remote_tags(remote, env:)
tag_list, exit_code, error = nil
cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} ls-remote --tags #{remote}]
Open3.popen3(env, *cmd) do |_stdin, stdout, stderr, wait_thr|
tag_list = stdout.read
error = stderr.read
exit_code = wait_thr.value.exitstatus
end
raise RemoteError, error unless exit_code.zero?
tag_list.split("\n")
end
end
end
end
|