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
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
65 # You don't add this piece of configuration to .gitmodules manually. Instead,
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
72 # [subcheckout "gnulib"]
73 # url = git://git.savannah.gnu.org/gnulib.git
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.
82 scriptversion
='2019-04-01'
88 # outputs to stdout the --help usage message.
92 Usage: gitsub.sh pull [SUBDIR]
93 gitsub.sh upgrade [SUBDIR]
94 gitsub.sh checkout SUBDIR REVISION
98 gitsub.sh pull [GIT_OPTIONS] [SUBDIR]
99 You should perform this operation after 'git clone ...' and after
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>
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.
130 # outputs to stdout the --version message.
133 year
=`echo "$scriptversion" | sed -e 's/^\(....\)-.*/\1/'`
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.
147 # - scriptname name of this program
150 echo "$scriptname: *** $1" 1>&2
151 echo "$scriptname: *** Stop." 1>&2
155 # func_warning message
156 # Outputs to stderr a warning message,
159 echo "gitsub.sh: warning: $1" 1>&2
163 # Outputs to stdout a note message,
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.
174 while test $# -gt 0; do
176 --help |
--hel |
--he |
--h )
179 --version |
--versio |
--versi |
--vers |
--ver |
--ve |
--v )
183 # Stop option processing
187 echo "gitsub.sh: unknown option $1" 1>&2
188 echo "Try 'gitsub.sh --help' for more information." 1>&2
195 echo "gitsub.sh: missing operation argument" 1>&2
196 echo "Try 'gitsub.sh --help' for more information." 1>&2
200 pull | upgrade | checkout
)
204 echo "gitsub.sh: unknown operation '$1'" 1>&2
205 echo "Try 'gitsub.sh --help' for more information." 1>&2
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
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
220 # Read the configuration.
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' ' '`
232 # func_validate SUBDIR
233 # Verifies that the state on the file system is in sync with the declarations
234 # in the configuration file.
236 # - subcheckout_names space-separated list of subcheckout names
237 # - submodule_names space-separated list of submodule names
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:
244 # - url if $srcdir = "" and $path is not yet initialized:
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"
252 if test -n "$srcdir"; then
253 func_note
"Ignoring '$1' because $srcdirvar is set."
257 case " $subcheckout_names " in *" $1 "*)
259 # It ought to be a subcheckout.
260 path
=`git config --file .gitmodules "subcheckout.$1.path"`
261 if test -z "$path"; then
264 if test -d "$path"; then
265 if test -d "$path/.git"; then
266 # It's a plain checkout.
269 if test -f "$path/.git"; then
271 func_fatal_error
"Subdirectory '$path' is supposed to be a plain checkout, but it is a submodule."
273 func_warning
"Ignoring '$path' because it exists but is not a git checkout."
277 # The subdir does not yet exist.
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"
286 case " $submodule_names " in *" $1 "*)
288 # It ought to be a submodule.
289 path
=`git config --file .gitmodules "submodule.$1.path"`
290 if test -z "$path"; then
293 if test -d "$path"; then
294 if test -d "$path/.git" ||
test -f "$path/.git"; then
295 # It's likely a submodule.
298 path_if_empty
=`find "$path" -prune -empty 2>/dev/null`
299 if test -n "$path_if_empty"; then
300 # The subdir is empty.
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."
312 # The subdir does not yet exist.
315 # Another way to determine needs_init could be:
316 # if git submodule status "$path" | grep '^-' > /dev/null; then
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"
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.
337 func_cleanup_current_git_clone
()
340 func_fatal_error
"git clone failed"
343 # func_pull SUBDIR GIT_OPTIONS
344 # Implements the 'pull' operation.
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"
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
363 case " $submodule_names " in *" $1 "*)
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"
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"
378 # func_upgrade SUBDIR
379 # Implements the 'upgrade' operation.
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"
392 case " $submodule_names " in *" $1 "*)
394 if test -z "$needs_init"; then
395 (cd "$path" && git fetch
&& git merge origin
/master
) || func_fatal_error
"git operation failed"
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.
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"
420 case " $submodule_names " in *" $1 "*)
422 if test -z "$needs_init"; then
423 (cd "$path" && git checkout
"$2") || func_fatal_error
"git operation failed"
428 # The subdir does not yet exist.
429 func_fatal_error
"Subdirectory '$path' does not exist yet. Use 'gitsub.sh pull' to create it."
437 while test $# -gt 0; do
439 --reference=* |
--depth=* |
--recursive)
440 git_options
="$git_options $1"
443 --reference |
--depth)
444 git_options
="$git_options $1 $2"
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
458 for sub
in $subcheckout_names $submodule_names; do
459 func_pull
"$sub" "$git_options"
463 for sub
in $subcheckout_names $submodule_names; do
464 if test "$sub" = "$1"; then
469 func_pull
"$1" "$git_options"
471 func_fatal_error
"Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
478 for sub
in $subcheckout_names $submodule_names; do
483 for sub
in $subcheckout_names $submodule_names; do
484 if test "$sub" = "$1"; then
491 func_fatal_error
"Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
498 for sub
in $subcheckout_names $submodule_names; do
499 if test "$sub" = "$1"; then
504 func_checkout
"$1" "$2"
506 func_fatal_error
"Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"