execute-tests: Fix compilation error with MSVC.
[gnulib.git] / top / gitsub.sh
blob4c6c05f666aec3155369f837c54540f170e51e86
1 #! /bin/sh
3 # Copyright (C) 2019-2020 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 [GIT_OPTIONS] [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 Supported GIT_OPTIONS (for expert git users) are:
106 --reference <repository>
107 --depth <depth>
108 --recursive
109 If no SUBDIR is specified, the operation applies to all dependencies.
111 gitsub.sh upgrade [SUBDIR]
112 You should perform this operation periodically, to ensure currency
113 of the dependency package revisions that you use.
114 This operation pulls and checks out the changes that the developers
115 of the dependency package have committed and pushed.
116 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
117 value, nothing is done for this SUBDIR.
118 If no SUBDIR is specified, the operation applies to all dependencies.
120 gitsub.sh checkout SUBDIR REVISION
121 Checks out a specific revision for a dependency package.
122 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
123 value, this operation fails.
125 This script requires the git program in the PATH and an internet connection.
129 # func_version
130 # outputs to stdout the --version message.
131 func_version ()
133 year=`echo "$scriptversion" | sed -e 's/^\(....\)-.*/\1/'`
134 echo "\
135 gitsub.sh (GNU gnulib) $scriptversion
136 Copyright (C) 2019-$year Free Software Foundation, Inc.
137 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
138 This is free software: you are free to change and redistribute it.
139 There is NO WARRANTY, to the extent permitted by law.
141 printf "Written by %s.\n" "Bruno Haible"
144 # func_fatal_error message
145 # outputs to stderr a fatal error message, and terminates the program.
146 # Input:
147 # - scriptname name of this program
148 func_fatal_error ()
150 echo "$scriptname: *** $1" 1>&2
151 echo "$scriptname: *** Stop." 1>&2
152 exit 1
155 # func_warning message
156 # Outputs to stderr a warning message,
157 func_warning ()
159 echo "gitsub.sh: warning: $1" 1>&2
162 # func_note message
163 # Outputs to stdout a note message,
164 func_note ()
166 echo "gitsub.sh: note: $1"
169 # Unset CDPATH. Otherwise, output from 'cd dir' can surprise callers.
170 (unset CDPATH) >/dev/null 2>&1 && unset CDPATH
172 # Command-line option processing.
173 mode=
174 while test $# -gt 0; do
175 case "$1" in
176 --help | --hel | --he | --h )
177 func_usage
178 exit $? ;;
179 --version | --versio | --versi | --vers | --ver | --ve | --v )
180 func_version
181 exit $? ;;
182 -- )
183 # Stop option processing
184 shift
185 break ;;
186 -* )
187 echo "gitsub.sh: unknown option $1" 1>&2
188 echo "Try 'gitsub.sh --help' for more information." 1>&2
189 exit 1 ;;
191 break ;;
192 esac
193 done
194 if test $# = 0; then
195 echo "gitsub.sh: missing operation argument" 1>&2
196 echo "Try 'gitsub.sh --help' for more information." 1>&2
197 exit 1
199 case "$1" in
200 pull | upgrade | checkout )
201 mode="$1"
202 shift ;;
204 echo "gitsub.sh: unknown operation '$1'" 1>&2
205 echo "Try 'gitsub.sh --help' for more information." 1>&2
206 exit 1 ;;
207 esac
208 if { test $mode = upgrade && test $# -gt 1; } \
209 || { test $mode = checkout && test $# -gt 2; }; then
210 echo "gitsub.sh: too many arguments in '$mode' mode" 1>&2
211 echo "Try 'gitsub.sh --help' for more information." 1>&2
212 exit 1
214 if test $# = 0 && test $mode = checkout; then
215 echo "gitsub.sh: too few arguments in '$mode' mode" 1>&2
216 echo "Try 'gitsub.sh --help' for more information." 1>&2
217 exit 1
220 # Read the configuration.
221 # Output:
222 # - subcheckout_names space-separated list of subcheckout names
223 # - submodule_names space-separated list of submodule names
224 if test -f .gitmodules; then
225 subcheckout_names=`git config --file .gitmodules --get-regexp --name-only 'subcheckout\..*\.url' | sed -e 's/^subcheckout\.//' -e 's/\.url$//' | tr -d '\r' | tr '\n' ' '`
226 submodule_names=`git config --file .gitmodules --get-regexp --name-only 'submodule\..*\.url' | sed -e 's/^submodule\.//' -e 's/\.url$//' | tr -d '\r' | tr '\n' ' '`
227 else
228 subcheckout_names=
229 submodule_names=
232 # func_validate SUBDIR
233 # Verifies that the state on the file system is in sync with the declarations
234 # in the configuration file.
235 # Input:
236 # - subcheckout_names space-separated list of subcheckout names
237 # - submodule_names space-separated list of submodule names
238 # Output:
239 # - srcdirvar Environment that the user can set
240 # - srcdir Value of the environment variable
241 # - path if $srcdir = "": relative path of the subdirectory
242 # - needs_init if $srcdir = "" and $path is not yet initialized:
243 # true
244 # - url if $srcdir = "" and $path is not yet initialized:
245 # the repository URL
246 func_validate ()
248 srcdirvar=`echo "$1" | LC_ALL=C sed -e 's/[^a-zA-Z0-9]/_/g' | LC_ALL=C tr '[a-z]' '[A-Z]'`"_SRCDIR"
249 eval 'srcdir=$'"$srcdirvar"
250 path=
251 url=
252 if test -n "$srcdir"; then
253 func_note "Ignoring '$1' because $srcdirvar is set."
254 else
255 found=false
256 needs_init=
257 case " $subcheckout_names " in *" $1 "*)
258 found=true
259 # It ought to be a subcheckout.
260 path=`git config --file .gitmodules "subcheckout.$1.path"`
261 if test -z "$path"; then
262 path="$1"
264 if test -d "$path"; then
265 if test -d "$path/.git"; then
266 # It's a plain checkout.
268 else
269 if test -f "$path/.git"; then
270 # It's a submodule.
271 func_fatal_error "Subdirectory '$path' is supposed to be a plain checkout, but it is a submodule."
272 else
273 func_warning "Ignoring '$path' because it exists but is not a git checkout."
276 else
277 # The subdir does not yet exist.
278 needs_init=true
279 url=`git config --file .gitmodules "subcheckout.$1.url"`
280 if test -z "$url"; then
281 func_fatal_error "Property subcheckout.$1.url is not defined in .gitmodules"
285 esac
286 case " $submodule_names " in *" $1 "*)
287 found=true
288 # It ought to be a submodule.
289 path=`git config --file .gitmodules "submodule.$1.path"`
290 if test -z "$path"; then
291 path="$1"
293 if test -d "$path"; then
294 if test -d "$path/.git" || test -f "$path/.git"; then
295 # It's likely a submodule.
297 else
298 path_if_empty=`find "$path" -prune -empty 2>/dev/null`
299 if test -n "$path_if_empty"; then
300 # The subdir is empty.
301 needs_init=true
302 else
303 # The subdir is not empty.
304 # It is important to report an error, because we don't want to erase
305 # the user's files and 'git submodule update gnulib' sometimes reports
306 # "fatal: destination path '$path' already exists and is not an empty directory."
307 # but sometimes does not.
308 func_fatal_error "Subdir '$path' exists but is not a git checkout."
311 else
312 # The subdir does not yet exist.
313 needs_init=true
315 # Another way to determine needs_init could be:
316 # if git submodule status "$path" | grep '^-' > /dev/null; then
317 # needs_init=true
318 # fi
319 if test -n "$needs_init"; then
320 url=`git config --file .gitmodules "submodule.$1.url"`
321 if test -z "$url"; then
322 func_fatal_error "Property submodule.$1.url is not defined in .gitmodules"
326 esac
327 if ! $found; then
328 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
333 # func_cleanup_current_git_clone
334 # Cleans up the current 'git clone' operation.
335 # Input:
336 # - path
337 func_cleanup_current_git_clone ()
339 rm -rf "$path"
340 func_fatal_error "git clone failed"
343 # func_pull SUBDIR GIT_OPTIONS
344 # Implements the 'pull' operation.
345 func_pull ()
347 func_validate "$1"
348 if test -z "$srcdir"; then
349 case " $subcheckout_names " in *" $1 "*)
350 # It's a subcheckout.
351 if test -d "$path"; then
352 if test -d "$path/.git"; then
353 (cd "$path" && git pull) || func_fatal_error "git operation failed"
355 else
356 # The subdir does not yet exist. Create a plain checkout.
357 trap func_cleanup_current_git_clone 1 2 13 15
358 git clone $2 "$url" "$path" || func_cleanup_current_git_clone
359 trap - 1 2 13 15
362 esac
363 case " $submodule_names " in *" $1 "*)
364 # It's a submodule.
365 if test -n "$needs_init"; then
366 # Create a submodule checkout.
367 git submodule init -- "$path" && git submodule update $2 -- "$path" || func_fatal_error "git operation failed"
368 else
369 # See https://stackoverflow.com/questions/1030169/easy-way-to-pull-latest-of-all-git-submodules
370 # https://stackoverflow.com/questions/4611512/is-there-a-way-to-make-git-pull-automatically-update-submodules
371 git submodule update "$path" || func_fatal_error "git operation failed"
374 esac
378 # func_upgrade SUBDIR
379 # Implements the 'upgrade' operation.
380 func_upgrade ()
382 func_validate "$1"
383 if test -z "$srcdir"; then
384 if test -d "$path"; then
385 case " $subcheckout_names " in *" $1 "*)
386 # It's a subcheckout.
387 if test -d "$path/.git"; then
388 (cd "$path" && git pull) || func_fatal_error "git operation failed"
391 esac
392 case " $submodule_names " in *" $1 "*)
393 # It's a submodule.
394 if test -z "$needs_init"; then
395 (cd "$path" && git fetch && git merge origin/master) || func_fatal_error "git operation failed"
398 esac
399 else
400 # The subdir does not yet exist.
401 func_fatal_error "Subdirectory '$path' does not exist yet. Use 'gitsub.sh pull' to create it."
406 # func_checkout SUBDIR REVISION
407 # Implements the 'checkout' operation.
408 func_checkout ()
410 func_validate "$1"
411 if test -z "$srcdir"; then
412 if test -d "$path"; then
413 case " $subcheckout_names " in *" $1 "*)
414 # It's a subcheckout.
415 if test -d "$path/.git"; then
416 (cd "$path" && git checkout "$2") || func_fatal_error "git operation failed"
419 esac
420 case " $submodule_names " in *" $1 "*)
421 # It's a submodule.
422 if test -z "$needs_init"; then
423 (cd "$path" && git checkout "$2") || func_fatal_error "git operation failed"
426 esac
427 else
428 # The subdir does not yet exist.
429 func_fatal_error "Subdirectory '$path' does not exist yet. Use 'gitsub.sh pull' to create it."
434 case "$mode" in
435 pull )
436 git_options=""
437 while test $# -gt 0; do
438 case "$1" in
439 --reference=* | --depth=* | --recursive)
440 git_options="$git_options $1"
441 shift
443 --reference | --depth)
444 git_options="$git_options $1 $2"
445 shift; shift
448 break
450 esac
451 done
452 if test $# -gt 1; then
453 echo "gitsub.sh: too many arguments in '$mode' mode" 1>&2
454 echo "Try 'gitsub.sh --help' for more information." 1>&2
455 exit 1
457 if test $# = 0; then
458 for sub in $subcheckout_names $submodule_names; do
459 func_pull "$sub" "$git_options"
460 done
461 else
462 valid=false
463 for sub in $subcheckout_names $submodule_names; do
464 if test "$sub" = "$1"; then
465 valid=true
467 done
468 if $valid; then
469 func_pull "$1" "$git_options"
470 else
471 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
476 upgrade )
477 if test $# = 0; then
478 for sub in $subcheckout_names $submodule_names; do
479 func_upgrade "$sub"
480 done
481 else
482 valid=false
483 for sub in $subcheckout_names $submodule_names; do
484 if test "$sub" = "$1"; then
485 valid=true
487 done
488 if $valid; then
489 func_upgrade "$1"
490 else
491 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
496 checkout )
497 valid=false
498 for sub in $subcheckout_names $submodule_names; do
499 if test "$sub" = "$1"; then
500 valid=true
502 done
503 if $valid; then
504 func_checkout "$1" "$2"
505 else
506 func_fatal_error "Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
509 esac