Update from gnulib.
[libiconv.git] / gitsub.sh
blob96651980680b6e867e501505d99ff8928a051ed2
1 #! /bin/sh
3 # Copyright (C) 2019 Free Software Foundation, Inc.
4 # Written by Bruno Haible <bruno@clisp.org>, 2019.
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <https://www.gnu.org/licenses/>.
19 # Program that manages the subdirectories of a git checkout of a package
20 # that come from other packages (called "dependency packages").
22 # This program is similar in spirit to 'git submodule', with three
23 # essential differences:
25 # 1) Its options are easy to remember, and do not require knowledge of
26 # 'git submodule'.
28 # 2) The developer may choose to work on a different checkout for each
29 # dependency package. This is important when the developer is
30 # preparing simultaneous changes to the package and the dependency
31 # package, or is using the dependency package in several packages.
33 # The developer indicates this different checkout by setting the
34 # environment variable <SUBDIR>_SRCDIR (e.g. GNULIB_SRCDIR) to point to it.
36 # 3) The package maintainer may choose to use or not use git submodules.
38 # The advantages of management through a git submodule are:
39 # - Changes to the dependency package cannot suddenly break your package.
40 # In other words, when there is an incompatible change that will cause
41 # a breakage, you can fix things at your pace; you are not forced to
42 # cope with such breakages in an emergency.
43 # - When you need to make a change as a response to a change in the
44 # dependency package, your co-developers cannot accidentally mix things
45 # up (for example, use a combination of your newest change with an
46 # older version of the dependency package).
48 # The advantages of management without a git submodule (just as a plain
49 # subdirectory, let's call it a "subcheckout") are:
50 # - The simplicity: you are conceptually always using the newest revision
51 # of the dependency package.
52 # - You don't have to remember to periodially upgrade the dependency.
53 # Upgrading the dependency is an implicit operation.
55 # This program is meant to be copied to the top-level directory of the package,
56 # together with a configuration file. The configuration is supposed to be
57 # named '.gitmodules' and to define:
58 # * The git submodules, as described in "man 5 gitmodules" or
59 # <https://git-scm.com/docs/gitmodules>. For example:
61 # [submodule "gnulib"]
62 # url = git://git.savannah.gnu.org/gnulib.git
63 # path = gnulib
65 # You don't add this piece of configuration to .gitmodules manually. Instead,
66 # you would invoke
67 # $ git submodule add --name "gnulib" -- git://git.savannah.gnu.org/gnulib.git gnulib
69 # * The subdirectories that are not git submodules, in a similar syntax. For
70 # example:
72 # [subcheckout "gnulib"]
73 # url = git://git.savannah.gnu.org/gnulib.git
74 # path = gnulib
76 # Here the URL is the one used for anonymous checkouts of the dependency
77 # package. If the developer needs a checkout with write access, they can
78 # either set the GNULIB_SRCDIR environment variable to point to that checkout
79 # or modify the gnulib/.git/config file to enter a different URL.
81 scriptname="$0"
82 scriptversion='2019-04-01'
83 nl='
85 IFS=" "" $nl"
87 # func_usage
88 # outputs to stdout the --help usage message.
89 func_usage ()
91 echo "\
92 Usage: gitsub.sh pull [SUBDIR]
93 gitsub.sh upgrade [SUBDIR]
94 gitsub.sh checkout SUBDIR REVISION
96 Operations:
98 gitsub.sh pull [SUBDIR]
99 You should perform this operation after 'git clone ...' and after
100 every 'git pull'.
101 It brings your checkout in sync with what the other developers of
102 your package have committed and pushed.
103 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
104 value, nothing is done for this SUBDIR.
105 If no SUBDIR is specified, the operation applies to all dependencies.
107 gitsub.sh upgrade [SUBDIR]
108 You should perform this operation periodically, to ensure currency
109 of the dependency package revisions that you use.
110 This operation pulls and checks out the changes that the developers
111 of the dependency package have committed and pushed.
112 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
113 value, nothing is done for this SUBDIR.
114 If no SUBDIR is specified, the operation applies to all dependencies.
116 gitsub.sh checkout SUBDIR REVISION
117 Checks out a specific revision for a dependency package.
118 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
119 value, this operation fails.
121 This script requires the git program in the PATH and an internet connection.
125 # func_version
126 # outputs to stdout the --version message.
127 func_version ()
129 year=`echo "$scriptversion" | sed -e 's/^\(....\)-.*/\1/'`
130 echo "\
131 gitsub.sh (GNU gnulib) $scriptversion
132 Copyright (C) 2019-$year Free Software Foundation, Inc.
133 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
134 This is free software: you are free to change and redistribute it.
135 There is NO WARRANTY, to the extent permitted by law.
137 printf "Written by %s.\n" "Bruno Haible"
140 # func_fatal_error message
141 # outputs to stderr a fatal error message, and terminates the program.
142 # Input:
143 # - scriptname name of this program
144 func_fatal_error ()
146 echo "$scriptname: *** $1" 1>&2
147 echo "$scriptname: *** Stop." 1>&2
148 exit 1
151 # func_warning message
152 # Outputs to stderr a warning message,
153 func_warning ()
155 echo "gitsub.sh: warning: $1" 1>&2
158 # func_note message
159 # Outputs to stdout a note message,
160 func_note ()
162 echo "gitsub.sh: note: $1"
165 # Unset CDPATH. Otherwise, output from 'cd dir' can surprise callers.
166 (unset CDPATH) >/dev/null 2>&1 && unset CDPATH
168 # Command-line option processing.
169 mode=
170 while test $# -gt 0; do
171 case "$1" in
172 --help | --hel | --he | --h )
173 func_usage
174 exit $? ;;
175 --version | --versio | --versi | --vers | --ver | --ve | --v )
176 func_version
177 exit $? ;;
178 -- )
179 # Stop option processing
180 shift
181 break ;;
182 -* )
183 echo "gitsub.sh: unknown option $1" 1>&2
184 echo "Try 'gitsub.sh --help' for more information." 1>&2
185 exit 1 ;;
187 break ;;
188 esac
189 done
190 if test $# = 0; then
191 echo "gitsub.sh: missing operation argument" 1>&2
192 echo "Try 'gitsub.sh --help' for more information." 1>&2
193 exit 1
195 case "$1" in
196 pull | upgrade | checkout )
197 mode="$1"
198 shift ;;
200 echo "gitsub.sh: unknown operation '$1'" 1>&2
201 echo "Try 'gitsub.sh --help' for more information." 1>&2
202 exit 1 ;;
203 esac
204 if test $# = 2 && test $mode != checkout || test $# -gt 2; then
205 echo "gitsub.sh: too many arguments in '$mode' mode" 1>&2
206 echo "Try 'gitsub.sh --help' for more information." 1>&2
207 exit 1
209 if test $# = 0 && test $mode = checkout; then
210 echo "gitsub.sh: too few arguments in '$mode' mode" 1>&2
211 echo "Try 'gitsub.sh --help' for more information." 1>&2
212 exit 1
215 # Read the configuration.
216 # Output:
217 # - subcheckout_names space-separated list of subcheckout names
218 # - submodule_names space-separated list of submodule names
219 if test -f .gitmodules; then
220 subcheckout_names=`git config --file .gitmodules --get-regexp --name-only 'subcheckout\..*\.url' | sed -e 's/^subcheckout\.//' -e 's/\.url$//' | tr -d '\r' | tr '\n' ' '`
221 submodule_names=`git config --file .gitmodules --get-regexp --name-only 'submodule\..*\.url' | sed -e 's/^submodule\.//' -e 's/\.url$//' | tr -d '\r' | tr '\n' ' '`
222 else
223 subcheckout_names=
224 submodule_names=
227 # func_validate SUBDIR
228 # Verifies that the state on the file system is in sync with the declarations
229 # in the configuration file.
230 # Input:
231 # - subcheckout_names space-separated list of subcheckout names
232 # - submodule_names space-separated list of submodule names
233 # Output:
234 # - srcdirvar Environment that the user can set
235 # - srcdir Value of the environment variable
236 # - path if $srcdir = "": relative path of the subdirectory
237 # - needs_init if $srcdir = "" and $path is not yet initialized:
238 # true
239 # - url if $srcdir = "" and $path is not yet initialized:
240 # the repository URL
241 func_validate ()
243 srcdirvar=`echo "$1" | LC_ALL=C sed -e 's/[^a-zA-Z0-9]/_/g' | LC_ALL=C tr '[a-z]' '[A-Z]'`"_SRCDIR"
244 eval 'srcdir=$'"$srcdirvar"
245 path=
246 url=
247 if test -n "$srcdir"; then
248 func_note "Ignoring '$1' because $srcdirvar is set."
249 else
250 found=false
251 needs_init=
252 case " $subcheckout_names " in *" $1 "*)
253 found=true
254 # It ought to be a subcheckout.
255 path=`git config --file .gitmodules "subcheckout.$1.path"`
256 if test -z "$path"; then
257 path="$1"
259 if test -d "$path"; then
260 if test -d "$path/.git"; then
261 # It's a plain checkout.
263 else
264 if test -f "$path/.git"; then
265 # It's a submodule.
266 func_fatal_error "Subdirectory '$path' is supposed to be a plain checkout, but it is a submodule."
267 else
268 func_warning "Ignoring '$path' because it exists but is not a git checkout."
271 else
272 # The subdir does not yet exist.
273 needs_init=true
274 url=`git config --file .gitmodules "subcheckout.$1.url"`
275 if test -z "$url"; then
276 func_fatal_error "Property subcheckout.$1.url is not defined in .gitmodules"
280 esac
281 case " $submodule_names " in *" $1 "*)
282 found=true
283 # It ought to be a submodule.
284 path=`git config --file .gitmodules "submodule.$1.path"`
285 if test -z "$path"; then
286 path="$1"
288 if test -d "$path"; then
289 if test -d "$path/.git" || test -f "$path/.git"; then
290 # It's likely a submodule.
292 else
293 path_if_empty=`find "$path" -prune -empty 2>/dev/null`
294 if test -n "$path_if_empty"; then
295 # The subdir is empty.
296 needs_init=true
297 else
298 # The subdir is not empty.
299 # It is important to report an error, because we don't want to erase
300 # the user's files and 'git submodule update gnulib' sometimes reports
301 # "fatal: destination path '$path' already exists and is not an empty directory."
302 # but sometimes does not.
303 func_fatal_error "Subdir '$path' exists but is not a git checkout."
306 else
307 # The subdir does not yet exist.
308 needs_init=true
310 # Another way to determine needs_init could be:
311 # if git submodule status "$path" | grep '^-' > /dev/null; then
312 # needs_init=true
313 # fi
314 if test -n "$needs_init"; then
315 url=`git config --file .gitmodules "submodule.$1.url"`
316 if test -z "$url"; then
317 func_fatal_error "Property submodule.$1.url is not defined in .gitmodules"
321 esac
322 if ! $found; then
323 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
328 # func_cleanup_current_git_clone
329 # Cleans up the current 'git clone' operation.
330 # Input:
331 # - path
332 func_cleanup_current_git_clone ()
334 rm -rf "$path"
335 func_fatal_error "git clone failed"
338 # func_pull SUBDIR
339 # Implements the 'pull' operation.
340 func_pull ()
342 func_validate "$1"
343 if test -z "$srcdir"; then
344 case " $subcheckout_names " in *" $1 "*)
345 # It's a subcheckout.
346 if test -d "$path"; then
347 if test -d "$path/.git"; then
348 (cd "$path" && git pull) || func_fatal_error "git operation failed"
350 else
351 # The subdir does not yet exist. Create a plain checkout.
352 trap func_cleanup_current_git_clone 1 2 13 15
353 git clone "$url" "$path" || func_cleanup_current_git_clone
354 trap - 1 2 13 15
357 esac
358 case " $submodule_names " in *" $1 "*)
359 # It's a submodule.
360 if test -n "$needs_init"; then
361 # Create a submodule checkout.
362 git submodule init -- "$path" && git submodule update -- "$path" || func_fatal_error "git operation failed"
363 else
364 # See https://stackoverflow.com/questions/1030169/easy-way-to-pull-latest-of-all-git-submodules
365 # https://stackoverflow.com/questions/4611512/is-there-a-way-to-make-git-pull-automatically-update-submodules
366 git submodule update "$path" || func_fatal_error "git operation failed"
369 esac
373 # func_upgrade SUBDIR
374 # Implements the 'upgrade' operation.
375 func_upgrade ()
377 func_validate "$1"
378 if test -z "$srcdir"; then
379 if test -d "$path"; then
380 case " $subcheckout_names " in *" $1 "*)
381 # It's a subcheckout.
382 if test -d "$path/.git"; then
383 (cd "$path" && git pull) || func_fatal_error "git operation failed"
386 esac
387 case " $submodule_names " in *" $1 "*)
388 # It's a submodule.
389 if test -z "$needs_init"; then
390 (cd "$path" && git fetch && git merge origin/master) || func_fatal_error "git operation failed"
393 esac
394 else
395 # The subdir does not yet exist.
396 func_fatal_error "Subdirectory '$path' does not exist yet. Use 'gitsub.sh pull' to create it."
401 # func_checkout SUBDIR REVISION
402 # Implements the 'checkout' operation.
403 func_checkout ()
405 func_validate "$1"
406 if test -z "$srcdir"; then
407 if test -d "$path"; then
408 case " $subcheckout_names " in *" $1 "*)
409 # It's a subcheckout.
410 if test -d "$path/.git"; then
411 (cd "$path" && git checkout "$2") || func_fatal_error "git operation failed"
414 esac
415 case " $submodule_names " in *" $1 "*)
416 # It's a submodule.
417 if test -z "$needs_init"; then
418 (cd "$path" && git checkout "$2") || func_fatal_error "git operation failed"
421 esac
422 else
423 # The subdir does not yet exist.
424 func_fatal_error "Subdirectory '$path' does not exist yet. Use 'gitsub.sh pull' to create it."
429 case "$mode" in
430 pull )
431 if test $# = 0; then
432 for sub in $subcheckout_names $submodule_names; do
433 func_pull "$sub"
434 done
435 else
436 valid=false
437 for sub in $subcheckout_names $submodule_names; do
438 if test "$sub" = "$1"; then
439 valid=true
441 done
442 if $valid; then
443 func_pull "$1"
444 else
445 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
450 upgrade )
451 if test $# = 0; then
452 for sub in $subcheckout_names $submodule_names; do
453 func_upgrade "$sub"
454 done
455 else
456 valid=false
457 for sub in $subcheckout_names $submodule_names; do
458 if test "$sub" = "$1"; then
459 valid=true
461 done
462 if $valid; then
463 func_upgrade "$1"
464 else
465 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
470 checkout )
471 valid=false
472 for sub in $subcheckout_names $submodule_names; do
473 if test "$sub" = "$1"; then
474 valid=true
476 done
477 if $valid; then
478 func_checkout "$1" "$2"
479 else
480 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
483 esac