summaryrefslogtreecommitdiff
path: root/scripts/vcsget.sh.in
blob: d33c9ec37b35b3ef10d13e12bb4fdae04112759e (plain)
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
#!/bin/bash
#
#   vcsget - downloader agent that generates tarballs from VCS repos
#   @configure_input@
#
#   Copyright (c) 2012 Luke Shumaker <lukeshu@sbcglobal.net>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# gettext initialization
export TEXTDOMAIN='pacman-scripts'
export TEXTDOMAINDIR='@localedir@'

unset CDPATH

m4_include(library/output_format.sh)

##
#  usage : in_array( $needle, $haystack )
# return : 0 - found
#          1 - not found
##
in_array() {
	local needle=$1; shift
	local item
	for item in "$@"; do
		[[ $item = "$needle" ]] && return 0 # Found
	done
	return 1 # Not Found
}

usage() {
	echo "Usage: $0 <URL> <OUTPUT_TARBALL>"
	echo ""
	echo "Talks with multiple version control systems (VCSs) to create a"
	echo "tarball of a specific commit."
	echo ""
	echo "<OUTPUT_TARBALL> is a plain, uncompressed tarball. Given the same"
	echo "commit, the tarball will always have the same checksum."
	echo ""
	echo "<URL> is the repository, and possibly commit to download and tar."
	echo "The following schemes are recognized:"
	echo "  * cvs://"
	echo "  * git://"
	echo "  * git+PROTO://"
	echo "  * svn+PROTO://"
	echo "  * bzr://"
	echo "  * hg+PROTO://"
	echo ""
	echo "URLs to be consumed by $0 are not always in the format of the"
	echo "relevent VCS program, but normalized to proper URLs. Simply, this"
	echo "means:"
	echo ""
	echo "    scheme://[authinfo@]host[:port]/path[#fragment]"
	echo ""
	echo "Where <authinfo> is in the format \"username[:password]\" and"
	echo "<fragment> is the commit ID, branch, or tag to be returned."
}

##
# Get the column "$2" from the space-delimited string "$1".
# This has the advantage over `set -- $1` and `arr=($1)` that it separates on
# a single space, instead of '\s+', which allows it to have empty columns.
##
get_field() {
	local str=$1
	local i=$2
	echo "$str"|cut -d' ' -f $i
}

##
# Parse a URL into parts, according to RFC 3986, "URI Generic Syntax".
# Sets the variables `scheme`, `authority`, `path`, `query` and `fragment`.
##
parse_url() {
	local url=$1

	# This regex is copied from RFC 3986
	local regex='^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?'
	local parts=$(echo "$url"|sed -r "s@$regex@\2 \4 \5 \7 \9@")

	scheme=$(   get_field "$parts" 1)
	authority=$(get_field "$parts" 2)
	path=$(     get_field "$parts" 3)
	query=$(    get_field "$parts" 4)
	fragment=$( get_field "$parts" 5)
}

##
# Parse an authority from a URL.
# Sets the variables `user`, `pass`, `sock`, `host` and `port`.
# `sock` is socket, which is defined as `hostport` in the RFC.
##
parse_authority() {
	local authority=$1

	local regex='^(([^:]*)(:([^@]*))?@)?(([^:]+)(:([0-9]*))?)'
	#             12      3 4           56      7 8
	# 1 = userinfo@
	# 2 = user
	# 3 = :pass
	# 4 = pass
	# 5 = hostport
	# 6 = host
	# 7 = :port
	# 8 = port
	local parts=$(echo "$authority"|sed -r "s%$regex%\2 \4 \5 \6 \8%")
	user=$(get_field "$parts" 1)
	pass=$(get_field "$parts" 2)
	sock=$(get_field "$parts" 3)
	host=$(get_field "$parts" 4)
	port=$(get_field "$parts" 5)
}

download_cvs() {
	# TODO
	error "$(gettext 'CVS not implemented')"
	exit 1
}

