/
git.rb
165 lines (141 loc) · 4.99 KB
/
git.rb
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
module Pod
module Downloader
# Concreted Downloader class that provides support for specifications with
# git sources.
#
class Git < Base
def self.options
[:commit, :tag, :branch, :submodules]
end
def options_specific?
!(options[:commit] || options[:tag]).nil?
end
def checkout_options
options = {}
options[:git] = url
options[:commit] = target_git('rev-parse', 'HEAD').chomp
options[:submodules] = true if self.options[:submodules]
options
end
def self.preprocess_options(options)
validate_input options
return options unless options[:branch]
command = ['ls-remote',
options[:git],
options[:branch]]
output = Git.execute_command('git', command)
match = commit_from_ls_remote output, options[:branch]
return options if match.nil?
options[:commit] = match
options.delete(:branch)
options
end
# Matches a commit from the branches reported by git ls-remote.
#
# @note When there is a branch and tag with the same name, it will match
# the branch, since `refs/heads` is sorted before `refs/tags`.
#
# @param [String] output
# The output from git ls-remote.
#
# @param [String] branch_name
# The desired branch to match a commit to.
#
# @return [String] commit hash string, or nil if no match found
#
def self.commit_from_ls_remote(output, branch_name)
return nil if branch_name.nil?
encoded_branch_name = branch_name.dup.force_encoding(Encoding::ASCII_8BIT)
match = %r{([a-z0-9]*)\trefs\/(heads|tags)\/#{Regexp.quote(encoded_branch_name)}}.match(output)
match[1] unless match.nil?
end
def self.validate_input(options)
input = [options[:git], options[:branch], options[:commit], options[:tag]]
invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') }
raise DownloaderError, "Provided unsafe input for git #{options}." if invalid
end
private_class_method :commit_from_ls_remote, :validate_input
private
# @!group Base class hooks
def download!
clone
checkout_commit if options[:commit]
end
# @return [void] Checks out the HEAD of the git source in the destination
# path.
#
def download_head!
clone(true)
end
# @!group Download implementations
executable :git
# Clones the repo. If possible the repo will be shallowly cloned.
#
# @note The `:commit` option requires a specific strategy as it is not
# possible to specify the commit to the `clone` command.
#
# @note `--branch` command line option can also take tags and detaches
# the HEAD.
#
# @param [Bool] force_head
# If any specific option should be ignored and the HEAD of the
# repo should be cloned.
#
# @param [Bool] shallow_clone
# Whether a shallow clone of the repo should be attempted, if
# possible given the specified {#options}.
#
def clone(force_head = false, shallow_clone = true)
ui_sub_action('Git download') do
begin
git! clone_arguments(force_head, shallow_clone)
update_submodules
rescue DownloaderError => e
if e.message =~ /^fatal:.*does not support (--depth|shallow capabilities)$/im
clone(force_head, false)
else
raise
end
end
end
end
def update_submodules
return unless options[:submodules]
target_git %w(submodule update --init --recursive)
end
# The arguments to pass to `git` to clone the repo.
#
# @param [Bool] force_head
# If any specific option should be ignored and the HEAD of the
# repo should be cloned.
#
# @param [Bool] shallow_clone
# Whether a shallow clone of the repo should be attempted, if
# possible given the specified {#options}.
#
# @return [Array<String>] arguments to pass to `git` to clone the repo.
#
def clone_arguments(force_head, shallow_clone)
command = ['clone', url, target_path, '--template=']
if shallow_clone && !options[:commit]
command += %w(--single-branch --depth 1)
end
unless force_head
if tag_or_branch = options[:tag] || options[:branch]
command += ['--branch', tag_or_branch]
end
end
command
end
# Checks out a specific commit of the cloned repo.
#
def checkout_commit
target_git 'checkout', '--quiet', options[:commit]
update_submodules
end
def target_git(*args)
git!(['-C', target_path] + args)
end
end
end
end