download_git() {
	msg "$(gettext 'Getting source from git')"
	: ${fragment:=master} # default to 'master' if not set
	send_url="${scheme#git+}://${authority}${path}"

	# Where to checkout the code to
	dir="$basedir/git/$send_url"
	if [[ "${dir##*.}" != git ]]; then
		dir="${dir}/.git"
	fi

	# Download
	if [ ! -d "$dir" ]; then
		msg2 "$(gettext 'doing initial clone')"
		mkdir -p "$dir"
		git clone --bare "$send_url" "$dir"
	fi

	cd "$dir"
	refs=($(git show-ref|sed 's@.*/@@'|sort -u))
	if in_array "$fragment" "${refs[@]}"; then
		# The ref is symbolic, so it may have changed upstream
		msg2 "$(gettext 'fetching updates')"
		git fetch --all
	fi

	# Generate tarball
	archive=(git archive --format=tar --prefix="$base/"
		"$fragment" -o "$tarball")
	msg2 "$(gettext 'trying to generate tarball')"
	if "${archive[@]}" 2>/dev/null; then
		# success
		msg2 "$(gettext 'success')"
		exit 0
	else
		# the relevent commit is probably newer than we have
		msg2 "$(gettext 'failure, forcing an update')"
		git fetch --all
		msg2 "$(gettext 'generating tarball')"
		"${archive[@]}"
		local s=$?
		exit $?
	fi
}

download_svn() {
	msg "$(gettext 'Getting source from Subversion')"
	parse_authority "$authority"
	send_url="${scheme#svn+}://${sock}${path}"
	args=('--config-dir' "$basedir/svn/.config")
	if [ -n "$user" ]; then
		args+=('--username' "$user")
	fi
	if [ -n "$pass" ]; then
		args+=('--password' "$pass")
	fi
	if [ -n "$fragment" ]; then
		args+=('-r' "$fragment")
	fi

	# Where to checkout the code to
	dir="$basedir/svn/$send_url#$fragment"

	# Download
	if [ ! -d "$dir" ]; then
		mkdir -p "$dir"
		svn co "${args[@]}" "$send_url" "$dir"
	else
		cd "$dir"
		svn up "${args[@]}"
	fi

	# Generate tarball
	mkdir "$dir.tmp$$"
	cp -r "$dir" "$dir.tmp$$/$base"
	find "$dir.tmp$$/$base" -name .svn -exec rm -rf '{}' +
	cd "$dir.tmp$$"
	bsdtar cf "$tarball" "$base"
	rm -rf "$dir.tmp$$"
}

download_bzr() {
	# TODO finish bzr support
	error "$(gettext 'Bazaar not implemented')"
	exit 1

	send_url="${url%%#*}"
	if [ "$scheme" != 'bzr+ssh' ]; then
		send_url="${send_url#bzr+}"
	fi

	dir="$basedir/bzr/$send_url"

	# Download
	if [ ! -d "$dir" ]; then
		msg2 "$(gettext 'doing initial checkout')"
		mkdir -p "$dir"
		bzr checkout "$send_url" "$dir"
	fi

	cd "$dir"
	bzr update

	# Generate tarball
}

download_hg() {
	msg "$(gettext 'Getting source from Mercurial')"
	send_url="${url#hg+}"
	hg clone -U "$send_url"
	dir="$basedir/hg/${send_url%%#*}"

	# Download
	if [ ! -d "$dir" ]; then
		mkdir -p "$dir"
		hg clone "$send_url" "$dir"
	else
		cd "$dir"
		hg update -r "$fragment"
	fi

	# Generate tarball
	mkdir "$dir.tmp$$"
	cp -r "$dir" "$dir.tmp$$/$base"
	rm -rf "$dir.tmp$$/$base/.hg"
	cd "$dir.tmp$$"
	bsdtar cf "$tarball" "$base"
	rm -rf "$dir.tmp$$"
}

main() {
	url=$1
	tarball=$(readlink -m "$2")

	base="${tarball##*/}"
	base="${base%%.*}"
	basedir="${tarball%/*}/vcs-cache"

	if ( in_array '-h' "$@" || in_array '--help' "$@" ); then
		usage
		exit 0
	fi
	if [ "$#" -ne 2 ]; then
		usage
		exit 1
	fi

	msg "$(printf "$(gettext "Saving '%s' to '%s'")" "$url" "$tarball")"

	parse_url "$url"
	case "$scheme" in
		cvs) download_cvs;;
		git+*|git) download_git;;
		svn+*) download_svn;;
		bzr) download_bzr;;
		hg+*) download_hg;;
		*)
			error "$(gettext 'Unable to match a VCS with scheme'): $scheme"
			exit 1;;
	esac
}

main "$@"